refactor: Migrate documentation content, rebuild UI components, and update core architecture.

This commit is contained in:
gitfromwildan
2026-03-10 01:38:58 +07:00
parent aac81dff8a
commit ab755844a3
132 changed files with 3947 additions and 12862 deletions

View File

@@ -0,0 +1,79 @@
---
title: Bridge Pattern
description: Integrating third-party plugins with WooNooW
date: 2024-01-31
---
## Philosophy
**WooNooW Core = Zero Addon Dependencies**
We don't integrate specific plugins into WooNooW core. Instead, we provide:
1. **Hook system** for addons to extend functionality
2. **Bridge snippets** for compatibility with existing plugins
3. **Addon development guide** for building proper WooNooW addons
---
## The Problem
Example: **Rajaongkir** (Indonesian Shipping Plugin).
It removes standard fields and adds custom dropdowns, storing data in WooCommerce sessions.
It doesn't work with WooNooW OrderForm out of the box because the OrderForm uses standard fields and API-based validation.
---
## Solution: Bridge Snippet
### Option A: Standalone Bridge Plugin
Create a tiny bridge plugin that makes the third-party plugin work with WooNooW.
```php
/**
* Plugin Name: WooNooW Rajaongkir Bridge
* Description: Makes Rajaongkir plugin work with WooNooW OrderForm
* Version: 1.0.0
*/
// Hook into WooNooW's shipping calculation
add_filter('woonoow_before_shipping_calculate', function($shipping_data) {
if ($shipping_data['country'] === 'ID' && !empty($shipping_data['city'])) {
// Search API and set session data
$api = Cekongkir_API::get_instance();
$results = $api->search_destination_api($shipping_data['city']);
if (!empty($results[0])) {
WC()->session->set('selected_destination_id', $results[0]['id']);
}
}
return $shipping_data;
});
```
### Option B: Frontend Injection
Inject script to handle UI changes.
```typescript
import { addonLoader, addFilter } from '@woonoow/hooks';
addonLoader.register({
id: 'rajaongkir-bridge',
init: () => {
addFilter('woonoow_order_form_after_shipping', (content, formData, setFormData) => {
if (formData.shipping?.country !== 'ID') return content;
return (
<>
{content}
<div className="custom-field">
{/* Custom Destination Select */}
</div>
</>
);
});
}
});
```

View File

