feat: implement header/footer visibility controls for checkout and thankyou pages
- Created LayoutWrapper component to conditionally render header/footer based on route - Created MinimalHeader component (logo only) - Created MinimalFooter component (trust badges + policy links) - Created usePageVisibility hook to get visibility settings per page - Wrapped ClassicLayout with LayoutWrapper for conditional rendering - Header/footer visibility now controlled directly in React SPA - Settings: show/minimal/hide for both header and footer - Background color support for checkout and thankyou pages
This commit is contained in:
454
includes/Admin/AppearanceController.php
Normal file
454
includes/Admin/AppearanceController.php
Normal file
@@ -0,0 +1,454 @@
|
||||
<?php
|
||||
namespace WooNooW\Admin;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_Error;
|
||||
|
||||
class AppearanceController {
|
||||
|
||||
const OPTION_KEY = 'woonoow_appearance_settings';
|
||||
const API_NAMESPACE = 'woonoow/v1';
|
||||
|
||||
public static function init() {
|
||||
add_action('rest_api_init', [__CLASS__, 'register_routes']);
|
||||
}
|
||||
|
||||
public static function register_routes() {
|
||||
// Get all settings (public access for frontend)
|
||||
register_rest_route(self::API_NAMESPACE, '/appearance/settings', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_settings'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// Save general settings
|
||||
register_rest_route(self::API_NAMESPACE, '/appearance/general', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'save_general'],
|
||||
'permission_callback' => [__CLASS__, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Save header settings
|
||||
register_rest_route(self::API_NAMESPACE, '/appearance/header', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'save_header'],
|
||||
'permission_callback' => [__CLASS__, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Save footer settings
|
||||
register_rest_route(self::API_NAMESPACE, '/appearance/footer', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'save_footer'],
|
||||
'permission_callback' => [__CLASS__, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Save page-specific settings
|
||||
register_rest_route(self::API_NAMESPACE, '/appearance/pages/(?P<page>[a-zA-Z0-9_-]+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'save_page_settings'],
|
||||
'permission_callback' => [__CLASS__, 'check_permission'],
|
||||
'args' => [
|
||||
'page' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'enum' => ['shop', 'product', 'cart', 'checkout', 'thankyou', 'account'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public static function check_permission() {
|
||||
return current_user_can('manage_woocommerce');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all appearance settings
|
||||
*/
|
||||
public static function get_settings(WP_REST_Request $request) {
|
||||
$settings = get_option(self::OPTION_KEY, self::get_default_settings());
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'data' => $settings,
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save general settings
|
||||
*/
|
||||
public static function save_general(WP_REST_Request $request) {
|
||||
$settings = get_option(self::OPTION_KEY, self::get_default_settings());
|
||||
|
||||
$general_data = [
|
||||
'spa_mode' => sanitize_text_field($request->get_param('spaMode')),
|
||||
'typography' => [
|
||||
'mode' => sanitize_text_field($request->get_param('typography')['mode'] ?? 'predefined'),
|
||||
'predefined_pair' => sanitize_text_field($request->get_param('typography')['predefined_pair'] ?? 'modern'),
|
||||
'custom' => [
|
||||
'heading' => sanitize_text_field($request->get_param('typography')['custom']['heading'] ?? ''),
|
||||
'body' => sanitize_text_field($request->get_param('typography')['custom']['body'] ?? ''),
|
||||
],
|
||||
'scale' => floatval($request->get_param('typography')['scale'] ?? 1.0),
|
||||
],
|
||||
'colors' => [
|
||||
'primary' => sanitize_hex_color($request->get_param('colors')['primary'] ?? '#1a1a1a'),
|
||||
'secondary' => sanitize_hex_color($request->get_param('colors')['secondary'] ?? '#6b7280'),
|
||||
'accent' => sanitize_hex_color($request->get_param('colors')['accent'] ?? '#3b82f6'),
|
||||
'text' => sanitize_hex_color($request->get_param('colors')['text'] ?? '#111827'),
|
||||
'background' => sanitize_hex_color($request->get_param('colors')['background'] ?? '#ffffff'),
|
||||
],
|
||||
];
|
||||
|
||||
$settings['general'] = $general_data;
|
||||
update_option(self::OPTION_KEY, $settings);
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => 'General settings saved successfully',
|
||||
'data' => $general_data,
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save header settings
|
||||
*/
|
||||
public static function save_header(WP_REST_Request $request) {
|
||||
$settings = get_option(self::OPTION_KEY, self::get_default_settings());
|
||||
|
||||
$header_data = [
|
||||
'style' => sanitize_text_field($request->get_param('style')),
|
||||
'sticky' => (bool) $request->get_param('sticky'),
|
||||
'height' => sanitize_text_field($request->get_param('height')),
|
||||
'mobile_menu' => sanitize_text_field($request->get_param('mobileMenu')),
|
||||
'mobile_logo' => sanitize_text_field($request->get_param('mobileLogo')),
|
||||
'logo_width' => sanitize_text_field($request->get_param('logoWidth') ?? 'auto'),
|
||||
'logo_height' => sanitize_text_field($request->get_param('logoHeight') ?? '40px'),
|
||||
'elements' => [
|
||||
'logo' => (bool) ($request->get_param('elements')['logo'] ?? true),
|
||||
'navigation' => (bool) ($request->get_param('elements')['navigation'] ?? true),
|
||||
'search' => (bool) ($request->get_param('elements')['search'] ?? true),
|
||||
'account' => (bool) ($request->get_param('elements')['account'] ?? true),
|
||||
'cart' => (bool) ($request->get_param('elements')['cart'] ?? true),
|
||||
'wishlist' => (bool) ($request->get_param('elements')['wishlist'] ?? false),
|
||||
],
|
||||
];
|
||||
|
||||
$settings['header'] = $header_data;
|
||||
update_option(self::OPTION_KEY, $settings);
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => 'Header settings saved successfully',
|
||||
'data' => $header_data,
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save footer settings
|
||||
*/
|
||||
public static function save_footer(WP_REST_Request $request) {
|
||||
$settings = get_option(self::OPTION_KEY, self::get_default_settings());
|
||||
|
||||
$social_links = $request->get_param('socialLinks') ?? [];
|
||||
$sanitized_links = [];
|
||||
foreach ($social_links as $link) {
|
||||
$sanitized_links[] = [
|
||||
'id' => sanitize_text_field($link['id'] ?? ''),
|
||||
'platform' => sanitize_text_field($link['platform'] ?? ''),
|
||||
'url' => esc_url_raw($link['url'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
// Sanitize contact data
|
||||
$contact_data = $request->get_param('contactData');
|
||||
$sanitized_contact = [
|
||||
'email' => sanitize_email($contact_data['email'] ?? ''),
|
||||
'phone' => sanitize_text_field($contact_data['phone'] ?? ''),
|
||||
'address' => sanitize_textarea_field($contact_data['address'] ?? ''),
|
||||
'show_email' => (bool) ($contact_data['show_email'] ?? true),
|
||||
'show_phone' => (bool) ($contact_data['show_phone'] ?? true),
|
||||
'show_address' => (bool) ($contact_data['show_address'] ?? true),
|
||||
];
|
||||
|
||||
// Sanitize labels
|
||||
$labels = $request->get_param('labels');
|
||||
$sanitized_labels = [
|
||||
'contact_title' => sanitize_text_field($labels['contact_title'] ?? 'Contact'),
|
||||
'menu_title' => sanitize_text_field($labels['menu_title'] ?? 'Quick Links'),
|
||||
'social_title' => sanitize_text_field($labels['social_title'] ?? 'Follow Us'),
|
||||
'newsletter_title' => sanitize_text_field($labels['newsletter_title'] ?? 'Newsletter'),
|
||||
'newsletter_description' => sanitize_text_field($labels['newsletter_description'] ?? 'Subscribe to get updates'),
|
||||
];
|
||||
|
||||
// Sanitize custom sections
|
||||
$sections = $request->get_param('sections') ?? [];
|
||||
$sanitized_sections = [];
|
||||
foreach ($sections as $section) {
|
||||
$sanitized_sections[] = [
|
||||
'id' => sanitize_text_field($section['id']),
|
||||
'title' => sanitize_text_field($section['title']),
|
||||
'type' => sanitize_text_field($section['type']),
|
||||
'content' => wp_kses_post($section['content'] ?? ''),
|
||||
'visible' => (bool) ($section['visible'] ?? true),
|
||||
];
|
||||
}
|
||||
|
||||
$footer_data = [
|
||||
'columns' => sanitize_text_field($request->get_param('columns')),
|
||||
'style' => sanitize_text_field($request->get_param('style')),
|
||||
'copyright_text' => wp_kses_post($request->get_param('copyrightText')),
|
||||
'elements' => [
|
||||
'newsletter' => (bool) ($request->get_param('elements')['newsletter'] ?? true),
|
||||
'social' => (bool) ($request->get_param('elements')['social'] ?? true),
|
||||
'payment' => (bool) ($request->get_param('elements')['payment'] ?? true),
|
||||
'copyright' => (bool) ($request->get_param('elements')['copyright'] ?? true),
|
||||
'menu' => (bool) ($request->get_param('elements')['menu'] ?? true),
|
||||
'contact' => (bool) ($request->get_param('elements')['contact'] ?? true),
|
||||
],
|
||||
'social_links' => $sanitized_links,
|
||||
'contact_data' => $sanitized_contact,
|
||||
'labels' => $sanitized_labels,
|
||||
'sections' => $sanitized_sections,
|
||||
];
|
||||
|
||||
$settings['footer'] = $footer_data;
|
||||
update_option(self::OPTION_KEY, $settings);
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => 'Footer settings saved successfully',
|
||||
'data' => $footer_data,
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save page-specific settings
|
||||
*/
|
||||
public static function save_page_settings(WP_REST_Request $request) {
|
||||
$settings = get_option(self::OPTION_KEY, self::get_default_settings());
|
||||
$page = $request->get_param('page');
|
||||
|
||||
// Get all parameters from request
|
||||
$page_data = $request->get_json_params();
|
||||
|
||||
// Sanitize based on page type
|
||||
$sanitized_data = self::sanitize_page_data($page, $page_data);
|
||||
|
||||
$settings['pages'][$page] = $sanitized_data;
|
||||
update_option(self::OPTION_KEY, $settings);
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => ucfirst($page) . ' page settings saved successfully',
|
||||
'data' => $sanitized_data,
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize page-specific data
|
||||
*/
|
||||
private static function sanitize_page_data($page, $data) {
|
||||
$sanitized = [];
|
||||
|
||||
switch ($page) {
|
||||
case 'shop':
|
||||
$sanitized = [
|
||||
'layout' => [
|
||||
'grid_columns' => sanitize_text_field($data['layout']['grid_columns'] ?? '3'),
|
||||
'grid_style' => sanitize_text_field($data['layout']['grid_style'] ?? 'standard'),
|
||||
'card_style' => sanitize_text_field($data['layout']['card_style'] ?? 'card'),
|
||||
'aspect_ratio' => sanitize_text_field($data['layout']['aspect_ratio'] ?? 'square'),
|
||||
'card_text_align' => sanitize_text_field($data['layout']['card_text_align'] ?? 'left'),
|
||||
],
|
||||
'elements' => [
|
||||
'category_filter' => (bool) ($data['elements']['category_filter'] ?? true),
|
||||
'search_bar' => (bool) ($data['elements']['search_bar'] ?? true),
|
||||
'sort_dropdown' => (bool) ($data['elements']['sort_dropdown'] ?? true),
|
||||
'sale_badges' => (bool) ($data['elements']['sale_badges'] ?? true),
|
||||
'quick_view' => (bool) ($data['elements']['quick_view'] ?? false),
|
||||
],
|
||||
'sale_badge' => [
|
||||
'color' => sanitize_hex_color($data['sale_badge']['color'] ?? '#ef4444'),
|
||||
],
|
||||
'add_to_cart' => [
|
||||
'position' => sanitize_text_field($data['add_to_cart']['position'] ?? 'below'),
|
||||
'style' => sanitize_text_field($data['add_to_cart']['style'] ?? 'solid'),
|
||||
'show_icon' => (bool) ($data['add_to_cart']['show_icon'] ?? true),
|
||||
],
|
||||
];
|
||||
break;
|
||||
|
||||
case 'product':
|
||||
$sanitized = [
|
||||
'layout' => [
|
||||
'image_position' => sanitize_text_field($data['layout']['image_position'] ?? 'left'),
|
||||
'gallery_style' => sanitize_text_field($data['layout']['gallery_style'] ?? 'thumbnails'),
|
||||
'sticky_add_to_cart' => (bool) ($data['layout']['sticky_add_to_cart'] ?? false),
|
||||
],
|
||||
'elements' => [
|
||||
'breadcrumbs' => (bool) ($data['elements']['breadcrumbs'] ?? true),
|
||||
'related_products' => (bool) ($data['elements']['related_products'] ?? true),
|
||||
'reviews' => (bool) ($data['elements']['reviews'] ?? true),
|
||||
'share_buttons' => (bool) ($data['elements']['share_buttons'] ?? false),
|
||||
'product_meta' => (bool) ($data['elements']['product_meta'] ?? true),
|
||||
],
|
||||
'related_products' => [
|
||||
'title' => sanitize_text_field($data['related_products']['title'] ?? 'You May Also Like'),
|
||||
],
|
||||
'reviews' => [
|
||||
'placement' => sanitize_text_field($data['reviews']['placement'] ?? 'product_page'),
|
||||
'hide_if_empty' => (bool) ($data['reviews']['hide_if_empty'] ?? true),
|
||||
],
|
||||
];
|
||||
break;
|
||||
|
||||
case 'cart':
|
||||
$sanitized = [
|
||||
'layout' => [
|
||||
'style' => sanitize_text_field($data['layout']['style'] ?? 'fullwidth'),
|
||||
'summary_position' => sanitize_text_field($data['layout']['summary_position'] ?? 'right'),
|
||||
],
|
||||
'elements' => [
|
||||
'product_images' => (bool) ($data['elements']['product_images'] ?? true),
|
||||
'continue_shopping_button' => (bool) ($data['elements']['continue_shopping_button'] ?? true),
|
||||
'coupon_field' => (bool) ($data['elements']['coupon_field'] ?? true),
|
||||
'shipping_calculator' => (bool) ($data['elements']['shipping_calculator'] ?? false),
|
||||
],
|
||||
];
|
||||
break;
|
||||
|
||||
case 'checkout':
|
||||
$sanitized = [
|
||||
'layout' => [
|
||||
'style' => sanitize_text_field($data['layout']['style'] ?? 'two-column'),
|
||||
'order_summary' => sanitize_text_field($data['layout']['order_summary'] ?? 'sidebar'),
|
||||
'header_visibility' => sanitize_text_field($data['layout']['header_visibility'] ?? 'minimal'),
|
||||
'footer_visibility' => sanitize_text_field($data['layout']['footer_visibility'] ?? 'minimal'),
|
||||
'background_color' => sanitize_hex_color($data['layout']['background_color'] ?? '#f9fafb'),
|
||||
],
|
||||
'elements' => [
|
||||
'order_notes' => (bool) ($data['elements']['order_notes'] ?? true),
|
||||
'coupon_field' => (bool) ($data['elements']['coupon_field'] ?? true),
|
||||
'shipping_options' => (bool) ($data['elements']['shipping_options'] ?? true),
|
||||
'payment_icons' => (bool) ($data['elements']['payment_icons'] ?? true),
|
||||
],
|
||||
];
|
||||
break;
|
||||
|
||||
case 'thankyou':
|
||||
$sanitized = [
|
||||
'template' => sanitize_text_field($data['template'] ?? 'basic'),
|
||||
'header_visibility' => sanitize_text_field($data['header_visibility'] ?? 'show'),
|
||||
'footer_visibility' => sanitize_text_field($data['footer_visibility'] ?? 'minimal'),
|
||||
'background_color' => sanitize_hex_color($data['background_color'] ?? '#f9fafb'),
|
||||
'custom_message' => wp_kses_post($data['custom_message'] ?? ''),
|
||||
'elements' => [
|
||||
'order_details' => (bool) ($data['elements']['order_details'] ?? true),
|
||||
'continue_shopping_button' => (bool) ($data['elements']['continue_shopping_button'] ?? true),
|
||||
'related_products' => (bool) ($data['elements']['related_products'] ?? false),
|
||||
],
|
||||
];
|
||||
break;
|
||||
|
||||
case 'account':
|
||||
$sanitized = [
|
||||
'layout' => [
|
||||
'navigation_style' => sanitize_text_field($data['layout']['navigation_style'] ?? 'sidebar'),
|
||||
],
|
||||
'elements' => [
|
||||
'dashboard' => (bool) ($data['elements']['dashboard'] ?? true),
|
||||
'orders' => (bool) ($data['elements']['orders'] ?? true),
|
||||
'downloads' => (bool) ($data['elements']['downloads'] ?? false),
|
||||
'addresses' => (bool) ($data['elements']['addresses'] ?? true),
|
||||
'account_details' => (bool) ($data['elements']['account_details'] ?? true),
|
||||
],
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default settings structure
|
||||
*/
|
||||
public static function get_default_settings() {
|
||||
return [
|
||||
'general' => [
|
||||
'spa_mode' => 'full',
|
||||
'typography' => [
|
||||
'mode' => 'predefined',
|
||||
'predefined_pair' => 'modern',
|
||||
'custom' => [
|
||||
'heading' => '',
|
||||
'body' => '',
|
||||
],
|
||||
'scale' => 1.0,
|
||||
],
|
||||
'colors' => [
|
||||
'primary' => '#1a1a1a',
|
||||
'secondary' => '#6b7280',
|
||||
'accent' => '#3b82f6',
|
||||
'text' => '#111827',
|
||||
'background' => '#ffffff',
|
||||
],
|
||||
],
|
||||
'header' => [
|
||||
'style' => 'classic',
|
||||
'sticky' => true,
|
||||
'height' => 'normal',
|
||||
'mobile_menu' => 'hamburger',
|
||||
'mobile_logo' => 'left',
|
||||
'elements' => [
|
||||
'logo' => true,
|
||||
'navigation' => true,
|
||||
'search' => true,
|
||||
'account' => true,
|
||||
'cart' => true,
|
||||
'wishlist' => false,
|
||||
],
|
||||
],
|
||||
'footer' => [
|
||||
'columns' => '4',
|
||||
'style' => 'detailed',
|
||||
'copyright_text' => '© 2024 WooNooW. All rights reserved.',
|
||||
'elements' => [
|
||||
'newsletter' => true,
|
||||
'social' => true,
|
||||
'payment' => true,
|
||||
'copyright' => true,
|
||||
'menu' => true,
|
||||
'contact' => true,
|
||||
],
|
||||
'social_links' => [],
|
||||
],
|
||||
'pages' => [
|
||||
'shop' => [
|
||||
'layout' => [
|
||||
'grid_columns' => '3',
|
||||
'card_style' => 'card',
|
||||
'aspect_ratio' => 'square',
|
||||
],
|
||||
'elements' => [
|
||||
'category_filter' => true,
|
||||
'search_bar' => true,
|
||||
'sort_dropdown' => true,
|
||||
'sale_badges' => true,
|
||||
'quick_view' => false,
|
||||
],
|
||||
'add_to_cart' => [
|
||||
'position' => 'below',
|
||||
'style' => 'solid',
|
||||
'show_icon' => true,
|
||||
],
|
||||
],
|
||||
'product' => [],
|
||||
'cart' => [],
|
||||
'checkout' => [],
|
||||
'thankyou' => [],
|
||||
'account' => [],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -225,9 +225,8 @@ class CheckoutController {
|
||||
// Totals
|
||||
$order->calculate_totals();
|
||||
|
||||
// Mirror Woo hooks so extensions still work
|
||||
// Mirror Woo hooks so extensions still work (but not thankyou which outputs HTML)
|
||||
do_action('woocommerce_checkout_create_order', $order->get_id(), $order);
|
||||
do_action('woocommerce_thankyou', $order->get_id());
|
||||
|
||||
$order->save();
|
||||
|
||||
|
||||
196
includes/Api/NewsletterController.php
Normal file
196
includes/Api/NewsletterController.php
Normal file
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
namespace WooNooW\API;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_Error;
|
||||
|
||||
class NewsletterController {
|
||||
const API_NAMESPACE = 'woonoow/v1';
|
||||
|
||||
public static function register_routes() {
|
||||
register_rest_route(self::API_NAMESPACE, '/newsletter/subscribe', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'subscribe'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'email' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'validate_callback' => function($param) {
|
||||
return is_email($param);
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/newsletter/subscribers', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_subscribers'],
|
||||
'permission_callback' => function() {
|
||||
return current_user_can('manage_options');
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/newsletter/subscribers/(?P<email>[^/]+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [__CLASS__, 'delete_subscriber'],
|
||||
'permission_callback' => function() {
|
||||
return current_user_can('manage_options');
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/newsletter/template/(?P<template>[^/]+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_template'],
|
||||
'permission_callback' => function() {
|
||||
return current_user_can('manage_options');
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/newsletter/template/(?P<template>[^/]+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'save_template'],
|
||||
'permission_callback' => function() {
|
||||
return current_user_can('manage_options');
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
public static function get_template(WP_REST_Request $request) {
|
||||
$template = $request->get_param('template');
|
||||
$option_key = "woonoow_newsletter_{$template}_template";
|
||||
|
||||
$data = get_option($option_key, [
|
||||
'subject' => $template === 'welcome' ? 'Welcome to {site_name} Newsletter!' : 'Confirm your newsletter subscription',
|
||||
'content' => $template === 'welcome'
|
||||
? "Thank you for subscribing to our newsletter!\n\nYou'll receive updates about our latest products and offers.\n\nBest regards,\n{site_name}"
|
||||
: "Please confirm your newsletter subscription by clicking the link below:\n\n{confirmation_url}\n\nBest regards,\n{site_name}",
|
||||
]);
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'subject' => $data['subject'] ?? '',
|
||||
'content' => $data['content'] ?? '',
|
||||
], 200);
|
||||
}
|
||||
|
||||
public static function save_template(WP_REST_Request $request) {
|
||||
$template = $request->get_param('template');
|
||||
$subject = sanitize_text_field($request->get_param('subject'));
|
||||
$content = wp_kses_post($request->get_param('content'));
|
||||
|
||||
$option_key = "woonoow_newsletter_{$template}_template";
|
||||
|
||||
update_option($option_key, [
|
||||
'subject' => $subject,
|
||||
'content' => $content,
|
||||
]);
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => 'Template saved successfully',
|
||||
], 200);
|
||||
}
|
||||
|
||||
public static function delete_subscriber(WP_REST_Request $request) {
|
||||
$email = urldecode($request->get_param('email'));
|
||||
$subscribers = get_option('woonoow_newsletter_subscribers', []);
|
||||
|
||||
$subscribers = array_filter($subscribers, function($sub) use ($email) {
|
||||
return isset($sub['email']) && $sub['email'] !== $email;
|
||||
});
|
||||
|
||||
update_option('woonoow_newsletter_subscribers', array_values($subscribers));
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => 'Subscriber removed successfully',
|
||||
], 200);
|
||||
}
|
||||
|
||||
public static function subscribe(WP_REST_Request $request) {
|
||||
$email = sanitize_email($request->get_param('email'));
|
||||
|
||||
if (!is_email($email)) {
|
||||
return new WP_Error('invalid_email', 'Invalid email address', ['status' => 400]);
|
||||
}
|
||||
|
||||
// Get existing subscribers (now stored as objects with metadata)
|
||||
$subscribers = get_option('woonoow_newsletter_subscribers', []);
|
||||
|
||||
// Check if already subscribed
|
||||
$existing = array_filter($subscribers, function($sub) use ($email) {
|
||||
return isset($sub['email']) && $sub['email'] === $email;
|
||||
});
|
||||
|
||||
if (!empty($existing)) {
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => 'You are already subscribed to our newsletter!',
|
||||
], 200);
|
||||
}
|
||||
|
||||
// Check if email belongs to a WP user
|
||||
$user = get_user_by('email', $email);
|
||||
$user_id = $user ? $user->ID : null;
|
||||
|
||||
// Add new subscriber with metadata
|
||||
$subscribers[] = [
|
||||
'email' => $email,
|
||||
'user_id' => $user_id,
|
||||
'status' => 'active',
|
||||
'subscribed_at' => current_time('mysql'),
|
||||
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '',
|
||||
];
|
||||
|
||||
update_option('woonoow_newsletter_subscribers', $subscribers);
|
||||
|
||||
// Trigger notification events
|
||||
do_action('woonoow_newsletter_subscribed', $email, $user_id);
|
||||
|
||||
// Trigger notification system events (uses email builder)
|
||||
do_action('woonoow/notification/event', 'newsletter_welcome', 'customer', [
|
||||
'email' => $email,
|
||||
'user_id' => $user_id,
|
||||
'subscribed_at' => current_time('mysql'),
|
||||
]);
|
||||
|
||||
do_action('woonoow/notification/event', 'newsletter_subscribed_admin', 'staff', [
|
||||
'email' => $email,
|
||||
'user_id' => $user_id,
|
||||
'subscribed_at' => current_time('mysql'),
|
||||
]);
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => 'Successfully subscribed! Check your email for confirmation.',
|
||||
], 200);
|
||||
}
|
||||
|
||||
private static function send_welcome_email($email) {
|
||||
$site_name = get_bloginfo('name');
|
||||
$template = get_option('woonoow_newsletter_welcome_template', '');
|
||||
|
||||
if (empty($template)) {
|
||||
$template = "Thank you for subscribing to our newsletter!\n\nYou'll receive updates about our latest products and offers.\n\nBest regards,\n{site_name}";
|
||||
}
|
||||
|
||||
$subject = sprintf('Welcome to %s Newsletter!', $site_name);
|
||||
$message = str_replace('{site_name}', $site_name, $template);
|
||||
|
||||
wp_mail($email, $subject, $message);
|
||||
}
|
||||
|
||||
public static function get_subscribers(WP_REST_Request $request) {
|
||||
$subscribers = get_option('woonoow_newsletter_subscribers', []);
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'subscribers' => $subscribers,
|
||||
'count' => count($subscribers),
|
||||
],
|
||||
], 200);
|
||||
}
|
||||
}
|
||||
@@ -317,14 +317,14 @@ class ProductsController {
|
||||
}
|
||||
|
||||
// Virtual and downloadable
|
||||
if (!empty($data['virtual'])) {
|
||||
$product->set_virtual(true);
|
||||
if (isset($data['virtual'])) {
|
||||
$product->set_virtual((bool) $data['virtual']);
|
||||
}
|
||||
if (!empty($data['downloadable'])) {
|
||||
$product->set_downloadable(true);
|
||||
if (isset($data['downloadable'])) {
|
||||
$product->set_downloadable((bool) $data['downloadable']);
|
||||
}
|
||||
if (!empty($data['featured'])) {
|
||||
$product->set_featured(true);
|
||||
if (isset($data['featured'])) {
|
||||
$product->set_featured((bool) $data['featured']);
|
||||
}
|
||||
|
||||
// Categories
|
||||
@@ -418,6 +418,17 @@ class ProductsController {
|
||||
if (isset($data['width'])) $product->set_width(self::sanitize_number($data['width']));
|
||||
if (isset($data['height'])) $product->set_height(self::sanitize_number($data['height']));
|
||||
|
||||
// Virtual and downloadable
|
||||
if (isset($data['virtual'])) {
|
||||
$product->set_virtual((bool) $data['virtual']);
|
||||
}
|
||||
if (isset($data['downloadable'])) {
|
||||
$product->set_downloadable((bool) $data['downloadable']);
|
||||
}
|
||||
if (isset($data['featured'])) {
|
||||
$product->set_featured((bool) $data['featured']);
|
||||
}
|
||||
|
||||
// Categories
|
||||
if (isset($data['categories'])) {
|
||||
$product->set_category_ids($data['categories']);
|
||||
|
||||
@@ -20,17 +20,23 @@ use WooNooW\Api\ActivityLogController;
|
||||
use WooNooW\Api\ProductsController;
|
||||
use WooNooW\Api\CouponsController;
|
||||
use WooNooW\Api\CustomersController;
|
||||
use WooNooW\Api\NewsletterController;
|
||||
use WooNooW\Frontend\ShopController;
|
||||
use WooNooW\Frontend\CartController as FrontendCartController;
|
||||
use WooNooW\Frontend\AccountController;
|
||||
use WooNooW\Frontend\HookBridge;
|
||||
use WooNooW\Api\Controllers\SettingsController;
|
||||
use WooNooW\Api\Controllers\CartController as ApiCartController;
|
||||
use WooNooW\Admin\AppearanceController;
|
||||
|
||||
class Routes {
|
||||
public static function init() {
|
||||
// Initialize controllers (register action hooks)
|
||||
OrdersController::init();
|
||||
AppearanceController::init();
|
||||
|
||||
// Initialize CartController auth bypass (must be before rest_api_init)
|
||||
FrontendCartController::init();
|
||||
|
||||
// Log ALL REST API requests to debug routing
|
||||
add_filter('rest_pre_dispatch', function($result, $server, $request) {
|
||||
@@ -76,9 +82,9 @@ class Routes {
|
||||
$settings_controller = new SettingsController();
|
||||
$settings_controller->register_routes();
|
||||
|
||||
// Cart controller (API)
|
||||
$api_cart_controller = new ApiCartController();
|
||||
$api_cart_controller->register_routes();
|
||||
// Cart controller (API) - DISABLED: Using Frontend CartController instead to avoid route conflicts
|
||||
// $api_cart_controller = new ApiCartController();
|
||||
// $api_cart_controller->register_routes();
|
||||
|
||||
// Payments controller
|
||||
$payments_controller = new PaymentsController();
|
||||
@@ -131,6 +137,9 @@ class Routes {
|
||||
// Customers controller
|
||||
CustomersController::register_routes();
|
||||
|
||||
// Newsletter controller
|
||||
NewsletterController::register_routes();
|
||||
|
||||
// Frontend controllers (customer-facing)
|
||||
error_log('WooNooW Routes: Registering Frontend controllers');
|
||||
ShopController::register_routes();
|
||||
|
||||
@@ -13,7 +13,7 @@ if ( ! defined('ABSPATH') ) exit;
|
||||
*/
|
||||
class NavigationRegistry {
|
||||
const NAV_OPTION = 'wnw_nav_tree';
|
||||
const NAV_VERSION = '1.0.1'; // Bumped to add Customer SPA settings
|
||||
const NAV_VERSION = '1.0.7'; // Removed 'New Coupon' from submenu
|
||||
|
||||
/**
|
||||
* Initialize hooks
|
||||
@@ -145,16 +145,6 @@ class NavigationRegistry {
|
||||
['label' => __('Attributes', 'woonoow'), 'mode' => 'spa', 'path' => '/products/attributes'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'coupons',
|
||||
'label' => __('Coupons', 'woonoow'),
|
||||
'path' => '/coupons',
|
||||
'icon' => 'tag',
|
||||
'children' => [
|
||||
['label' => __('All coupons', 'woonoow'), 'mode' => 'spa', 'path' => '/coupons'],
|
||||
['label' => __('New', 'woonoow'), 'mode' => 'spa', 'path' => '/coupons/new'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'customers',
|
||||
'label' => __('Customers', 'woonoow'),
|
||||
@@ -165,6 +155,33 @@ class NavigationRegistry {
|
||||
['label' => __('New', 'woonoow'), 'mode' => 'spa', 'path' => '/customers/new'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'marketing',
|
||||
'label' => __('Marketing', 'woonoow'),
|
||||
'path' => '/marketing',
|
||||
'icon' => 'mail',
|
||||
'children' => [
|
||||
['label' => __('Newsletter', 'woonoow'), 'mode' => 'spa', 'path' => '/marketing/newsletter'],
|
||||
['label' => __('Coupons', 'woonoow'), 'mode' => 'spa', 'path' => '/coupons'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'appearance',
|
||||
'label' => __('Appearance', 'woonoow'),
|
||||
'path' => '/appearance',
|
||||
'icon' => 'palette',
|
||||
'children' => [
|
||||
['label' => __('General', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/general'],
|
||||
['label' => __('Header', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/header'],
|
||||
['label' => __('Footer', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/footer'],
|
||||
['label' => __('Shop', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/shop'],
|
||||
['label' => __('Product', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/product'],
|
||||
['label' => __('Cart', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/cart'],
|
||||
['label' => __('Checkout', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/checkout'],
|
||||
['label' => __('Thank You', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/thankyou'],
|
||||
['label' => __('My Account', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/account'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'settings',
|
||||
'label' => __('Settings', 'woonoow'),
|
||||
@@ -191,7 +208,6 @@ class NavigationRegistry {
|
||||
['label' => __('Tax', 'woonoow'), 'mode' => 'spa', 'path' => '/settings/tax'],
|
||||
['label' => __('Customers', 'woonoow'), 'mode' => 'spa', 'path' => '/settings/customers'],
|
||||
['label' => __('Notifications', 'woonoow'), 'mode' => 'spa', 'path' => '/settings/notifications'],
|
||||
['label' => __('Customer SPA', 'woonoow'), 'mode' => 'spa', 'path' => '/settings/customer-spa'],
|
||||
['label' => __('Developer', 'woonoow'), 'mode' => 'spa', 'path' => '/settings/developer'],
|
||||
];
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ use WooNooW\Branding;
|
||||
use WooNooW\Frontend\Assets as FrontendAssets;
|
||||
use WooNooW\Frontend\Shortcodes;
|
||||
use WooNooW\Frontend\TemplateOverride;
|
||||
use WooNooW\Frontend\PageAppearance;
|
||||
|
||||
class Bootstrap {
|
||||
public static function init() {
|
||||
@@ -44,6 +45,7 @@ class Bootstrap {
|
||||
FrontendAssets::init();
|
||||
Shortcodes::init();
|
||||
TemplateOverride::init();
|
||||
new PageAppearance();
|
||||
|
||||
// Activity Log
|
||||
ActivityLogTable::create_table();
|
||||
|
||||
@@ -43,6 +43,26 @@ class EventRegistry {
|
||||
'wc_email' => 'customer_new_account',
|
||||
'enabled' => true,
|
||||
],
|
||||
|
||||
// ===== NEWSLETTER EVENTS =====
|
||||
'newsletter_welcome' => [
|
||||
'id' => 'newsletter_welcome',
|
||||
'label' => __('Newsletter Welcome', 'woonoow'),
|
||||
'description' => __('Welcome email sent when someone subscribes to newsletter', 'woonoow'),
|
||||
'category' => 'marketing',
|
||||
'recipient_type' => 'customer',
|
||||
'wc_email' => '',
|
||||
'enabled' => true,
|
||||
],
|
||||
'newsletter_subscribed_admin' => [
|
||||
'id' => 'newsletter_subscribed_admin',
|
||||
'label' => __('New Newsletter Subscriber', 'woonoow'),
|
||||
'description' => __('Admin notification when someone subscribes to newsletter', 'woonoow'),
|
||||
'category' => 'marketing',
|
||||
'recipient_type' => 'staff',
|
||||
'wc_email' => '',
|
||||
'enabled' => true,
|
||||
],
|
||||
|
||||
// ===== ORDER INITIATION =====
|
||||
'order_placed' => [
|
||||
|
||||
@@ -137,8 +137,8 @@ class Assets {
|
||||
'mode' => 'disabled',
|
||||
'layout' => 'modern',
|
||||
'colors' => [
|
||||
'primary' => '#3B82F6',
|
||||
'secondary' => '#8B5CF6',
|
||||
'primary' => get_option('woonoow_primary_color', '#111827'), // Gray-900 from Store Details
|
||||
'secondary' => '#6B7280', // Gray-500
|
||||
'accent' => '#10B981',
|
||||
],
|
||||
'typography' => [
|
||||
@@ -147,6 +147,13 @@ class Assets {
|
||||
];
|
||||
$theme_settings = array_replace_recursive($default_settings, $spa_settings);
|
||||
|
||||
// Get appearance settings and preload them
|
||||
$appearance_settings = get_option('woonoow_appearance_settings', []);
|
||||
if (empty($appearance_settings)) {
|
||||
// Use defaults from AppearanceController
|
||||
$appearance_settings = \WooNooW\Admin\AppearanceController::get_default_settings();
|
||||
}
|
||||
|
||||
// Get WooCommerce currency settings
|
||||
$currency_settings = [
|
||||
'code' => get_woocommerce_currency(),
|
||||
@@ -157,17 +164,52 @@ class Assets {
|
||||
'decimals' => wc_get_price_decimals(),
|
||||
];
|
||||
|
||||
// Get store logo from WooNooW Store Details (Settings > Store Details)
|
||||
$logo_url = get_option('woonoow_store_logo', '');
|
||||
|
||||
// Get user billing/shipping data if logged in
|
||||
$user_data = [
|
||||
'isLoggedIn' => is_user_logged_in(),
|
||||
'id' => get_current_user_id(),
|
||||
];
|
||||
|
||||
if (is_user_logged_in()) {
|
||||
$customer = new \WC_Customer(get_current_user_id());
|
||||
$user_data['email'] = $customer->get_email();
|
||||
$user_data['billing'] = [
|
||||
'first_name' => $customer->get_billing_first_name(),
|
||||
'last_name' => $customer->get_billing_last_name(),
|
||||
'email' => $customer->get_billing_email(),
|
||||
'phone' => $customer->get_billing_phone(),
|
||||
'address_1' => $customer->get_billing_address_1(),
|
||||
'city' => $customer->get_billing_city(),
|
||||
'state' => $customer->get_billing_state(),
|
||||
'postcode' => $customer->get_billing_postcode(),
|
||||
'country' => $customer->get_billing_country(),
|
||||
];
|
||||
$user_data['shipping'] = [
|
||||
'first_name' => $customer->get_shipping_first_name(),
|
||||
'last_name' => $customer->get_shipping_last_name(),
|
||||
'address_1' => $customer->get_shipping_address_1(),
|
||||
'city' => $customer->get_shipping_city(),
|
||||
'state' => $customer->get_shipping_state(),
|
||||
'postcode' => $customer->get_shipping_postcode(),
|
||||
'country' => $customer->get_shipping_country(),
|
||||
];
|
||||
}
|
||||
|
||||
$config = [
|
||||
'apiUrl' => rest_url('woonoow/v1'),
|
||||
'apiRoot' => rest_url('woonoow/v1'),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
'siteUrl' => get_site_url(),
|
||||
'siteTitle' => get_bloginfo('name'),
|
||||
'user' => [
|
||||
'isLoggedIn' => is_user_logged_in(),
|
||||
'id' => get_current_user_id(),
|
||||
],
|
||||
'storeName' => get_bloginfo('name'),
|
||||
'storeLogo' => $logo_url,
|
||||
'user' => $user_data,
|
||||
'theme' => $theme_settings,
|
||||
'currency' => $currency_settings,
|
||||
'appearanceSettings' => $appearance_settings,
|
||||
];
|
||||
|
||||
?>
|
||||
|
||||
@@ -11,24 +11,51 @@ use WP_Error;
|
||||
*/
|
||||
class CartController {
|
||||
|
||||
/**
|
||||
* Initialize controller
|
||||
*/
|
||||
public static function init() {
|
||||
// Bypass cookie authentication for cart endpoints to allow guest users
|
||||
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) {
|
||||
error_log('WooNooW Cart: Bypassing authentication for cart endpoint');
|
||||
return true; // Allow access
|
||||
}
|
||||
|
||||
return $result;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register REST API routes
|
||||
*/
|
||||
public static function register_routes() {
|
||||
error_log('WooNooW CartController::register_routes() START');
|
||||
$namespace = 'woonoow/v1';
|
||||
|
||||
// Get cart
|
||||
register_rest_route($namespace, '/cart', [
|
||||
$result = register_rest_route($namespace, '/cart', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_cart'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
error_log('WooNooW CartController: GET /cart registered: ' . ($result ? 'SUCCESS' : 'FAILED'));
|
||||
|
||||
// Add to cart
|
||||
register_rest_route($namespace, '/cart/add', [
|
||||
$result = register_rest_route($namespace, '/cart/add', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'add_to_cart'],
|
||||
'permission_callback' => '__return_true',
|
||||
'permission_callback' => function() {
|
||||
// Allow both logged-in and guest users
|
||||
return true;
|
||||
},
|
||||
'args' => [
|
||||
'product_id' => [
|
||||
'required' => true,
|
||||
@@ -46,12 +73,13 @@ class CartController {
|
||||
],
|
||||
],
|
||||
]);
|
||||
error_log('WooNooW CartController: POST /cart/add registered: ' . ($result ? 'SUCCESS' : 'FAILED'));
|
||||
|
||||
// Update cart item
|
||||
register_rest_route($namespace, '/cart/update', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'update_cart'],
|
||||
'permission_callback' => '__return_true',
|
||||
'permission_callback' => function() { return true; },
|
||||
'args' => [
|
||||
'cart_item_key' => [
|
||||
'required' => true,
|
||||
@@ -68,7 +96,7 @@ class CartController {
|
||||
register_rest_route($namespace, '/cart/remove', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'remove_from_cart'],
|
||||
'permission_callback' => '__return_true',
|
||||
'permission_callback' => function() { return true; },
|
||||
'args' => [
|
||||
'cart_item_key' => [
|
||||
'required' => true,
|
||||
@@ -81,7 +109,7 @@ class CartController {
|
||||
register_rest_route($namespace, '/cart/apply-coupon', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'apply_coupon'],
|
||||
'permission_callback' => '__return_true',
|
||||
'permission_callback' => function() { return true; },
|
||||
'args' => [
|
||||
'coupon_code' => [
|
||||
'required' => true,
|
||||
@@ -94,7 +122,7 @@ class CartController {
|
||||
register_rest_route($namespace, '/cart/remove-coupon', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'remove_coupon'],
|
||||
'permission_callback' => '__return_true',
|
||||
'permission_callback' => function() { return true; },
|
||||
'args' => [
|
||||
'coupon_code' => [
|
||||
'required' => true,
|
||||
@@ -123,28 +151,119 @@ class CartController {
|
||||
$quantity = $request->get_param('quantity');
|
||||
$variation_id = $request->get_param('variation_id');
|
||||
|
||||
// Initialize WooCommerce session for guest users
|
||||
if (!WC()->session->has_session()) {
|
||||
WC()->session->set_customer_session_cookie(true);
|
||||
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) {
|
||||
return new WP_Error('cart_error', 'Cart not initialized', ['status' => 500]);
|
||||
error_log('WooNooW Cart: Initializing WC cart for REST API');
|
||||
WC()->initialize_cart();
|
||||
}
|
||||
|
||||
// Set session cookie for guest users
|
||||
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]);
|
||||
}
|
||||
|
||||
// Add to cart
|
||||
$cart_item_key = WC()->cart->add_to_cart($product_id, $quantity, $variation_id);
|
||||
error_log("WooNooW Cart: Product validated - {$product->get_name()} (Type: {$product->get_type()})");
|
||||
|
||||
// For variable products, validate the variation and get attributes
|
||||
$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]);
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return new WP_Error('add_to_cart_failed', 'Failed to add product to cart', ['status' => 400]);
|
||||
// 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));
|
||||
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',
|
||||
'cart_item_key' => $cart_item_key,
|
||||
|
||||
91
includes/Frontend/PageAppearance.php
Normal file
91
includes/Frontend/PageAppearance.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
namespace WooNooW\Frontend;
|
||||
|
||||
class PageAppearance {
|
||||
|
||||
private $settings;
|
||||
|
||||
public function __construct() {
|
||||
add_action('wp_head', [$this, 'add_appearance_styles'], 999);
|
||||
}
|
||||
|
||||
private function get_appearance_settings() {
|
||||
if ($this->settings === null) {
|
||||
$this->settings = get_option('woonoow_appearance_settings', []);
|
||||
}
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
public function add_appearance_styles() {
|
||||
// Only inject styles on SPA pages (shop archive)
|
||||
if (!is_post_type_archive('product') && !is_shop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
?>
|
||||
<style id="woonoow-page-appearance">
|
||||
/* Header visibility styles */
|
||||
body.woonoow-header-hide header,
|
||||
body.woonoow-header-hide .site-header,
|
||||
body.woonoow-header-hide #masthead {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.woonoow-header-minimal header,
|
||||
body.woonoow-header-minimal .site-header,
|
||||
body.woonoow-header-minimal #masthead {
|
||||
padding: 1rem 0 !important;
|
||||
box-shadow: none !important;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
body.woonoow-header-minimal header .site-navigation,
|
||||
body.woonoow-header-minimal .site-header .site-navigation,
|
||||
body.woonoow-header-minimal #masthead .site-navigation,
|
||||
body.woonoow-header-minimal header nav,
|
||||
body.woonoow-header-minimal .site-header nav,
|
||||
body.woonoow-header-minimal #masthead nav {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.woonoow-header-minimal header .header-widgets,
|
||||
body.woonoow-header-minimal .site-header .header-widgets,
|
||||
body.woonoow-header-minimal #masthead .header-widgets {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Footer visibility styles */
|
||||
body.woonoow-footer-hide footer,
|
||||
body.woonoow-footer-hide .site-footer,
|
||||
body.woonoow-footer-hide #colophon {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.woonoow-footer-minimal footer,
|
||||
body.woonoow-footer-minimal .site-footer,
|
||||
body.woonoow-footer-minimal #colophon {
|
||||
padding: 2rem 0 !important;
|
||||
background: #f9fafb !important;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
body.woonoow-footer-minimal footer .footer-widgets,
|
||||
body.woonoow-footer-minimal .site-footer .footer-widgets,
|
||||
body.woonoow-footer-minimal #colophon .footer-widgets,
|
||||
body.woonoow-footer-minimal footer .widget-area,
|
||||
body.woonoow-footer-minimal .site-footer .widget-area,
|
||||
body.woonoow-footer-minimal #colophon .widget-area {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.woonoow-footer-minimal footer .site-info,
|
||||
body.woonoow-footer-minimal .site-footer .site-info,
|
||||
body.woonoow-footer-minimal #colophon .site-info {
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,8 @@ class ShopController {
|
||||
$orderby = $request->get_param('orderby');
|
||||
$order = $request->get_param('order');
|
||||
$slug = $request->get_param('slug');
|
||||
$include = $request->get_param('include');
|
||||
$exclude = $request->get_param('exclude');
|
||||
|
||||
$args = [
|
||||
'post_type' => 'product',
|
||||
@@ -115,12 +117,27 @@ class ShopController {
|
||||
$args['name'] = $slug;
|
||||
}
|
||||
|
||||
// Add include filter (specific product IDs)
|
||||
if (!empty($include)) {
|
||||
$ids = array_map('intval', explode(',', $include));
|
||||
$args['post__in'] = $ids;
|
||||
$args['orderby'] = 'post__in'; // Maintain order of IDs
|
||||
}
|
||||
|
||||
// Add exclude filter (exclude specific product IDs)
|
||||
if (!empty($exclude)) {
|
||||
$ids = array_map('intval', explode(',', $exclude));
|
||||
$args['post__not_in'] = $ids;
|
||||
}
|
||||
|
||||
// Add category filter
|
||||
if (!empty($category)) {
|
||||
// Check if category is numeric (ID) or string (slug)
|
||||
$field = is_numeric($category) ? 'term_id' : 'slug';
|
||||
$args['tax_query'] = [
|
||||
[
|
||||
'taxonomy' => 'product_cat',
|
||||
'field' => 'slug',
|
||||
'field' => $field,
|
||||
'terms' => $category,
|
||||
],
|
||||
];
|
||||
@@ -251,6 +268,9 @@ class ShopController {
|
||||
'type' => $product->get_type(),
|
||||
'image' => wp_get_attachment_url($product->get_image_id()),
|
||||
'permalink' => get_permalink($product->get_id()),
|
||||
'categories' => wp_get_post_terms($product->get_id(), 'product_cat', ['fields' => 'all']),
|
||||
'virtual' => $product->is_virtual(),
|
||||
'downloadable' => $product->is_downloadable(),
|
||||
];
|
||||
|
||||
// Add detailed info if requested
|
||||
@@ -258,7 +278,6 @@ class ShopController {
|
||||
$data['description'] = $product->get_description();
|
||||
$data['short_description'] = $product->get_short_description();
|
||||
$data['sku'] = $product->get_sku();
|
||||
$data['categories'] = wp_get_post_terms($product->get_id(), 'product_cat', ['fields' => 'all']);
|
||||
$data['tags'] = wp_get_post_terms($product->get_id(), 'product_tag', ['fields' => 'names']);
|
||||
|
||||
// Gallery images
|
||||
@@ -329,9 +348,25 @@ class ShopController {
|
||||
$variation_obj = wc_get_product($variation['variation_id']);
|
||||
|
||||
if ($variation_obj) {
|
||||
// Get attributes directly from post meta (most reliable)
|
||||
$attributes = [];
|
||||
$variation_id = $variation['variation_id'];
|
||||
|
||||
// Query all post meta for this variation
|
||||
global $wpdb;
|
||||
$meta_rows = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT meta_key, meta_value FROM {$wpdb->postmeta}
|
||||
WHERE post_id = %d AND meta_key LIKE 'attribute_%%'",
|
||||
$variation_id
|
||||
));
|
||||
|
||||
foreach ($meta_rows as $row) {
|
||||
$attributes[$row->meta_key] = $row->meta_value;
|
||||
}
|
||||
|
||||
$variations[] = [
|
||||
'id' => $variation['variation_id'],
|
||||
'attributes' => $variation['attributes'],
|
||||
'id' => $variation_id,
|
||||
'attributes' => $attributes,
|
||||
'price' => $variation_obj->get_price(),
|
||||
'regular_price' => $variation_obj->get_regular_price(),
|
||||
'sale_price' => $variation_obj->get_sale_price(),
|
||||
|
||||
Reference in New Issue
Block a user