When WC packages array is empty, manually calculate rates by:
1. Get zone matching the shipping address
2. Call get_rates_for_package() on each method
3. Or fallback to method title/cost for simple methods like Free Shipping
Also added debug info to response to help diagnose issues.
WooCommerce stores variation custom attribute meta keys in lowercase
(e.g., attribute_size), but we were using the original case from
parent attributes (e.g., attribute_Size). This caused empty attribute
values in the admin Order Form variation selector.
Fix: Use sanitize_title() to normalize the attribute name.
Root cause: LicensingModule::init() was called from within
plugins_loaded but then tried to add ANOTHER plugins_loaded action
for LicenseManager::init(). Since plugins_loaded already fired,
LicenseManager::init() never ran and WooCommerce order hooks
were never registered.
Fix: Call self::maybe_init_manager() directly instead of
scheduling via add_action.
- Added woocommerce_payment_complete hook
- Added woocommerce_thankyou hook for COD/virtual orders
- Added is_virtual_order helper to detect virtual-only orders
- generate_licenses_for_order now called from multiple hooks
(safe due to license_exists_for_order_item check)
1. Category Selection Bug:
- Added 'id' alias to category/tag API responses
- Frontend uses cat.id which was undefined (API returned term_id)
2. Checkout Redirect:
- Changed from window.location.href + reload to navigate()
- Added !isProcessing check to empty cart condition
3. Sidebar Shipping for Virtual-Only:
- Hide Shipping Method section when isVirtualOnly
- Hide Shipping row in totals when isVirtualOnly
4. License Table:
- Table creation runs via ensure_tables() on plugins_loaded
- Added license_duration_days field to ProductVariant type
- Added License Duration input to each variation card
- Backend: ProductsController saves/loads variation-level _license_duration_days meta
- Allows different license periods per variation (e.g., 1-year, 2-year, lifetime)
Copy Cart/Checkout Links:
- Added to GeneralTab for simple products (same pattern as variations)
- Link generation with add-to-cart and redirect params
Licensing Settings:
- 'Enable licensing for this product' checkbox in Additional Options
- License settings panel: activation limit, duration (days)
- State management in ProductFormTabbed
- Backend: ProductsController saves/loads licensing meta fields
Backend:
- _licensing_enabled, _license_activation_limit, _license_duration_days post meta
- LicensingSettings.php with key format, activation limits, expiry settings
- LicenseManager.php with key generation, activation/deactivation, validation
- LicensingModule.php with WooCommerce product meta integration
- LicensesController.php with admin, customer, and public API endpoints
- Database tables: woonoow_licenses, woonoow_license_activations
- has_settings enabled in ModuleRegistry
1. Admin Store Link - Add to WP admin bar (Menu.php) with proper option check
2. Activity Log - Fix Loading text to show correct state after data loads
3. Avatar Upload - Use correct option key woonoow_allow_custom_avatar
4. Downloadable Files - Connect to WooCommerce native:
- Add downloads array to format_product_full
- Add downloads/download_limit/download_expiry handling in update_product
- Add downloads handling in create_product
Customer Avatar Upload:
- Add /account/avatar endpoint for upload/delete
- Add /account/avatar-settings endpoint for settings
- Update AccountDetails.tsx with avatar upload UI
- Support base64 image upload with validation
Product Downloadable Files:
- Create DownloadsTab component for file management
- Add downloads state to ProductFormTabbed
- Show Downloads tab when 'downloadable' is checked
- Support file name, URL, download limit, and expiry
- Add Store link to admin header (visible when customer SPA is enabled)
- Add storeUrl and customerSpaEnabled to WNW_CONFIG in Assets.php and StandaloneAdmin.php
- Update window.d.ts with new WNW_CONFIG properties
- Create ActivityLog.tsx component with search, filters, and pagination
- Add /notifications/logs API endpoint to NotificationsController
- Update Notifications.tsx to link to activity log page
- Add ActivityLog route to App.tsx
1. Add allow_custom_avatar toggle to Customer Settings
2. Implement coupon apply/remove in Cart and Checkout pages
3. Update Cart interface with coupons array and discount_total
4. Implement Downloads page to fetch from /account/downloads API
- Added Help item to NavigationRegistry::get_base_tree
- Empty children array means no submenu bar displayed
- Incremented NAV_VERSION to 1.0.9 to trigger cache rebuild
- Help icon: help-circle
Phase 1: Core Documentation
- Created docs/ folder with 8 markdown documentation files
- Getting Started, Installation, Troubleshooting, FAQ
- Configuration docs (Appearance, SPA Mode)
- Feature docs (Shop, Checkout)
- PHP registry with filter hook for addon extensibility
Phase 2: Documentation Viewer
- DocsController.php with REST API endpoints
- GET /woonoow/v1/docs - List all docs (with addon hook)
- GET /woonoow/v1/docs/{slug} - Get document content
- Admin SPA /help route with sidebar navigation
- Markdown rendering with react-markdown
- Added Help & Docs to More page for mobile access
Filter Hook: woonoow_docs_registry
Addons can register their own documentation sections.
- Removed shortcode replacement for Cart, Checkout, My Account pages
- WooCommerce pages now keep their original [woocommerce_*] shortcodes
- Plugin only creates dedicated SPA page (/store) with [woonoow_spa]
- Auto-sets spa_page in appearance settings
This aligns with template override approach - WC pages render normally
when SPA is disabled, and redirect to SPA when mode is 'full'.
- Fixed redirect_wc_pages_to_spa: added spa_mode check (only redirect when 'full')
- Fixed PHP fatal error: use get_queried_object() instead of global $product
- Removed all error_log debug statements from codebase
- Fixed broken syntax in PaymentGatewaysProvider.php after error_log removal
Phase 4: Dynamic Meta Tags
- Added react-helmet-async dependency
- Created SEOHead component with Open Graph and Twitter Card support
- Added HelmetProvider wrapper to App.tsx
- Integrated SEOHead in Product page (title, description, image, product info)
- Integrated SEOHead in Shop page (basic meta tags)
Phase 5: Auto-Flush Permalinks
- Enhanced settings change handler to only flush when spa_mode,
spa_page, or use_browser_router changes
- Plugin already flushes on activation (Installer.php)
This enables proper link previews when sharing product URLs
on Facebook, Twitter, Slack, etc.
Changed from /my-account to /store page URL:
- Now reads spa_page from woonoow_appearance_settings
- Uses get_permalink() on the configured SPA page ID
- Fallback to home_url if SPA not configured
- Reset URL format: /store/#/reset-password?key=...&login=...
Changed reset link URL from admin SPA to customer-spa:
- Old: /wp-admin/admin.php?page=woonoow#/reset-password?key=...
- New: /my-account#/reset-password?key=...
This fixes the login redirect issue - the customer-spa is publicly
accessible so users can reset their password without logging in first.
Added:
- customer-spa/src/pages/ResetPassword/index.tsx
- Route /reset-password in customer-spa App.tsx
EmailManager.php now:
- Uses wc_get_page_id('myaccount') to get my-account page URL
- Falls back to home_url if my-account page not found
- Created ResetPassword.tsx with:
- Password reset form with strength indicator
- Key validation on load
- Show/hide password toggle
- Success/error states
- Redirect to login on success
- Updated EmailManager.php:
- Changed reset_link from wp-login.php to SPA route
- Format: /wp-admin/admin.php?page=woonoow#/reset-password?key=KEY&login=LOGIN
- Added AuthController API methods:
- validate_reset_key: Validates reset key before showing form
- reset_password: Performs actual password reset
- Registered new REST routes in Routes.php:
- POST /auth/validate-reset-key
- POST /auth/reset-password
Password reset emails now link to the SPA instead of native WordPress.
OLD BEHAVIOR (broken):
parse_cards processed ALL [card:type] syntax FIRST, then [card type=...]
This caused cards to render out of order when syntaxes were mixed.
NEW BEHAVIOR (fixed):
Using a unified regex that matches BOTH syntaxes simultaneously:
/\[card(?::(\w+)|([^\]]*)?)\](.*?)\[\/card\]/s
Each match includes:
- Group 1: Card type from new syntax [card:type]
- Group 2: Attributes from old syntax [card type='...']
- Group 3: Card content
Cards now render in exact document order regardless of syntax used.
ROOT CAUSE:
Frontend blocksToMarkdown outputs NEW syntax:
- [card:type]...[/card]
- [button:style](url)Text[/button]
But backend EmailRenderer.php only had regex for OLD syntax:
- [card type="..."]...[/card]
- [button url="..."]Text[/button]
FIXES:
1. parse_cards() now handles BOTH syntaxes:
- NEW [card:type] regex first (extracts type from :type)
- OLD [card type="..."] regex for backward compatibility
2. render_card() now handles BOTH button syntaxes:
- NEW [button:style](url)Text[/button] regex
- OLD [button url="..."] regex for backward compatibility
3. Card types properly styled with inline CSS:
- hero: gradient background
- success: green background + border
- info: blue background + border
- warning: yellow background + orange border
4. Buttons rendered with full inline styles + table wrapper
for Gmail/email client compatibility
1. API Route Fix (NotificationsController.php):
- Changed PUT to POST for /templates/:eventId/:channelId
- Frontend was using api.post() but backend only accepted PUT
- Templates can now be saved
2. Contextual Variables (EventRegistry.php):
- Added get_variables_for_event() method
- Returns category-based variables (order, customer, product, etc.)
- Merges event-specific variables from event definition
- Sorted alphabetically for easy browsing
3. API Response (NotificationsController.php):
- Template API now returns available_variables for the event
- Frontend can show only relevant variables
4. Frontend (EditTemplate.tsx):
- Removed hardcoded 50+ variable list
- Now uses template.available_variables from API
- Variables update based on selected event type
1. Auto-login after checkout:
- Added wp_set_auth_cookie() and wp_set_current_user() in CheckoutController
- Auto-registered users are now logged in when thank-you page loads
2. ThankYou page guest buttons:
- Added 'Login / Create Account' button for guests
- Shows for both receipt and basic templates
- No more dead-end after placing order as guest
3. Forgot password flow:
- Created ForgotPassword page component (/forgot-password route)
- Added forgot_password API endpoint in AuthController
- Uses WordPress retrieve_password() for reset email
- Replaced wp-login.php link in Login page
1. Temp password for auto-registered users:
- Store password in _woonoow_temp_password user meta (CheckoutController)
- Add {user_temp_password} and {login_url} variables (EmailRenderer)
- Update new_customer email template to show credentials
2. WC page redirects to SPA routes:
- Added redirect_wc_pages_to_spa() in TemplateOverride
- Maps: /shop → /store/#/, /cart → /store/#/cart, etc.
- /checkout → /store/#/checkout, /my-account → /store/#/account
- Single products → /store/#/products/{slug}
3. Removed shortcode system:
- Commented out Shortcodes::init() in Bootstrap
- WC pages now redirect to SPA instead
1. Added missing base variables in get_variables():
- site_name, site_title, store_name
- shop_url, my_account_url
- support_email, current_year
2. Fixed social icon URL path calculation:
- Was using 3x dirname which pointed to 'includes/' not plugin root
- Now uses WOONOOW_URL constant or correct 4x dirname
3. Added px-6 padding to EmailBuilder dialog body
4. Added portal container to Select component for CSS scoping
1. EmailBuilder: Fixed dialog handlers to not block all interactions
- Previously dialog prevented all outside clicks
- Now only blocks when WP media modal is open
- Dialog can be properly closed via escape or outside click
2. DefaultTemplates: Updated new_customer email
- Added note about using 'Forgot Password?' if link expires
- Clear instructions for users
1. EmailRenderer: Added button parsing with full inline styles
- Buttons now use table-based layout for email client compatibility
- Solid and outline button styles with custom colors from settings
2. DefaultTemplates: Updated new_customer template
- Added 'Set Your Password' button for auto-registered users
- Uses {set_password_url} variable for password reset link
3. EmailRenderer: Added set_password_url variable
- Generates secure password reset link for new customers
- Also added my_account_url and shop_url to customer variables
- Created Login/index.tsx with styled form
- Added /auth/customer-login API endpoint (no admin perms required)
- Registered route in Routes.php
- Added /login route in customer-spa App.tsx
- Account page now redirects to SPA login instead of wp-login.php
- Login supports redirect param for post-login navigation
1. Remove wishlist setting from customer settings (now in module toggle)
- Removed from CustomerSettingsProvider.php
- Removed from Customers.tsx
2. Remove auto-login from REST API (causes cookie issues)
- Auto-login in REST context doesn't properly set browser cookies
- Removed wp_set_current_user/wp_set_auth_cookie calls
3. Fix cart not clearing after order
- Added WC()->cart->empty_cart() after successful order
- Server-side cart was not being cleared, causing re-population
- Frontend clears local store but Cart page syncs with server
- After creating new user account, immediately log them in
- Uses wp_set_current_user() and wp_set_auth_cookie()
- Provides smoother UX - customer is logged in after placing order
- Shop page and other customer pages need to read module settings
- Settings are non-sensitive configuration values (e.g. wishlist display)
- POST endpoint remains admin-only for security
- Fixes 401 errors on shop page for /modules/wishlist/settings
- When 'Auto-register customers as site members' is enabled
- Creates WP user account with 'customer' role for guest checkouts
- Links order to existing user if email already registered
- Sets WooCommerce customer billing data on new account
- Triggers woocommerce_created_customer action for email notification
- Add public /checkout/order/{id} endpoint with order_key validation
- Update checkout redirect to include order_key parameter
- Update ThankYou page to use new public endpoint with key
- Support both guest (via key) and logged-in (via customer_id) access
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
Complete WooCommerce-style page architecture implementation:
Backend (already committed):
- API endpoint to fetch WordPress pages
- spa_pages field in appearance settings
- is_spa_page() checks in TemplateOverride and Assets
Frontend (this commit):
- Added page selector UI in Appearance > General
- Dropdowns for Shop, Cart, Checkout, Account pages
- Loads available WordPress pages from API
- Saves selected page IDs to settings
- Info alert explaining full-body rendering
UI Features:
- Clean page selection interface
- Shows all published WordPress pages
- '— None —' option to disable
- Integrated into existing General settings tab
- Follows existing design patterns
How it works:
1. Admin selects pages in Appearance > General
2. Page IDs saved to woonoow_appearance_settings
3. Frontend checks if current page matches selected pages
4. If match, renders full SPA to body (no theme interference)
5. Works with ANY theme consistently
Next: Test page selection and verify clean SPA rendering