@@ -0,0 +1,311 @@
---
title: Custom Notification Channels
description: Learn how to add custom notification channels like WhatsApp, SMS, or Telegram to WooNooW
---
WooNooW supports custom notification channels through a pluggable architecture. You can extend the notification system to send messages via WhatsApp, SMS, Telegram, or any other service.
## Architecture Overview
The multi-channel notification system consists of three core components:
1. **`ChannelInterface`** - Contract that all channels must implement
2. **`ChannelRegistry`** - Central registry for managing channels
3. **`NotificationManager`** - Sends notifications through registered channels
```mermaid
graph LR
A[NotificationManager] --> B[ChannelRegistry]
B --> C[Email Channel]
B --> D[WhatsApp Channel]
B --> E[SMS Channel]
B --> F[Custom Channel]
```
## Creating a Custom Channel
### Step 1: Implement ChannelInterface
Create a class that implements `WooNooW\Core\Notifications\Channels\ChannelInterface`:
```php
<?php
namespace MyPlugin\Channels;
use WooNooW\Core\Notifications\Channels\ChannelInterface;
class WhatsAppChannel implements ChannelInterface {
public function get_id() {
return 'whatsapp';
}
public function get_label() {
return __('WhatsApp', 'my-plugin');
}
public function is_configured() {
$api_key = get_option('my_whatsapp_api_key');
return !empty($api_key);
}
public function send($event_id, $recipient, $data) {
// Your sending logic here
return [
'success' => true,
'message' => 'Sent successfully'
];
}
public function get_config_fields() {
return [
[
'id' => 'my_whatsapp_api_key',
'label' => 'API Key',
'type' => 'text',
],
];
}
}
```
### Step 2: Register Your Channel
Register your channel with the `ChannelRegistry` during plugin initialization:
```php
use WooNooW\Core\Notifications\ChannelRegistry;
use MyPlugin\Channels\WhatsAppChannel;
add_action('init', function() {
$channel = new WhatsAppChannel();
ChannelRegistry::register($channel);
});
```
### Step 3: Enable in Settings
Once registered, your channel will be available in the notification settings UI, where users can configure which events should use WhatsApp.
## Interface Reference
### get_id()
Returns a unique identifier for your channel.
```php
public function get_id(): string
```
**Example:**
```php
public function get_id() {
return 'whatsapp'; // or 'sms', 'telegram', etc.
}
```
### get_label()
Returns a human-readable label for the admin UI.
```php
public function get_label(): string
```
**Example:**
```php
public function get_label() {
return __('WhatsApp Business', 'my-plugin');
}
```
### is_configured()
Checks if the channel has all required configuration (API keys, credentials, etc.).
```php
public function is_configured(): bool
```
**Example:**
```php
public function is_configured() {
$api_key = get_option('my_whatsapp_api_key');
$phone = get_option('my_whatsapp_phone');
return !empty($api_key) && !empty($phone);
}
```
### send()
Sends a notification through this channel.
```php
public function send(string $event_id, string $recipient, array $data): bool|array
```
**Parameters:**
- `$event_id` - Event identifier (e.g., `'order_completed'`, `'newsletter_confirm'`)
- `$recipient` - Recipient type (`'customer'` or `'staff'`)
- `$data` - Context data including order, user, custom variables
**Returns:**
- `bool` - Simple success/failure
- `array` - Detailed result with `success` and `message` keys
**Example:**
```php
public function send($event_id, $recipient, $data) {
$phone = $this->get_recipient_phone($recipient, $data);
$message = $this->build_message($event_id, $data);
$response = wp_remote_post('https://api.provider.com/send', [
'body' => [
'to' => $phone,
'message' => $message,
'api_key' => get_option('my_api_key'),
],
]);
if (is_wp_error($response)) {
return [
'success' => false,
'message' => $response->get_error_message(),
];
}
return ['success' => true];
}
```
### get_config_fields()
Returns configuration fields for the admin settings UI (optional).
```php
public function get_config_fields(): array
```
**Field Structure:**
```php
[
'id' => 'option_name',
'label' => 'Field Label',
'type' => 'text|select|textarea',
'description' => 'Help text',
'options' => [], // For select fields
'default' => 'value',
]
```
## Complete Example: WhatsApp Channel
See the reference implementation:
[WhatsAppChannel.example.php](file:///Users/dwindown/Local%20Sites/woonoow/app/public/wp-content/plugins/woonoow/includes/Core/Notifications/Channels/WhatsAppChannel.example.php)
This example includes:
- ✅ Twilio API integration
- ✅ Phone number extraction from orders/users
- ✅ Message templates for common events
- ✅ Configuration fields for admin settings
## Message Customization
Use filters to customize messages for specific events:
```php
add_filter('woonoow_whatsapp_message_order_completed', function($message, $data) {
if (isset($data['order'])) {
$order = $data['order'];
return sprintf(
"🎉 Order #%s confirmed! Track here: %s",
$order->get_order_number(),
$order->get_view_order_url()
);
}
return $message;
}, 10, 2);
```
## Available Events
Your channel can handle any registered notification event:
| Event | Recipient | Data Available |
|-------|-----------|----------------|
| `order_completed` | customer | `order`, `user_id` |
| `order_cancelled` | customer | `order`, `user_id` |
| `newsletter_confirm` | customer | `email`, `confirmation_url` |
| `newsletter_welcome` | customer | `email`, `user_id` |
| `subscription_expiring` | customer | `subscription`, `user_id` |
See [Event Registry](/hooks/notifications#event-registry) for the complete list.
## Testing Your Channel
```php
// Manual test
use WooNooW\Core\Notifications\NotificationManager;
NotificationManager::send('order_completed', 'whatsapp', [
'order' => wc_get_order(123),
'user_id' => 1,
]);
```
## Best Practices
1. **Validate Configuration**: Always check `is_configured()` before attempting to send
2. **Handle Errors Gracefully**: Return detailed error messages for debugging
3. **Log Send Attempts**: Use `do_action()` for tracking/analytics
4. **Support Filtering**: Allow message customization via filters
5. **Rate Limiting**: Consider implementing rate limiting for API calls
## Hooks
### Registration Hook
```php
// Register channels during init
add_action('init', function() {
ChannelRegistry::register(new MyChannel());
});
```
### Custom Hooks in Your Channel
```php
// Allow logging/tracking
do_action('my_channel_sent', $event_id, $recipient, $result);
// Allow message customization
$message = apply_filters(
"my_channel_message_{$event_id}",
$default_message,
$data
);
```
## Troubleshooting
**Channel not appearing in settings?**
- Ensure `ChannelRegistry::register()` is called during `init`
- Check that `get_id()` returns a unique string
- Verify `is_configured()` returns `true`
**Messages not sending?**
- Check notification settings: Marketing > Notifications
- Verify the event has your channel enabled
- Enable debug mode and check logs
- Test `is_configured()` returns true
**API errors?**
- Validate API credentials in settings
- Check API provider status/quotas
- Review error logs for API responses
## Related Documentation
- [Notification System](/core-concepts/notifications)
- [Event Registry](/hooks/notifications#event-registry)
- [Notification Hooks](/hooks/notifications)

View File

@@ -0,0 +1,243 @@
---
title: Module Registry
description: Register custom modules and addons with WooNooW's unified module system
---
WooNooW's modular architecture allows developers to create custom modules and addons that integrate seamlessly with the **Settings > Modules** UI.
## Overview
The Module Registry provides a unified system for:
- Enable/disable toggle in the admin UI
- Custom settings with schema-based forms
- Dependencies on other modules
- SPA route registration for custom settings pages
## Registering a Module
Add your module using the `woonoow/modules/registry` filter:
```php
add_filter('woonoow/modules/registry', 'my_plugin_register_module');
function my_plugin_register_module($modules) {
$modules['my-module'] = [
'id' => 'my-module',
'name' => __('My Custom Module', 'my-plugin'),
'description' => __('Description of what this module does.', 'my-plugin'),
'icon' => 'Sparkles', // Lucide icon name
'category' => 'marketing', // marketing, sales, developer, etc.
'requires' => [], // Array of required module IDs
'settings' => [], // Settings schema array
];
return $modules;
}
```
## Module Properties
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `id` | string | Yes | Unique identifier (lowercase, hyphens) |
| `name` | string | Yes | Display name in the UI |
| `description` | string | Yes | Brief description of functionality |
| `icon` | string | No | Lucide icon name (e.g., `Package`, `Mail`) |
| `category` | string | No | Grouping category: `marketing`, `sales`, `developer` |
| `requires` | array | No | Array of module IDs this depends on |
| `settings` | array | No | Settings schema for module configuration |
---
## Settings Schema
Define a settings schema to allow users to configure your module:
```php
'settings' => [
[
'id' => 'api_key',
'type' => 'text',
'label' => __('API Key', 'my-plugin'),
'description' => __('Enter your API key', 'my-plugin'),
'default' => '',
],
[
'id' => 'rate_limit',
'type' => 'number',
'label' => __('Rate Limit', 'my-plugin'),
'description' => __('Max requests per minute', 'my-plugin'),
'default' => 10,
'min' => 1,
'max' => 100,
],
[
'id' => 'enable_debug',
'type' => 'toggle',
'label' => __('Enable Debug Mode', 'my-plugin'),
'default' => false,
],
],
```
### Available Field Types
| Type | Description | Properties |
|------|-------------|------------|
| `text` | Single-line text input | `default`, `placeholder` |
| `textarea` | Multi-line text input | `default`, `rows` |
| `number` | Numeric input with validation | `default`, `min`, `max`, `step` |
| `toggle` | Boolean on/off switch | `default` (true/false) |
| `select` | Dropdown selection | `default`, `options` (array of `{value, label}`) |
| `checkbox` | Single checkbox | `default` (true/false) |
| `color` | Color picker | `default` (#hex value) |
| `url` | URL input with validation | `default`, `placeholder` |
| `email` | Email input with validation | `default`, `placeholder` |
| `password` | Password input (masked) | `default` |
### Common Field Properties
| Property | Type | Description |
|----------|------|-------------|
| `id` | string | Unique field identifier |
| `type` | string | Field type from list above |
| `label` | string | Display label |
| `description` | string | Help text below field |
| `default` | mixed | Default value |
### Select Field Example
```php
[
'id' => 'display_mode',
'type' => 'select',
'label' => __('Display Mode', 'my-plugin'),
'default' => 'grid',
'options' => [
['value' => 'grid', 'label' => __('Grid', 'my-plugin')],
['value' => 'list', 'label' => __('List', 'my-plugin')],
['value' => 'carousel', 'label' => __('Carousel', 'my-plugin')],
],
],
```
---
## Checking Module Status
Use `ModuleRegistry::is_enabled()` to check if your module is active:
```php
use WooNooW\Core\ModuleRegistry;
if (ModuleRegistry::is_enabled('my-module')) {
// Module is enabled, initialize features
My_Module_Manager::init();
}
```
## Module Lifecycle Events
Hook into module enable/disable events:
```php
// When any module is enabled
add_action('woonoow/module/enabled', function($module_id) {
if ($module_id === 'my-module') {
// Create database tables, initialize settings, etc.
My_Module_Manager::install();
}
});
// When any module is disabled
add_action('woonoow/module/disabled', function($module_id) {
if ($module_id === 'my-module') {
// Cleanup if necessary
}
});
```
---
## SPA Routes for Settings Pages
Addons can register custom React settings pages:
```php
add_filter('woonoow/spa_routes', function($routes) {
$routes[] = [
'path' => '/settings/my-addon',
'component_url' => plugin_dir_url(__FILE__) . 'dist/Settings.js',
'title' => 'My Addon Settings',
];
return $routes;
});
```
## Addon Registry (External Addons)
External addons can also register via `woonoow/addon_registry` for extended metadata:
```php
add_filter('woonoow/addon_registry', function($addons) {
$addons['my-shipping-addon'] = [
'id' => 'my-shipping-addon',
'name' => 'My Shipping',
'description' => 'Custom shipping integration',
'version' => '1.0.0',
'author' => 'My Company',
'category' => 'shipping',
'icon' => 'truck',
'settings_url' => '/settings/shipping/my-addon',
'spa_bundle' => plugin_dir_url(__FILE__) . 'dist/addon.js',
];
return $addons;
});
```
---
## Complete Example
```php
<?php
namespace MyPlugin;
class MyModuleSettings {
public static function init() {
add_filter('woonoow/modules/registry', [__CLASS__, 'register']);
}
public static function register($modules) {
$modules['my-module'] = [
'id' => 'my-module',
'name' => __('My Module', 'my-plugin'),
'description' => __('Adds awesome features to your store.', 'my-plugin'),
'icon' => 'Zap',
'category' => 'marketing',
'requires' => [], // No dependencies
'settings' => [
[
'id' => 'feature_enabled',
'type' => 'toggle',
'label' => __('Enable Feature', 'my-plugin'),
'default' => true,
],
],
];
return $modules;
}
}
// Initialize on plugins_loaded
add_action('plugins_loaded', ['MyPlugin\MyModuleSettings', 'init']);
```
## Best Practices
1. **Check module status** before loading heavy features
2. **Use the `woonoow/module/enabled` hook** to run installation routines only when needed
3. **Specify dependencies** so WooNooW can prompt users to enable required modules
4. **Provide meaningful descriptions** to help users understand what each module does

View File

@@ -0,0 +1,103 @@
---
title: Custom Page Templates
description: Learn how to register custom starting templates for the Page Editor.
---
WooNooW allows developers to register custom page templates. These templates appear in the "Create New Page" modal, allowing users to start with a pre-configured layout instead of a blank page.
## Registering a Template
To add a new template, use the `woonoow_page_templates` filter. You should append your template definition to the existing array.
```php
add_filter('woonoow_page_templates', function($templates) {
$templates[] = [
'id' => 'my-custom-landing',
'label' => 'Product Launch',
'description' => 'A structured page for new product announcements.',
'icon' => 'rocket', // Lucide icon name (lowercase)
'sections' => [
// Section definitions...
]
];
return $templates;
});
```
## Template Structure
Each template requires the following properties:
| Property | Type | Description |
| :--- | :--- | :--- |
| `id` | string | Unique identifier for the template. |
| `label` | string | Display name shown in the modal. |
| `description` | string | Short description of the template's purpose. |
| `icon` | string | Name of a Lucide icon (e.g., `layout`, `users`, `rocket`). |
| `sections` | array | Array of section objects defining the initial layout. |
### Defining Sections
Sections are the building blocks of a page. Each section object mimics the structure stored in the database.
```php
[
'id' => uniqid('section_'), // Must be unique
'type' => 'hero', // Component type (hero, feature-grid, image-text, etc.)
'props' => [
'title' => ['type' => 'static', 'value' => 'Hello World'],
'subtitle' => ['type' => 'static', 'value' => 'This is a template.'],
],
'styles' => [
'contentWidth' => 'contained', // 'full' or 'contained'
'padding' => 'medium',
]
]
```
## Example: Full Configuration
Here is a complete example that registers a "Support Page" template with a Hero and a FAQ section.
```php
add_filter('woonoow_page_templates', function($templates) {
$templates[] = [
'id' => 'support-page',
'label' => 'Support Center',
'description' => 'Help desk layout with search and FAQ grid.',
'icon' => 'help-circle',
'sections' => [
// Hero Section
[
'id' => uniqid('section_'),
'type' => 'hero',
'props' => [
'title' => ['type' => 'static', 'value' => 'How can we help?'],
'cta_text' => ['type' => 'static', 'value' => 'Contact Support'],
'cta_url' => ['type' => 'static', 'value' => '/contact'],
],
'styles' => ['contentWidth' => 'full']
],
// Content Section
[
'id' => uniqid('section_'),
'type' => 'content',
'props' => [
'content' => ['type' => 'static', 'value' => '<h2>Frequently Asked Questions</h2><p>Find answers below.</p>']
],
'styles' => ['contentWidth' => 'contained']
]
]
];
return $templates;
});
```
## Available Section Types
Common available section types include:
- `hero`: Large banner with title, subtitle, and CTA.
- `content`: Rich text editor content.
- `image-text`: Split layout with image and text.
- `feature-grid`: Grid of icons and text.
- `cta-banner`: Call to action strip.

View File

@@ -0,0 +1,79 @@
---
title: React Integration
description: Using React within WooNooW Addons
date: 2024-01-31
---
## The Challenge
External addons cannot bundle React because WooNooW already ships with a React runtime. Bundling it again would cause conflicts and bloat.
## Solution: Exposed Runtime
WooNooW exposes its React instance and Component library on the `window` object.
### Core Setup (How it works internally)
```typescript
// WooNooW Core
window.WooNooW = {
React: React,
ReactDOM: ReactDOM,
components: {
Button: Button,
Input: Input,
Select: Select,
},
hooks: { addFilter, addAction }
};
```
### Addon implementation
#### Level 1: Vanilla JS (No Build)
Good for simple injections.
```javascript
(function() {
window.addEventListener('woonoow:loaded', function() {
window.WooNooW.addFilter('woonoow_order_form_after_shipping', function(container) {
// Manual DOM manipulation
return container;
});
});
})();
```
#### Level 2: React with Build (Recommended)
Use `vite` or `webpack` and configure React as an **external**.
**vite.config.js**
```javascript
export default {
build: {
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'window.WooNooW.React',
'react-dom': 'window.WooNooW.ReactDOM'
}
}
}
}
};
```
**Index.tsx**
```typescript
const { React, hooks, components } = window.WooNooW;
const { Button } = components;
function MyAddonComponent() {
return <Button>Click Me</Button>;
}
```