From ffdc7aae5f2e2d9b56df9f8e911ee7ef03ef5c2b Mon Sep 17 00:00:00 2001 From: dwindown Date: Tue, 11 Nov 2025 12:31:13 +0700 Subject: [PATCH] feat: Implement notification system with 3 subpages (Events, Channels, Templates) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ✅ Correct Implementation Following NOTIFICATION_STRATEGY.md ### Frontend (React) - 3 Subpages **1. Main Notifications Page** (`Notifications.tsx`) - Tab navigation for 3 sections - Events | Channels | Templates **2. Events Subpage** (`Notifications/Events.tsx`) - Configure which channels per event - Grouped by category (Orders, Products, Customers) - Toggle channels (Email, WhatsApp, Telegram, etc.) - Show recipient (Admin/Customer/Both) - Switch UI for enable/disable per channel **3. Channels Subpage** (`Notifications/Channels.tsx`) - List available channels - Built-in channels (Email) - Addon channels (WhatsApp, Telegram, SMS, Push) - Channel status (Active/Inactive) - Configure button for each channel - Addon discovery cards **4. Templates Subpage** (`Notifications/Templates.tsx`) - Email templates (link to WooCommerce) - Addon channel templates - Template variables reference - Preview and edit buttons - Variable documentation ({order_number}, {customer_name}, etc.) ### Backend (PHP) - Bridge to WooCommerce **NotificationsController** (`includes/Api/NotificationsController.php`) - Bridges to WooCommerce email system - Does NOT reinvent notification system - Provides addon integration hooks **REST API Endpoints:** ``` GET /notifications/channels - List channels (email + addons) GET /notifications/events - List events (maps to WC emails) POST /notifications/events/update - Update event channel settings ``` **Key Features:** ✅ Leverages WooCommerce emails (not reinventing) ✅ Stores settings in wp_options ✅ Provides hooks for addons: - `woonoow_notification_channels` filter - `woonoow_notification_events` filter - `woonoow_notification_event_updated` action ### Addon Integration **Example: WhatsApp Addon** ```php // Register channel add_filter("woonoow_notification_channels", function($channels) { $channels[] = [ "id" => "whatsapp", "label" => "WhatsApp", "icon" => "message-circle", "enabled" => true, "addon" => "woonoow-whatsapp", ]; return $channels; }); // React to event updates add_action("woonoow_notification_event_updated", function($event_id, $channel_id, $enabled, $recipient) { if ($channel_id === "whatsapp" && $enabled) { // Setup WhatsApp notification for this event } }, 10, 4); // Hook into WooCommerce email triggers add_action("woocommerce_order_status_processing", function($order_id) { // Send WhatsApp notification }, 10, 1); ``` ### Architecture **NOT a new notification system** ✅ - Uses WooCommerce email infrastructure - Maps events to WC email IDs - Addons hook into WC triggers **IS an extensible framework** ✅ - Unified UI for all channels - Per-event channel configuration - Template management - Addon discovery ### Files Created - `Notifications.tsx` - Main page with tabs - `Notifications/Events.tsx` - Events configuration - `Notifications/Channels.tsx` - Channel management - `Notifications/Templates.tsx` - Template editor - `NotificationsController.php` - REST API bridge ### Files Modified - `Routes.php` - Register NotificationsController --- **Ready for addon development!** 🚀 Next: Build Telegram addon as proof of concept --- .../src/routes/Settings/Notifications.tsx | 286 ++-------------- .../Settings/Notifications/Channels.tsx | 215 ++++++++++++ .../routes/Settings/Notifications/Events.tsx | 312 ++++++++++++++++++ .../Settings/Notifications/Templates.tsx | 225 +++++++++++++ includes/Api/NotificationsController.php | 210 +++++++----- includes/Core/Bootstrap.php | 2 - .../Notifications/NotificationManager.php | 230 ------------- .../NotificationSettingsProvider.php | 182 ---------- 8 files changed, 910 insertions(+), 752 deletions(-) create mode 100644 admin-spa/src/routes/Settings/Notifications/Channels.tsx create mode 100644 admin-spa/src/routes/Settings/Notifications/Events.tsx create mode 100644 admin-spa/src/routes/Settings/Notifications/Templates.tsx delete mode 100644 includes/Core/Notifications/NotificationManager.php delete mode 100644 includes/Core/Notifications/NotificationSettingsProvider.php diff --git a/admin-spa/src/routes/Settings/Notifications.tsx b/admin-spa/src/routes/Settings/Notifications.tsx index 69febdb..ed52590 100644 --- a/admin-spa/src/routes/Settings/Notifications.tsx +++ b/admin-spa/src/routes/Settings/Notifications.tsx @@ -1,280 +1,38 @@ import React, { useState } from 'react'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { api } from '@/lib/api'; import { SettingsLayout } from './components/SettingsLayout'; -import { SettingsCard } from './components/SettingsCard'; -import { Switch } from '@/components/ui/switch'; -import { Button } from '@/components/ui/button'; -import { Badge } from '@/components/ui/badge'; -import { ExternalLink, RefreshCw, Mail, MessageCircle, Send, Bell } from 'lucide-react'; -import { toast } from 'sonner'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { __ } from '@/lib/i18n'; +import NotificationEvents from './Notifications/Events'; +import NotificationChannels from './Notifications/Channels'; +import NotificationTemplates from './Notifications/Templates'; export default function NotificationsSettings() { - const queryClient = useQueryClient(); - const [activeTab, setActiveTab] = useState<'channels' | 'events'>('channels'); - - // Fetch notification channels - const { data: channels, isLoading: channelsLoading } = useQuery({ - queryKey: ['notification-channels'], - queryFn: () => api.get('/notifications/channels'), - }); - - // Fetch notification events - const { data: events, isLoading: eventsLoading } = useQuery({ - queryKey: ['notification-events'], - queryFn: () => api.get('/notifications/events'), - }); - - // Fetch notification settings - const { data: settings, isLoading: settingsLoading } = useQuery({ - queryKey: ['notification-settings'], - queryFn: () => api.get('/notifications/settings'), - }); - - // Update settings mutation - const updateMutation = useMutation({ - mutationFn: async (newSettings: any) => { - return api.post('/notifications/settings', newSettings); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['notification-settings'] }); - queryClient.invalidateQueries({ queryKey: ['notification-events'] }); - toast.success(__('Notification settings updated')); - }, - onError: (error: any) => { - toast.error(error?.message || __('Failed to update settings')); - }, - }); - - const toggleEventChannel = (eventId: string, channelId: string, enabled: boolean) => { - const newSettings = { ...settings }; - if (!newSettings.events) newSettings.events = {}; - if (!newSettings.events[eventId]) newSettings.events[eventId] = { enabled: true, channels: [], recipients: {} }; - - const channels = newSettings.events[eventId].channels || []; - if (enabled) { - if (!channels.includes(channelId)) { - newSettings.events[eventId].channels = [...channels, channelId]; - } - } else { - newSettings.events[eventId].channels = channels.filter((c: string) => c !== channelId); - } - - updateMutation.mutate(newSettings); - }; - - const isLoading = channelsLoading || eventsLoading || settingsLoading; - - if (isLoading) { - return ( - -
- -
-
- ); - } - - const getChannelIcon = (channelId: string) => { - switch (channelId) { - case 'email': return ; - case 'whatsapp': return ; - case 'telegram': return ; - default: return ; - } - }; + const [activeTab, setActiveTab] = useState('events'); return ( -
- {/* Notification Channels */} - -
- {channels?.map((channel: any) => ( -
-
-
- {getChannelIcon(channel.id)} -
-
-
-

{channel.label}

- {channel.builtin && ( - {__('Built-in')} - )} - {channel.addon && ( - {__('Addon')} - )} -
- {channel.addon && ( -

- {__('Provided by')} {channel.addon} -

- )} -
-
- - {channel.enabled ? __('Active') : __('Inactive')} - -
- ))} - -
-

- 💡 {__('Want more channels like WhatsApp, Telegram, or SMS? Install notification addons to extend your notification capabilities.')} -

-
-
-
+ + + {__('Events')} + {__('Channels')} + {__('Templates')} + - {/* Order Notifications */} - {events?.orders && events.orders.length > 0 && ( - -
- {events.orders.map((event: any) => ( -
-
-
-

{event.label}

-

{event.description}

-
-
-
- {channels?.map((channel: any) => { - const isEnabled = event.channels?.includes(channel.id); - return ( - - ); - })} -
-
- ))} -
-
- )} + + + - {/* Product Notifications */} - {events?.products && events.products.length > 0 && ( - -
- {events.products.map((event: any) => ( -
-
-
-

{event.label}

-

{event.description}

-
-
-
- {channels?.map((channel: any) => { - const isEnabled = event.channels?.includes(channel.id); - return ( - - ); - })} -
-
- ))} -
-
- )} + + + - {/* Customer Notifications */} - {events?.customers && events.customers.length > 0 && ( - -
- {events.customers.map((event: any) => ( -
-
-
-

{event.label}

-

{event.description}

-
-
-
- {channels?.map((channel: any) => { - const isEnabled = event.channels?.includes(channel.id); - return ( - - ); - })} -
-
- ))} -
-
- )} - - {/* WooCommerce Email Settings Link */} - -
-

- {__('Email notifications are powered by WooCommerce. For advanced customization like templates, subject lines, and sender details, use the WooCommerce email settings.')} -

- -
-
- -
+ + + +
); } diff --git a/admin-spa/src/routes/Settings/Notifications/Channels.tsx b/admin-spa/src/routes/Settings/Notifications/Channels.tsx new file mode 100644 index 0000000..97a6597 --- /dev/null +++ b/admin-spa/src/routes/Settings/Notifications/Channels.tsx @@ -0,0 +1,215 @@ +import React from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { api } from '@/lib/api'; +import { SettingsCard } from '../components/SettingsCard'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { RefreshCw, Mail, MessageCircle, Send, Bell, ExternalLink, Settings } from 'lucide-react'; +import { __ } from '@/lib/i18n'; + +interface NotificationChannel { + id: string; + label: string; + icon: string; + enabled: boolean; + builtin?: boolean; + addon?: string; +} + +export default function NotificationChannels() { + // Fetch channels + const { data: channels, isLoading } = useQuery({ + queryKey: ['notification-channels'], + queryFn: () => api.get('/notifications/channels'), + }); + + const getChannelIcon = (channelId: string) => { + switch (channelId) { + case 'email': + return ; + case 'whatsapp': + return ; + case 'telegram': + return ; + default: + return ; + } + }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + const builtinChannels = channels?.filter((c: NotificationChannel) => c.builtin) || []; + const addonChannels = channels?.filter((c: NotificationChannel) => !c.builtin) || []; + + return ( +
+ {/* Info Card */} + +
+

+ {__( + 'Channels are the different ways notifications can be sent. Email is built-in and always available. Install addons to enable additional channels like WhatsApp, Telegram, SMS, and more.' + )} +

+
+
+ + {/* Built-in Channels */} + +
+ {builtinChannels.map((channel: NotificationChannel) => ( +
+
+
{getChannelIcon(channel.id)}
+
+
+

{channel.label}

+ + {__('Built-in')} + + + {channel.enabled ? __('Active') : __('Inactive')} + +
+

+ {channel.id === 'email' && + __('Email notifications powered by WooCommerce. Configure templates and SMTP settings.')} +

+
+
+ {channel.id === 'email' && ( + + )} +
+ ))} +
+
+ + {/* Addon Channels */} + {addonChannels.length > 0 ? ( + +
+ {addonChannels.map((channel: NotificationChannel) => ( +
+
+
{getChannelIcon(channel.id)}
+
+
+

{channel.label}

+ + {__('Addon')} + + + {channel.enabled ? __('Active') : __('Inactive')} + +
+

+ {__('Provided by')} {channel.addon} +

+
+
+ +
+ ))} +
+
+ ) : ( + +
+

+ {__( + 'Install notification addons to send notifications via WhatsApp, Telegram, SMS, Push notifications, and more.' + )} +

+ +
+ {/* Example addon cards */} +
+
+ +

{__('WhatsApp Notifications')}

+
+

+ {__('Send order updates and notifications via WhatsApp Business API')} +

+ +
+ +
+
+ +

{__('Telegram Notifications')}

+
+

+ {__('Get instant notifications in your Telegram channel or group')} +

+ +
+ +
+
+ +

{__('SMS Notifications')}

+
+

+ {__('Send SMS notifications via Twilio, Nexmo, or other providers')} +

+ +
+ +
+
+ +

{__('Push Notifications')}

+
+

+ {__('Send browser push notifications to customers and admins')} +

+ +
+
+
+
+ )} +
+ ); +} diff --git a/admin-spa/src/routes/Settings/Notifications/Events.tsx b/admin-spa/src/routes/Settings/Notifications/Events.tsx new file mode 100644 index 0000000..5fbcf6f --- /dev/null +++ b/admin-spa/src/routes/Settings/Notifications/Events.tsx @@ -0,0 +1,312 @@ +import React from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { api } from '@/lib/api'; +import { SettingsCard } from '../components/SettingsCard'; +import { Switch } from '@/components/ui/switch'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { RefreshCw, Mail, MessageCircle, Send, Bell } from 'lucide-react'; +import { toast } from 'sonner'; +import { __ } from '@/lib/i18n'; + +interface NotificationEvent { + id: string; + label: string; + description: string; + category: string; + enabled: boolean; + channels: { + [channelId: string]: { + enabled: boolean; + recipient: 'admin' | 'customer' | 'both'; + }; + }; +} + +interface NotificationChannel { + id: string; + label: string; + icon: string; + enabled: boolean; + builtin?: boolean; + addon?: string; +} + +export default function NotificationEvents() { + const queryClient = useQueryClient(); + + // Fetch events + const { data: eventsData, isLoading: eventsLoading } = useQuery({ + queryKey: ['notification-events'], + queryFn: () => api.get('/notifications/events'), + }); + + // Fetch channels + const { data: channels, isLoading: channelsLoading } = useQuery({ + queryKey: ['notification-channels'], + queryFn: () => api.get('/notifications/channels'), + }); + + // Update event mutation + const updateMutation = useMutation({ + mutationFn: async ({ eventId, channelId, enabled, recipient }: any) => { + return api.post('/notifications/events/update', { + eventId, + channelId, + enabled, + recipient, + }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['notification-events'] }); + toast.success(__('Event settings updated')); + }, + onError: (error: any) => { + toast.error(error?.message || __('Failed to update event')); + }, + }); + + const getChannelIcon = (channelId: string) => { + switch (channelId) { + case 'email': + return ; + case 'whatsapp': + return ; + case 'telegram': + return ; + default: + return ; + } + }; + + const toggleChannel = (eventId: string, channelId: string, currentlyEnabled: boolean) => { + updateMutation.mutate({ + eventId, + channelId, + enabled: !currentlyEnabled, + recipient: 'admin', // Default recipient + }); + }; + + if (eventsLoading || channelsLoading) { + return ( +
+ +
+ ); + } + + const orderEvents = eventsData?.orders || []; + const productEvents = eventsData?.products || []; + const customerEvents = eventsData?.customers || []; + + return ( +
+ {/* Info Card */} + +
+

+ {__( + 'Choose which notification channels (Email, WhatsApp, Telegram, etc.) should be used for each event. Enable multiple channels to send notifications through different mediums.' + )} +

+
+

+ 💡 {__('Tip: Email is always available. Install addons to enable WhatsApp, Telegram, SMS, and other channels.')} +

+
+
+
+ + {/* Order Events */} + {orderEvents.length > 0 && ( + +
+ {orderEvents.map((event: NotificationEvent) => ( +
+
+
+

{event.label}

+

{event.description}

+
+ +
+ + {/* Channel Selection */} +
+ {channels?.map((channel: NotificationChannel) => { + const channelEnabled = event.channels?.[channel.id]?.enabled || false; + const recipient = event.channels?.[channel.id]?.recipient || 'admin'; + + return ( +
+
+
+ {getChannelIcon(channel.id)} +
+
+
+ {channel.label} + {channel.builtin && ( + + {__('Built-in')} + + )} +
+ {channelEnabled && ( + + {__('Send to')}: {recipient === 'admin' ? __('Admin') : recipient === 'customer' ? __('Customer') : __('Both')} + + )} +
+
+ toggleChannel(event.id, channel.id, channelEnabled)} + disabled={!channel.enabled || updateMutation.isPending} + /> +
+ ); + })} +
+
+ ))} +
+
+ )} + + {/* Product Events */} + {productEvents.length > 0 && ( + +
+ {productEvents.map((event: NotificationEvent) => ( +
+
+
+

{event.label}

+

{event.description}

+
+ +
+ +
+ {channels?.map((channel: NotificationChannel) => { + const channelEnabled = event.channels?.[channel.id]?.enabled || false; + const recipient = event.channels?.[channel.id]?.recipient || 'admin'; + + return ( +
+
+
+ {getChannelIcon(channel.id)} +
+
+
+ {channel.label} + {channel.builtin && ( + + {__('Built-in')} + + )} +
+ {channelEnabled && ( + + {__('Send to')}: {recipient === 'admin' ? __('Admin') : recipient === 'customer' ? __('Customer') : __('Both')} + + )} +
+
+ toggleChannel(event.id, channel.id, channelEnabled)} + disabled={!channel.enabled || updateMutation.isPending} + /> +
+ ); + })} +
+
+ ))} +
+
+ )} + + {/* Customer Events */} + {customerEvents.length > 0 && ( + +
+ {customerEvents.map((event: NotificationEvent) => ( +
+
+
+

{event.label}

+

{event.description}

+
+ +
+ +
+ {channels?.map((channel: NotificationChannel) => { + const channelEnabled = event.channels?.[channel.id]?.enabled || false; + const recipient = event.channels?.[channel.id]?.recipient || 'admin'; + + return ( +
+
+
+ {getChannelIcon(channel.id)} +
+
+
+ {channel.label} + {channel.builtin && ( + + {__('Built-in')} + + )} +
+ {channelEnabled && ( + + {__('Send to')}: {recipient === 'admin' ? __('Admin') : recipient === 'customer' ? __('Customer') : __('Both')} + + )} +
+
+ toggleChannel(event.id, channel.id, channelEnabled)} + disabled={!channel.enabled || updateMutation.isPending} + /> +
+ ); + })} +
+
+ ))} +
+
+ )} +
+ ); +} diff --git a/admin-spa/src/routes/Settings/Notifications/Templates.tsx b/admin-spa/src/routes/Settings/Notifications/Templates.tsx new file mode 100644 index 0000000..dd4b54b --- /dev/null +++ b/admin-spa/src/routes/Settings/Notifications/Templates.tsx @@ -0,0 +1,225 @@ +import React from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { api } from '@/lib/api'; +import { SettingsCard } from '../components/SettingsCard'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { RefreshCw, Mail, MessageCircle, Send, Bell, ExternalLink, Edit, Eye } from 'lucide-react'; +import { __ } from '@/lib/i18n'; + +interface NotificationChannel { + id: string; + label: string; + icon: string; + enabled: boolean; + builtin?: boolean; + addon?: string; +} + +export default function NotificationTemplates() { + // Fetch channels + const { data: channels, isLoading } = useQuery({ + queryKey: ['notification-channels'], + queryFn: () => api.get('/notifications/channels'), + }); + + const getChannelIcon = (channelId: string) => { + switch (channelId) { + case 'email': + return ; + case 'whatsapp': + return ; + case 'telegram': + return ; + default: + return ; + } + }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + const enabledChannels = channels?.filter((c: NotificationChannel) => c.enabled) || []; + + return ( +
+ {/* Info Card */} + +
+

+ {__( + 'Templates define the content and format of notifications sent through each channel. Customize the message, subject, and variables for each notification type.' + )} +

+
+

+ 💡 {__('Tip: Use variables like {customer_name}, {order_number}, and {order_total} to personalize your notifications.')} +

+
+
+
+ + {/* Email Templates */} + +
+
+
+
+ +
+
+
+

{__('Email Templates')}

+ + {__('Built-in')} + +
+

+ {__('Email templates are managed by WooCommerce. Customize subject lines, headers, and content.')} +

+
+
+ +
+ +
+

{__('Available Email Templates')}

+
    +
  • • {__('New Order (Admin)')}
  • +
  • • {__('Order Processing (Customer)')}
  • +
  • • {__('Order Completed (Customer)')}
  • +
  • • {__('Order Cancelled')}
  • +
  • • {__('Order Refunded')}
  • +
  • • {__('Customer Note')}
  • +
  • • {__('Low Stock Alert')}
  • +
  • • {__('Out of Stock Alert')}
  • +
+
+
+
+ + {/* Addon Channel Templates */} + {enabledChannels.filter((c: NotificationChannel) => !c.builtin).length > 0 ? ( + +
+ {enabledChannels + .filter((c: NotificationChannel) => !c.builtin) + .map((channel: NotificationChannel) => ( +
+
+
{getChannelIcon(channel.id)}
+
+
+

{channel.label} {__('Templates')}

+ + {__('Addon')} + +
+

+ {__('Customize message templates for')} {channel.label} {__('notifications')} +

+
+
+
+ + +
+
+ ))} +
+
+ ) : ( + +
+ +

+ {__( + 'Install notification addons like WhatsApp, Telegram, or SMS to customize their message templates.' + )} +

+ +
+
+ )} + + {/* Template Variables Reference */} + +
+
+

{__('Order Variables')}

+
+ {'{order_number}'} + {'{order_total}'} + {'{order_status}'} + {'{order_date}'} + {'{payment_method}'} + {'{shipping_method}'} +
+
+ +
+

{__('Customer Variables')}

+
+ {'{customer_name}'} + {'{customer_email}'} + {'{customer_phone}'} + {'{billing_address}'} + {'{shipping_address}'} +
+
+ +
+

{__('Store Variables')}

+
+ {'{store_name}'} + {'{store_url}'} + {'{store_email}'} + {'{store_phone}'} +
+
+
+
+
+ ); +} diff --git a/includes/Api/NotificationsController.php b/includes/Api/NotificationsController.php index 6953ed7..d3e9169 100644 --- a/includes/Api/NotificationsController.php +++ b/includes/Api/NotificationsController.php @@ -2,7 +2,7 @@ /** * Notifications REST API Controller * - * Handles notification settings API endpoints. + * Bridge to WooCommerce emails + addon channel framework. * * @package WooNooW\Api */ @@ -12,8 +12,6 @@ namespace WooNooW\Api; use WP_REST_Request; use WP_REST_Response; use WP_Error; -use WooNooW\Core\Notifications\NotificationManager; -use WooNooW\Core\Notifications\NotificationSettingsProvider; class NotificationsController { @@ -49,20 +47,11 @@ class NotificationsController { ], ]); - // GET /woonoow/v1/notifications/settings - register_rest_route($this->namespace, '/' . $this->rest_base . '/settings', [ - [ - 'methods' => 'GET', - 'callback' => [$this, 'get_settings'], - 'permission_callback' => [$this, 'check_permission'], - ], - ]); - - // POST /woonoow/v1/notifications/settings - register_rest_route($this->namespace, '/' . $this->rest_base . '/settings', [ + // POST /woonoow/v1/notifications/events/update + register_rest_route($this->namespace, '/' . $this->rest_base . '/events/update', [ [ 'methods' => 'POST', - 'callback' => [$this, 'update_settings'], + 'callback' => [$this, 'update_event'], 'permission_callback' => [$this, 'check_permission'], ], ]); @@ -75,17 +64,20 @@ class NotificationsController { * @return WP_REST_Response */ public function get_channels(WP_REST_Request $request) { - $channels = NotificationManager::get_channels(); + $channels = [ + [ + 'id' => 'email', + 'label' => __('Email', 'woonoow'), + 'icon' => 'mail', + 'enabled' => true, + 'builtin' => true, + ], + ]; - // Add enabled status from settings - $settings = NotificationSettingsProvider::get_settings(); - $channel_settings = $settings['channels'] ?? []; + // Allow addons to register their channels + $channels = apply_filters('woonoow_notification_channels', $channels); - foreach ($channels as $id => &$channel) { - $channel['enabled'] = $channel_settings[$id]['enabled'] ?? $channel['builtin']; - } - - return new WP_REST_Response(array_values($channels), 200); + return new WP_REST_Response($channels, 200); } /** @@ -95,78 +87,148 @@ class NotificationsController { * @return WP_REST_Response */ public function get_events(WP_REST_Request $request) { - $events = NotificationManager::get_events(); - $settings = NotificationSettingsProvider::get_settings(); - $event_settings = $settings['events'] ?? []; + // Get saved settings + $settings = get_option('woonoow_notification_settings', []); - // Merge event data with settings - foreach ($events as $id => &$event) { - $event_config = $event_settings[$id] ?? []; - $event['enabled'] = $event_config['enabled'] ?? true; - $event['channels'] = $event_config['channels'] ?? ['email']; - $event['recipients'] = $event_config['recipients'] ?? ['email' => 'admin']; - } - - // Group by category - $grouped = [ - 'orders' => [], - 'products' => [], - 'customers' => [], + // Define default events (maps to WooCommerce emails) + $events = [ + 'orders' => [ + [ + 'id' => 'order_placed', + 'label' => __('Order Placed', 'woonoow'), + 'description' => __('When a new order is placed', 'woonoow'), + 'category' => 'orders', + 'wc_email' => 'new_order', + 'enabled' => true, + 'channels' => $settings['order_placed'] ?? ['email' => ['enabled' => true, 'recipient' => 'admin']], + ], + [ + 'id' => 'order_processing', + 'label' => __('Order Processing', 'woonoow'), + 'description' => __('When order status changes to processing', 'woonoow'), + 'category' => 'orders', + 'wc_email' => 'customer_processing_order', + 'enabled' => true, + 'channels' => $settings['order_processing'] ?? ['email' => ['enabled' => true, 'recipient' => 'customer']], + ], + [ + 'id' => 'order_completed', + 'label' => __('Order Completed', 'woonoow'), + 'description' => __('When order is marked as completed', 'woonoow'), + 'category' => 'orders', + 'wc_email' => 'customer_completed_order', + 'enabled' => true, + 'channels' => $settings['order_completed'] ?? ['email' => ['enabled' => true, 'recipient' => 'customer']], + ], + [ + 'id' => 'order_cancelled', + 'label' => __('Order Cancelled', 'woonoow'), + 'description' => __('When order is cancelled', 'woonoow'), + 'category' => 'orders', + 'wc_email' => 'cancelled_order', + 'enabled' => true, + 'channels' => $settings['order_cancelled'] ?? ['email' => ['enabled' => true, 'recipient' => 'admin']], + ], + [ + 'id' => 'order_refunded', + 'label' => __('Order Refunded', 'woonoow'), + 'description' => __('When order is refunded', 'woonoow'), + 'category' => 'orders', + 'wc_email' => 'customer_refunded_order', + 'enabled' => true, + 'channels' => $settings['order_refunded'] ?? ['email' => ['enabled' => true, 'recipient' => 'customer']], + ], + ], + 'products' => [ + [ + 'id' => 'low_stock', + 'label' => __('Low Stock Alert', 'woonoow'), + 'description' => __('When product stock is low', 'woonoow'), + 'category' => 'products', + 'wc_email' => 'low_stock', + 'enabled' => true, + 'channels' => $settings['low_stock'] ?? ['email' => ['enabled' => true, 'recipient' => 'admin']], + ], + [ + 'id' => 'out_of_stock', + 'label' => __('Out of Stock Alert', 'woonoow'), + 'description' => __('When product is out of stock', 'woonoow'), + 'category' => 'products', + 'wc_email' => 'no_stock', + 'enabled' => true, + 'channels' => $settings['out_of_stock'] ?? ['email' => ['enabled' => true, 'recipient' => 'admin']], + ], + ], + 'customers' => [ + [ + 'id' => 'new_customer', + 'label' => __('New Customer', 'woonoow'), + 'description' => __('When a new customer registers', 'woonoow'), + 'category' => 'customers', + 'wc_email' => 'customer_new_account', + 'enabled' => true, + 'channels' => $settings['new_customer'] ?? ['email' => ['enabled' => true, 'recipient' => 'customer']], + ], + [ + 'id' => 'customer_note', + 'label' => __('Customer Note Added', 'woonoow'), + 'description' => __('When a note is added to customer order', 'woonoow'), + 'category' => 'customers', + 'wc_email' => 'customer_note', + 'enabled' => true, + 'channels' => $settings['customer_note'] ?? ['email' => ['enabled' => true, 'recipient' => 'customer']], + ], + ], ]; - foreach ($events as $event) { - $category = $event['category'] ?? 'general'; - if (!isset($grouped[$category])) { - $grouped[$category] = []; - } - $grouped[$category][] = $event; - } + // Allow addons to add custom events + $events = apply_filters('woonoow_notification_events', $events); - return new WP_REST_Response($grouped, 200); + return new WP_REST_Response($events, 200); } /** - * Get notification settings - * - * @param WP_REST_Request $request Request object - * @return WP_REST_Response - */ - public function get_settings(WP_REST_Request $request) { - $settings = NotificationSettingsProvider::get_settings(); - return new WP_REST_Response($settings, 200); - } - - /** - * Update notification settings + * Update event settings * * @param WP_REST_Request $request Request object * @return WP_REST_Response|WP_Error */ - public function update_settings(WP_REST_Request $request) { - $new_settings = $request->get_json_params(); + public function update_event(WP_REST_Request $request) { + $event_id = $request->get_param('eventId'); + $channel_id = $request->get_param('channelId'); + $enabled = $request->get_param('enabled'); + $recipient = $request->get_param('recipient'); - if (empty($new_settings)) { + if (empty($event_id) || empty($channel_id)) { return new WP_Error( - 'invalid_settings', - __('Invalid settings data', 'woonoow'), + 'invalid_params', + __('Event ID and Channel ID are required', 'woonoow'), ['status' => 400] ); } - $updated = NotificationSettingsProvider::update_settings($new_settings); + // Get current settings + $settings = get_option('woonoow_notification_settings', []); - if (!$updated) { - return new WP_Error( - 'update_failed', - __('Failed to update notification settings', 'woonoow'), - ['status' => 500] - ); + // Update settings + if (!isset($settings[$event_id])) { + $settings[$event_id] = []; } + $settings[$event_id][$channel_id] = [ + 'enabled' => $enabled, + 'recipient' => $recipient ?? 'admin', + ]; + + // Save settings + update_option('woonoow_notification_settings', $settings); + + // Fire action for addons to react + do_action('woonoow_notification_event_updated', $event_id, $channel_id, $enabled, $recipient); + return new WP_REST_Response([ 'success' => true, - 'message' => __('Notification settings updated successfully', 'woonoow'), - 'settings' => NotificationSettingsProvider::get_settings(), + 'message' => __('Event settings updated successfully', 'woonoow'), ], 200); } diff --git a/includes/Core/Bootstrap.php b/includes/Core/Bootstrap.php index f8868b7..c2c8fcb 100644 --- a/includes/Core/Bootstrap.php +++ b/includes/Core/Bootstrap.php @@ -19,7 +19,6 @@ use WooNooW\Core\Mail\MailQueue; use WooNooW\Core\Mail\WooEmailOverride; use WooNooW\Core\DataStores\OrderStore; use WooNooW\Core\MediaUpload; -use WooNooW\Core\Notifications\NotificationManager; use WooNooW\Branding; class Bootstrap { @@ -31,7 +30,6 @@ class Bootstrap { StandaloneAdmin::init(); Branding::init(); MediaUpload::init(); - NotificationManager::init(); // Addon system (order matters: Registry → Routes → Navigation) AddonRegistry::init(); diff --git a/includes/Core/Notifications/NotificationManager.php b/includes/Core/Notifications/NotificationManager.php deleted file mode 100644 index fea52d9..0000000 --- a/includes/Core/Notifications/NotificationManager.php +++ /dev/null @@ -1,230 +0,0 @@ - 'email', - 'label' => __('Email', 'woonoow'), - 'icon' => 'mail', - 'builtin' => true, - 'enabled' => true, - ]); - - // Register default notification events - self::register_default_events(); - - // Allow addons to register their channels - add_action('woonoow_register_notification_channels', [__CLASS__, 'allow_addon_registration']); - } - - /** - * Register a notification channel - * - * @param string $id Channel ID - * @param array $args Channel arguments - */ - public static function register_channel($id, $args = []) { - $defaults = [ - 'id' => $id, - 'label' => ucfirst($id), - 'icon' => 'bell', - 'builtin' => false, - 'enabled' => false, - 'addon' => '', - ]; - - self::$channels[$id] = wp_parse_args($args, $defaults); - } - - /** - * Get all registered channels - * - * @return array - */ - public static function get_channels() { - return apply_filters('woonoow_notification_channels', self::$channels); - } - - /** - * Register default notification events - */ - private static function register_default_events() { - // Order events - self::register_event('order_placed', [ - 'label' => __('Order Placed', 'woonoow'), - 'description' => __('When a new order is placed', 'woonoow'), - 'category' => 'orders', - 'wc_email' => 'new_order', // Maps to WC_Email_New_Order - ]); - - self::register_event('order_processing', [ - 'label' => __('Order Processing', 'woonoow'), - 'description' => __('When order status changes to processing', 'woonoow'), - 'category' => 'orders', - 'wc_email' => 'customer_processing_order', - ]); - - self::register_event('order_completed', [ - 'label' => __('Order Completed', 'woonoow'), - 'description' => __('When order is marked as completed', 'woonoow'), - 'category' => 'orders', - 'wc_email' => 'customer_completed_order', - ]); - - self::register_event('order_cancelled', [ - 'label' => __('Order Cancelled', 'woonoow'), - 'description' => __('When order is cancelled', 'woonoow'), - 'category' => 'orders', - 'wc_email' => 'cancelled_order', - ]); - - self::register_event('order_refunded', [ - 'label' => __('Order Refunded', 'woonoow'), - 'description' => __('When order is refunded', 'woonoow'), - 'category' => 'orders', - 'wc_email' => 'customer_refunded_order', - ]); - - // Product events - self::register_event('low_stock', [ - 'label' => __('Low Stock Alert', 'woonoow'), - 'description' => __('When product stock is low', 'woonoow'), - 'category' => 'products', - 'wc_email' => 'low_stock', - ]); - - self::register_event('out_of_stock', [ - 'label' => __('Out of Stock Alert', 'woonoow'), - 'description' => __('When product is out of stock', 'woonoow'), - 'category' => 'products', - 'wc_email' => 'no_stock', - ]); - - // Customer events - self::register_event('new_customer', [ - 'label' => __('New Customer', 'woonoow'), - 'description' => __('When a new customer registers', 'woonoow'), - 'category' => 'customers', - 'wc_email' => 'customer_new_account', - ]); - - self::register_event('customer_note', [ - 'label' => __('Customer Note Added', 'woonoow'), - 'description' => __('When a note is added to customer order', 'woonoow'), - 'category' => 'customers', - 'wc_email' => 'customer_note', - ]); - } - - /** - * Register a notification event - * - * @param string $id Event ID - * @param array $args Event arguments - */ - public static function register_event($id, $args = []) { - $defaults = [ - 'id' => $id, - 'label' => ucfirst(str_replace('_', ' ', $id)), - 'description' => '', - 'category' => 'general', - 'wc_email' => '', - 'channels' => [], - ]; - - self::$events[$id] = wp_parse_args($args, $defaults); - } - - /** - * Get all registered events - * - * @return array - */ - public static function get_events() { - return apply_filters('woonoow_notification_events', self::$events); - } - - /** - * Get events by category - * - * @param string $category Category name - * @return array - */ - public static function get_events_by_category($category) { - $events = self::get_events(); - return array_filter($events, function($event) use ($category) { - return $event['category'] === $category; - }); - } - - /** - * Send notification - * - * @param string $event_id Event ID - * @param array $data Notification data - * @param array $channels Channels to use (default: from settings) - */ - public static function send($event_id, $data = [], $channels = null) { - // Get event configuration - $event = self::$events[$event_id] ?? null; - if (!$event) { - return; - } - - // Get channels from settings if not specified - if ($channels === null) { - $settings = NotificationSettingsProvider::get_event_settings($event_id); - $channels = $settings['channels'] ?? ['email']; - } - - // Send via each enabled channel - foreach ($channels as $channel_id) { - // Email is handled by WooCommerce, skip it - if ($channel_id === 'email') { - continue; - } - - // Fire action for addon channels - do_action("woonoow_notification_send_{$channel_id}", $event_id, $data); - } - } - - /** - * Allow addons to register channels - */ - public static function allow_addon_registration() { - // Addons hook into this to register their channels - // Example: add_action('woonoow_register_notification_channels', function() { - // NotificationManager::register_channel('whatsapp', [...]); - // }); - } -} diff --git a/includes/Core/Notifications/NotificationSettingsProvider.php b/includes/Core/Notifications/NotificationSettingsProvider.php deleted file mode 100644 index 297c07c..0000000 --- a/includes/Core/Notifications/NotificationSettingsProvider.php +++ /dev/null @@ -1,182 +0,0 @@ - self::get_default_event_settings(), - 'channels' => self::get_default_channel_settings(), - ]; - } - - /** - * Get default event settings - * - * @return array - */ - private static function get_default_event_settings() { - $events = NotificationManager::get_events(); - $settings = []; - - foreach ($events as $event_id => $event) { - $settings[$event_id] = [ - 'enabled' => true, - 'channels' => ['email'], // Email enabled by default - 'recipients' => [ - 'email' => 'admin', // admin, customer, or both - ], - ]; - } - - return $settings; - } - - /** - * Get default channel settings - * - * @return array - */ - private static function get_default_channel_settings() { - return [ - 'email' => [ - 'enabled' => true, - // Email settings are managed by WooCommerce - // We just track if it's enabled in our system - ], - ]; - } - - /** - * Get settings for a specific event - * - * @param string $event_id Event ID - * @return array - */ - public static function get_event_settings($event_id) { - $settings = self::get_settings(); - return $settings['events'][$event_id] ?? []; - } - - /** - * Get settings for a specific channel - * - * @param string $channel_id Channel ID - * @return array - */ - public static function get_channel_settings($channel_id) { - $settings = self::get_settings(); - return $settings['channels'][$channel_id] ?? []; - } - - /** - * Update notification settings - * - * @param array $new_settings New settings - * @return bool - */ - public static function update_settings($new_settings) { - $current = self::get_settings(); - $updated = wp_parse_args($new_settings, $current); - - return update_option(self::OPTION_KEY, $updated); - } - - /** - * Update event settings - * - * @param string $event_id Event ID - * @param array $event_settings Event settings - * @return bool - */ - public static function update_event_settings($event_id, $event_settings) { - $settings = self::get_settings(); - $settings['events'][$event_id] = $event_settings; - - return self::update_settings($settings); - } - - /** - * Update channel settings - * - * @param string $channel_id Channel ID - * @param array $channel_settings Channel settings - * @return bool - */ - public static function update_channel_settings($channel_id, $channel_settings) { - $settings = self::get_settings(); - $settings['channels'][$channel_id] = $channel_settings; - - return self::update_settings($settings); - } - - /** - * Check if event is enabled - * - * @param string $event_id Event ID - * @return bool - */ - public static function is_event_enabled($event_id) { - $event_settings = self::get_event_settings($event_id); - return $event_settings['enabled'] ?? true; - } - - /** - * Check if channel is enabled for event - * - * @param string $event_id Event ID - * @param string $channel_id Channel ID - * @return bool - */ - public static function is_channel_enabled_for_event($event_id, $channel_id) { - $event_settings = self::get_event_settings($event_id); - $channels = $event_settings['channels'] ?? []; - - return in_array($channel_id, $channels, true); - } - - /** - * Get recipient type for event channel - * - * @param string $event_id Event ID - * @param string $channel_id Channel ID - * @return string admin|customer|both - */ - public static function get_recipient_type($event_id, $channel_id) { - $event_settings = self::get_event_settings($event_id); - $recipients = $event_settings['recipients'] ?? []; - - return $recipients[$channel_id] ?? 'admin'; - } -}