feat: Implement notification system with extensible channel architecture
## ✅ Notification System Implementation Following NOTIFICATION_STRATEGY.md, built on top of WooCommerce email infrastructure. ### Backend (PHP) **1. NotificationManager** (`includes/Core/Notifications/NotificationManager.php`) - Central manager for notification system - Registers email channel (built-in) - Registers default notification events (orders, products, customers) - Provides hooks for addon channels - Maps to WooCommerce email IDs **2. NotificationSettingsProvider** (`includes/Core/Notifications/NotificationSettingsProvider.php`) - Manages settings in wp_options - Per-event channel configuration - Per-channel recipient settings (admin/customer/both) - Default settings with email enabled **3. NotificationsController** (`includes/Api/NotificationsController.php`) - REST API endpoints: - GET /notifications/channels - List available channels - GET /notifications/events - List notification events (grouped by category) - GET /notifications/settings - Get all settings - POST /notifications/settings - Update settings ### Frontend (React) **Updated Notifications.tsx:** - Shows available notification channels (email + addons) - Channel cards with built-in/addon badges - Event configuration by category (orders, products, customers) - Toggle channels per event with button UI - Link to WooCommerce advanced email settings - Responsive and modern UI ### Key Features ✅ **Built on WooCommerce Emails** - Email channel uses existing WC email system - No reinventing the wheel - Maps events to WC email IDs ✅ **Extensible Architecture** - Addons can register channels via hooks - `woonoow_notification_channels` filter - `woonoow_notification_send_{channel}` action - Per-event channel selection ✅ **User-Friendly UI** - Clear channel status (Active/Inactive) - Per-event channel toggles - Category grouping (orders, products, customers) - Addon discovery hints ✅ **Settings Storage** - Stored in wp_options (woonoow_notification_settings) - Per-event configuration - Per-channel settings - Default: email enabled for all events ### Addon Integration Example ```php // Addon registers WhatsApp channel add_action("woonoow_register_notification_channels", function() { NotificationManager::register_channel("whatsapp", [ "label" => "WhatsApp", "icon" => "message-circle", "addon" => "woonoow-whatsapp", ]); }); // Addon handles sending add_action("woonoow_notification_send_whatsapp", function($event_id, $data) { // Send WhatsApp message }, 10, 2); ``` ### Files Created - NotificationManager.php - NotificationSettingsProvider.php - NotificationsController.php ### Files Modified - Routes.php - Register NotificationsController - Bootstrap.php - Initialize NotificationManager - Notifications.tsx - New UI with channels and events --- **Ready for addon development!** 🚀 Next: Build Telegram addon as proof of concept
This commit is contained in:
@@ -1,43 +1,76 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { api } from '@/lib/api';
|
import { api } from '@/lib/api';
|
||||||
import { SettingsLayout } from './components/SettingsLayout';
|
import { SettingsLayout } from './components/SettingsLayout';
|
||||||
import { SettingsCard } from './components/SettingsCard';
|
import { SettingsCard } from './components/SettingsCard';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { ExternalLink, RefreshCw, Mail } from 'lucide-react';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { ExternalLink, RefreshCw, Mail, MessageCircle, Send, Bell } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { __ } from '@/lib/i18n';
|
import { __ } from '@/lib/i18n';
|
||||||
|
|
||||||
export default function NotificationsSettings() {
|
export default function NotificationsSettings() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const wcAdminUrl = (window as any).WNW_CONFIG?.wpAdminUrl || '/wp-admin';
|
const [activeTab, setActiveTab] = useState<'channels' | 'events'>('channels');
|
||||||
|
|
||||||
// Fetch email settings
|
// Fetch notification channels
|
||||||
const { data: settings, isLoading, refetch } = useQuery({
|
const { data: channels, isLoading: channelsLoading } = useQuery({
|
||||||
queryKey: ['email-settings'],
|
queryKey: ['notification-channels'],
|
||||||
queryFn: () => api.get('/settings/emails'),
|
queryFn: () => api.get('/notifications/channels'),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Toggle email mutation
|
// Fetch notification events
|
||||||
const toggleMutation = useMutation({
|
const { data: events, isLoading: eventsLoading } = useQuery({
|
||||||
mutationFn: async ({ emailId, enabled }: { emailId: string; enabled: boolean }) => {
|
queryKey: ['notification-events'],
|
||||||
return api.post(`/settings/emails/${emailId}/toggle`, { enabled });
|
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: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['email-settings'] });
|
queryClient.invalidateQueries({ queryKey: ['notification-settings'] });
|
||||||
toast.success(__('Email settings updated'));
|
queryClient.invalidateQueries({ queryKey: ['notification-events'] });
|
||||||
|
toast.success(__('Notification settings updated'));
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error?.message || __('Failed to update email settings'));
|
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) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<SettingsLayout
|
<SettingsLayout
|
||||||
title={__('Notifications')}
|
title={__('Notifications')}
|
||||||
description={__('Manage email notifications sent to customers and admins')}
|
description={__('Manage notifications sent via email and other channels')}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
<RefreshCw className="h-6 w-6 animate-spin text-muted-foreground" />
|
<RefreshCw className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||||
@@ -46,110 +79,198 @@ export default function NotificationsSettings() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const customerEmails = settings?.emails?.filter((e: any) => e.recipient === 'customer') || [];
|
const getChannelIcon = (channelId: string) => {
|
||||||
const adminEmails = settings?.emails?.filter((e: any) => e.recipient === 'admin') || [];
|
switch (channelId) {
|
||||||
|
case 'email': return <Mail className="h-4 w-4" />;
|
||||||
|
case 'whatsapp': return <MessageCircle className="h-4 w-4" />;
|
||||||
|
case 'telegram': return <Send className="h-4 w-4" />;
|
||||||
|
default: return <Bell className="h-4 w-4" />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsLayout
|
<SettingsLayout
|
||||||
title={__('Notifications')}
|
title={__('Notifications')}
|
||||||
description={__('Manage email notifications sent to customers and admins')}
|
description={__('Manage notifications sent via email and other channels')}
|
||||||
action={
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => refetch()}
|
|
||||||
>
|
|
||||||
<RefreshCw className="h-4 w-4 mr-2" />
|
|
||||||
{__('Refresh')}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Info Card - Shopify/Marketplace Style */}
|
{/* Notification Channels */}
|
||||||
<SettingsCard
|
<SettingsCard
|
||||||
title={__('Email Notifications')}
|
title={__('Notification Channels')}
|
||||||
description={__('Manage automated emails sent to customers and admins')}
|
description={__('Available channels for sending notifications')}
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{channels?.map((channel: any) => (
|
||||||
|
<div key={channel.id} className="flex items-center justify-between py-3 border-b last:border-0">
|
||||||
|
<div className="flex items-center gap-3 flex-1">
|
||||||
|
<div className="p-2 rounded-lg bg-muted">
|
||||||
|
{getChannelIcon(channel.id)}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<h3 className="font-medium text-sm">{channel.label}</h3>
|
||||||
|
{channel.builtin && (
|
||||||
|
<Badge variant="secondary" className="text-xs">{__('Built-in')}</Badge>
|
||||||
|
)}
|
||||||
|
{channel.addon && (
|
||||||
|
<Badge variant="outline" className="text-xs">{__('Addon')}</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{channel.addon && (
|
||||||
|
<p className="text-xs text-muted-foreground mt-0.5">
|
||||||
|
{__('Provided by')} {channel.addon}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Badge variant={channel.enabled ? 'default' : 'secondary'}>
|
||||||
|
{channel.enabled ? __('Active') : __('Inactive')}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div className="bg-muted/50 rounded-lg p-4 mt-4">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
💡 {__('Want more channels like WhatsApp, Telegram, or SMS? Install notification addons to extend your notification capabilities.')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SettingsCard>
|
||||||
|
|
||||||
|
{/* Order Notifications */}
|
||||||
|
{events?.orders && events.orders.length > 0 && (
|
||||||
|
<SettingsCard
|
||||||
|
title={__('Order Notifications')}
|
||||||
|
description={__('Notifications sent when order status changes')}
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{events.orders.map((event: any) => (
|
||||||
|
<div key={event.id} className="py-3 border-b last:border-0">
|
||||||
|
<div className="flex items-start justify-between mb-3">
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-medium text-sm">{event.label}</h3>
|
||||||
|
<p className="text-xs text-muted-foreground mt-0.5">{event.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{channels?.map((channel: any) => {
|
||||||
|
const isEnabled = event.channels?.includes(channel.id);
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={channel.id}
|
||||||
|
variant={isEnabled ? 'default' : 'outline'}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => toggleEventChannel(event.id, channel.id, !isEnabled)}
|
||||||
|
disabled={!channel.enabled || updateMutation.isPending}
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
{getChannelIcon(channel.id)}
|
||||||
|
<span className="ml-1.5">{channel.label}</span>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SettingsCard>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Product Notifications */}
|
||||||
|
{events?.products && events.products.length > 0 && (
|
||||||
|
<SettingsCard
|
||||||
|
title={__('Product Notifications')}
|
||||||
|
description={__('Notifications about product stock levels')}
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{events.products.map((event: any) => (
|
||||||
|
<div key={event.id} className="py-3 border-b last:border-0">
|
||||||
|
<div className="flex items-start justify-between mb-3">
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-medium text-sm">{event.label}</h3>
|
||||||
|
<p className="text-xs text-muted-foreground mt-0.5">{event.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{channels?.map((channel: any) => {
|
||||||
|
const isEnabled = event.channels?.includes(channel.id);
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={channel.id}
|
||||||
|
variant={isEnabled ? 'default' : 'outline'}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => toggleEventChannel(event.id, channel.id, !isEnabled)}
|
||||||
|
disabled={!channel.enabled || updateMutation.isPending}
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
{getChannelIcon(channel.id)}
|
||||||
|
<span className="ml-1.5">{channel.label}</span>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SettingsCard>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Customer Notifications */}
|
||||||
|
{events?.customers && events.customers.length > 0 && (
|
||||||
|
<SettingsCard
|
||||||
|
title={__('Customer Notifications')}
|
||||||
|
description={__('Notifications about customer activities')}
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{events.customers.map((event: any) => (
|
||||||
|
<div key={event.id} className="py-3 border-b last:border-0">
|
||||||
|
<div className="flex items-start justify-between mb-3">
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-medium text-sm">{event.label}</h3>
|
||||||
|
<p className="text-xs text-muted-foreground mt-0.5">{event.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{channels?.map((channel: any) => {
|
||||||
|
const isEnabled = event.channels?.includes(channel.id);
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={channel.id}
|
||||||
|
variant={isEnabled ? 'default' : 'outline'}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => toggleEventChannel(event.id, channel.id, !isEnabled)}
|
||||||
|
disabled={!channel.enabled || updateMutation.isPending}
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
{getChannelIcon(channel.id)}
|
||||||
|
<span className="ml-1.5">{channel.label}</span>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SettingsCard>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* WooCommerce Email Settings Link */}
|
||||||
|
<SettingsCard
|
||||||
|
title={__('Advanced Email Settings')}
|
||||||
|
description={__('Customize email templates and sender details')}
|
||||||
>
|
>
|
||||||
<div className="text-sm space-y-3">
|
<div className="text-sm space-y-3">
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
{__('Control which email notifications are sent automatically when orders are placed, shipped, or updated. All emails use your store branding and can be customized in WooCommerce settings.')}
|
{__('Email notifications are powered by WooCommerce. For advanced customization like templates, subject lines, and sender details, use the WooCommerce email settings.')}
|
||||||
</p>
|
</p>
|
||||||
|
<Button
|
||||||
<div className="bg-muted/50 rounded-lg p-4 space-y-2">
|
variant="outline"
|
||||||
<p className="font-medium text-foreground text-sm">
|
size="sm"
|
||||||
💡 {__('Quick Tips')}
|
onClick={() => window.open(`${(window as any).WNW_CONFIG?.wpAdminUrl || '/wp-admin'}/admin.php?page=wc-settings&tab=email`, '_blank')}
|
||||||
</p>
|
>
|
||||||
<ul className="text-xs text-muted-foreground space-y-1.5">
|
<ExternalLink className="h-4 w-4 mr-2" />
|
||||||
<li>• {__('Keep order confirmation emails enabled - customers expect immediate confirmation')}</li>
|
{__('Open WooCommerce Email Settings')}
|
||||||
<li>• {__('Enable shipping notifications to reduce "where is my order?" inquiries')}</li>
|
</Button>
|
||||||
<li>• {__('Admin notifications help you stay on top of new orders and issues')}</li>
|
|
||||||
<li>• {__('Disable emails you don\'t need to reduce inbox clutter')}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="pt-2">
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{__('Need to customize email templates, subject lines, or sender details?')}{' '}
|
|
||||||
<a
|
|
||||||
href={`${(window as any).WNW_CONFIG?.wpAdminUrl || '/wp-admin'}/admin.php?page=wc-settings&tab=email`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-primary hover:underline"
|
|
||||||
>
|
|
||||||
{__('Go to advanced email settings →')}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SettingsCard>
|
|
||||||
|
|
||||||
{/* Customer Emails */}
|
|
||||||
<SettingsCard
|
|
||||||
title={__('Customer Notifications')}
|
|
||||||
description={__('Emails sent to customers about their orders')}
|
|
||||||
>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{customerEmails.map((email: any) => (
|
|
||||||
<div key={email.id} className="flex items-center justify-between py-3 border-b last:border-0">
|
|
||||||
<div className="flex-1">
|
|
||||||
<h3 className="font-medium text-sm">{email.title}</h3>
|
|
||||||
<p className="text-xs text-muted-foreground mt-0.5">{email.description}</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={email.enabled === 'yes'}
|
|
||||||
onCheckedChange={(checked) => toggleMutation.mutate({
|
|
||||||
emailId: email.id,
|
|
||||||
enabled: checked
|
|
||||||
})}
|
|
||||||
disabled={toggleMutation.isPending}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</SettingsCard>
|
|
||||||
|
|
||||||
{/* Admin Emails */}
|
|
||||||
<SettingsCard
|
|
||||||
title={__('Admin Notifications')}
|
|
||||||
description={__('Emails sent to store administrators')}
|
|
||||||
>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{adminEmails.map((email: any) => (
|
|
||||||
<div key={email.id} className="flex items-center justify-between py-3 border-b last:border-0">
|
|
||||||
<div className="flex-1">
|
|
||||||
<h3 className="font-medium text-sm">{email.title}</h3>
|
|
||||||
<p className="text-xs text-muted-foreground mt-0.5">{email.description}</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={email.enabled === 'yes'}
|
|
||||||
onCheckedChange={(checked) => toggleMutation.mutate({
|
|
||||||
emailId: email.id,
|
|
||||||
enabled: checked
|
|
||||||
})}
|
|
||||||
disabled={toggleMutation.isPending}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</SettingsCard>
|
</SettingsCard>
|
||||||
|
|
||||||
|
|||||||
181
includes/Api/NotificationsController.php
Normal file
181
includes/Api/NotificationsController.php
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Notifications REST API Controller
|
||||||
|
*
|
||||||
|
* Handles notification settings API endpoints.
|
||||||
|
*
|
||||||
|
* @package WooNooW\Api
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API namespace
|
||||||
|
*/
|
||||||
|
private $namespace = 'woonoow/v1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API base
|
||||||
|
*/
|
||||||
|
private $rest_base = 'notifications';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register routes
|
||||||
|
*/
|
||||||
|
public function register_routes() {
|
||||||
|
// GET /woonoow/v1/notifications/channels
|
||||||
|
register_rest_route($this->namespace, '/' . $this->rest_base . '/channels', [
|
||||||
|
[
|
||||||
|
'methods' => 'GET',
|
||||||
|
'callback' => [$this, 'get_channels'],
|
||||||
|
'permission_callback' => [$this, 'check_permission'],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// GET /woonoow/v1/notifications/events
|
||||||
|
register_rest_route($this->namespace, '/' . $this->rest_base . '/events', [
|
||||||
|
[
|
||||||
|
'methods' => 'GET',
|
||||||
|
'callback' => [$this, 'get_events'],
|
||||||
|
'permission_callback' => [$this, 'check_permission'],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 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', [
|
||||||
|
[
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => [$this, 'update_settings'],
|
||||||
|
'permission_callback' => [$this, 'check_permission'],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get available notification channels
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request Request object
|
||||||
|
* @return WP_REST_Response
|
||||||
|
*/
|
||||||
|
public function get_channels(WP_REST_Request $request) {
|
||||||
|
$channels = NotificationManager::get_channels();
|
||||||
|
|
||||||
|
// Add enabled status from settings
|
||||||
|
$settings = NotificationSettingsProvider::get_settings();
|
||||||
|
$channel_settings = $settings['channels'] ?? [];
|
||||||
|
|
||||||
|
foreach ($channels as $id => &$channel) {
|
||||||
|
$channel['enabled'] = $channel_settings[$id]['enabled'] ?? $channel['builtin'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WP_REST_Response(array_values($channels), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get notification events
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request Request object
|
||||||
|
* @return WP_REST_Response
|
||||||
|
*/
|
||||||
|
public function get_events(WP_REST_Request $request) {
|
||||||
|
$events = NotificationManager::get_events();
|
||||||
|
$settings = NotificationSettingsProvider::get_settings();
|
||||||
|
$event_settings = $settings['events'] ?? [];
|
||||||
|
|
||||||
|
// 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' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($events as $event) {
|
||||||
|
$category = $event['category'] ?? 'general';
|
||||||
|
if (!isset($grouped[$category])) {
|
||||||
|
$grouped[$category] = [];
|
||||||
|
}
|
||||||
|
$grouped[$category][] = $event;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WP_REST_Response($grouped, 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
|
||||||
|
*
|
||||||
|
* @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();
|
||||||
|
|
||||||
|
if (empty($new_settings)) {
|
||||||
|
return new WP_Error(
|
||||||
|
'invalid_settings',
|
||||||
|
__('Invalid settings data', 'woonoow'),
|
||||||
|
['status' => 400]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$updated = NotificationSettingsProvider::update_settings($new_settings);
|
||||||
|
|
||||||
|
if (!$updated) {
|
||||||
|
return new WP_Error(
|
||||||
|
'update_failed',
|
||||||
|
__('Failed to update notification settings', 'woonoow'),
|
||||||
|
['status' => 500]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WP_REST_Response([
|
||||||
|
'success' => true,
|
||||||
|
'message' => __('Notification settings updated successfully', 'woonoow'),
|
||||||
|
'settings' => NotificationSettingsProvider::get_settings(),
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user has permission
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function check_permission() {
|
||||||
|
return current_user_can('manage_woocommerce') || current_user_can('manage_options');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ use WooNooW\Api\PickupLocationsController;
|
|||||||
use WooNooW\Api\EmailController;
|
use WooNooW\Api\EmailController;
|
||||||
use WooNooW\API\DeveloperController;
|
use WooNooW\API\DeveloperController;
|
||||||
use WooNooW\API\SystemController;
|
use WooNooW\API\SystemController;
|
||||||
|
use WooNooW\Api\NotificationsController;
|
||||||
|
|
||||||
class Routes {
|
class Routes {
|
||||||
public static function init() {
|
public static function init() {
|
||||||
@@ -79,6 +80,10 @@ class Routes {
|
|||||||
// System controller
|
// System controller
|
||||||
$system_controller = new SystemController();
|
$system_controller = new SystemController();
|
||||||
$system_controller->register_routes();
|
$system_controller->register_routes();
|
||||||
|
|
||||||
|
// Notifications controller
|
||||||
|
$notifications_controller = new NotificationsController();
|
||||||
|
$notifications_controller->register_routes();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use WooNooW\Core\Mail\MailQueue;
|
|||||||
use WooNooW\Core\Mail\WooEmailOverride;
|
use WooNooW\Core\Mail\WooEmailOverride;
|
||||||
use WooNooW\Core\DataStores\OrderStore;
|
use WooNooW\Core\DataStores\OrderStore;
|
||||||
use WooNooW\Core\MediaUpload;
|
use WooNooW\Core\MediaUpload;
|
||||||
|
use WooNooW\Core\Notifications\NotificationManager;
|
||||||
use WooNooW\Branding;
|
use WooNooW\Branding;
|
||||||
|
|
||||||
class Bootstrap {
|
class Bootstrap {
|
||||||
@@ -30,6 +31,7 @@ class Bootstrap {
|
|||||||
StandaloneAdmin::init();
|
StandaloneAdmin::init();
|
||||||
Branding::init();
|
Branding::init();
|
||||||
MediaUpload::init();
|
MediaUpload::init();
|
||||||
|
NotificationManager::init();
|
||||||
|
|
||||||
// Addon system (order matters: Registry → Routes → Navigation)
|
// Addon system (order matters: Registry → Routes → Navigation)
|
||||||
AddonRegistry::init();
|
AddonRegistry::init();
|
||||||
|
|||||||
230
includes/Core/Notifications/NotificationManager.php
Normal file
230
includes/Core/Notifications/NotificationManager.php
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Notification Manager
|
||||||
|
*
|
||||||
|
* Central manager for notification system. Works with WooCommerce emails
|
||||||
|
* and provides hooks for addon channels (WhatsApp, Telegram, etc.)
|
||||||
|
*
|
||||||
|
* @package WooNooW\Core\Notifications
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace WooNooW\Core\Notifications;
|
||||||
|
|
||||||
|
class NotificationManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registered notification channels
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private static $channels = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notification events registry
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private static $events = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize notification system
|
||||||
|
*/
|
||||||
|
public static function init() {
|
||||||
|
// Register built-in email channel
|
||||||
|
self::register_channel('email', [
|
||||||
|
'id' => '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', [...]);
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
182
includes/Core/Notifications/NotificationSettingsProvider.php
Normal file
182
includes/Core/Notifications/NotificationSettingsProvider.php
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Notification Settings Provider
|
||||||
|
*
|
||||||
|
* Manages notification settings stored in wp_options.
|
||||||
|
* Works with WooCommerce email settings.
|
||||||
|
*
|
||||||
|
* @package WooNooW\Core\Notifications
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace WooNooW\Core\Notifications;
|
||||||
|
|
||||||
|
class NotificationSettingsProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option key for notification settings
|
||||||
|
*/
|
||||||
|
const OPTION_KEY = 'woonoow_notification_settings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all notification settings
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function get_settings() {
|
||||||
|
$defaults = self::get_default_settings();
|
||||||
|
$saved = get_option(self::OPTION_KEY, []);
|
||||||
|
|
||||||
|
return wp_parse_args($saved, $defaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default notification settings
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private static function get_default_settings() {
|
||||||
|
return [
|
||||||
|
'events' => 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user