User feedback: 'SPA means Single Page, why 4 pages?' Correct architecture: - 1 SPA entry page (e.g., /store) - SPA Mode determines initial route: * Full SPA → starts at shop page * Checkout Only → starts at cart page * Disabled → never loads - React Router handles rest via /#/ routing Changes: - Admin UI: Changed from 4 page selectors to 1 SPA entry page - Backend: spa_pages array → spa_page integer - Template: Initial route based on spa_mode setting - Simplified is_spa_page() checks (single ID comparison) Benefits: - User can set /store as homepage (Settings → Reading) - Landing page → CTA → direct to cart/checkout - Clean single entry point - Mode controls behavior, not multiple pages Example flow: - Visit https://site.com/store - Full SPA: loads shop, navigate via /#/product/123 - Checkout Only: loads cart, navigate via /#/checkout - Homepage: set /store as homepage, SPA loads on site root Next: Add direct-to-cart CTA with product parameter
490 lines
21 KiB
PHP
490 lines
21 KiB
PHP
<?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'],
|
|
],
|
|
],
|
|
]);
|
|
|
|
// Get all WordPress pages for page selector
|
|
register_rest_route(self::API_NAMESPACE, '/pages/list', [
|
|
'methods' => 'GET',
|
|
'callback' => [__CLASS__, 'get_pages_list'],
|
|
'permission_callback' => [__CLASS__, 'check_permission'],
|
|
]);
|
|
}
|
|
|
|
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')),
|
|
'spa_page' => absint($request->get_param('spaPage') ?? 0),
|
|
'toast_position' => sanitize_text_field($request->get_param('toastPosition') ?? 'top-right'),
|
|
'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 list of WordPress pages for page selector
|
|
*/
|
|
public static function get_pages_list(WP_REST_Request $request) {
|
|
$pages = get_pages([
|
|
'post_status' => 'publish',
|
|
'sort_column' => 'post_title',
|
|
'sort_order' => 'ASC',
|
|
]);
|
|
|
|
$pages_list = array_map(function($page) {
|
|
return [
|
|
'id' => $page->ID,
|
|
'title' => $page->post_title,
|
|
'slug' => $page->post_name,
|
|
];
|
|
}, $pages);
|
|
|
|
return new WP_REST_Response([
|
|
'success' => true,
|
|
'data' => $pages_list,
|
|
], 200);
|
|
}
|
|
|
|
/**
|
|
* Get default settings structure
|
|
*/
|
|
public static function get_default_settings() {
|
|
return [
|
|
'general' => [
|
|
'spa_mode' => 'full',
|
|
'spa_page' => 0,
|
|
'toast_position' => 'top-right',
|
|
'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' => [],
|
|
],
|
|
];
|
|
}
|
|
}
|