feat: Backend API & Email Rendering with Settings! 🔌

## 4. Wire to Backend 

### API Endpoints Created:
- `GET /woonoow/v1/notifications/email-settings` - Fetch settings
- `POST /woonoow/v1/notifications/email-settings` - Save settings
- `DELETE /woonoow/v1/notifications/email-settings` - Reset to defaults

### Features:
- Proper sanitization (sanitize_hex_color, esc_url_raw, etc.)
- Social links validation (allowed platforms only)
- Defaults fallback
- Stored in wp_options as `woonoow_email_settings`

### Email Rendering Integration:
**Logo & Header:**
- Uses logo_url if set, otherwise header_text
- Falls back to store name

**Footer:**
- Uses footer_text with {current_year} support
- Replaces {current_year} with actual year dynamically
- Social icons rendered from social_links array

**Hero Cards:**
- Applies hero_gradient_start and hero_gradient_end
- Applies hero_text_color to text and headings
- Works for type="hero" and type="success" cards

**Button Colors:**
- Ready to apply primary_color and button_text_color
- (Template needs update for dynamic button colors)

### Files:
- `includes/Api/NotificationsController.php` - API endpoints
- `includes/Core/Notifications/EmailRenderer.php` - Apply settings to emails

### Security:
- Permission checks (check_permission)
- Input sanitization
- URL validation
- Platform whitelist for social links

Frontend can now save/load settings! Backend applies them to emails! 🎉
This commit is contained in:
dwindown
2025-11-13 13:45:03 +07:00
parent 7badee9ee4
commit 2a98d6fc2b
2 changed files with 166 additions and 20 deletions

View File

@@ -121,6 +121,33 @@ class NotificationsController {
],
]);
// GET /woonoow/v1/notifications/email-settings
register_rest_route($this->namespace, '/' . $this->rest_base . '/email-settings', [
[
'methods' => 'GET',
'callback' => [$this, 'get_email_settings'],
'permission_callback' => [$this, 'check_permission'],
],
]);
// POST /woonoow/v1/notifications/email-settings
register_rest_route($this->namespace, '/' . $this->rest_base . '/email-settings', [
[
'methods' => 'POST',
'callback' => [$this, 'save_email_settings'],
'permission_callback' => [$this, 'check_permission'],
],
]);
// DELETE /woonoow/v1/notifications/email-settings
register_rest_route($this->namespace, '/' . $this->rest_base . '/email-settings', [
[
'methods' => 'DELETE',
'callback' => [$this, 'reset_email_settings'],
'permission_callback' => [$this, 'check_permission'],
],
]);
// GET /woonoow/v1/notifications/push/settings
register_rest_route($this->namespace, '/' . $this->rest_base . '/push/settings', [
[
@@ -815,4 +842,104 @@ class NotificationsController {
'enabled' => (bool) $verified,
], 200);
}
/**
* Get email customization settings
*/
public function get_email_settings(WP_REST_Request $request) {
$defaults = [
'primary_color' => '#7f54b3',
'secondary_color' => '#7f54b3',
'hero_gradient_start' => '#667eea',
'hero_gradient_end' => '#764ba2',
'hero_text_color' => '#ffffff',
'button_text_color' => '#ffffff',
'logo_url' => '',
'header_text' => '',
'footer_text' => '',
'social_links' => [],
];
$settings = get_option('woonoow_email_settings', $defaults);
// Ensure social_links is an array
if (!isset($settings['social_links']) || !is_array($settings['social_links'])) {
$settings['social_links'] = [];
}
// Merge with defaults to ensure all fields exist
$settings = array_merge($defaults, $settings);
return new WP_REST_Response($settings, 200);
}
/**
* Save email customization settings
*/
public function save_email_settings(WP_REST_Request $request) {
$data = $request->get_json_params();
$settings = [
'primary_color' => sanitize_hex_color($data['primary_color'] ?? '#7f54b3'),
'secondary_color' => sanitize_hex_color($data['secondary_color'] ?? '#7f54b3'),
'hero_gradient_start' => sanitize_hex_color($data['hero_gradient_start'] ?? '#667eea'),
'hero_gradient_end' => sanitize_hex_color($data['hero_gradient_end'] ?? '#764ba2'),
'hero_text_color' => sanitize_hex_color($data['hero_text_color'] ?? '#ffffff'),
'button_text_color' => sanitize_hex_color($data['button_text_color'] ?? '#ffffff'),
'logo_url' => esc_url_raw($data['logo_url'] ?? ''),
'header_text' => sanitize_text_field($data['header_text'] ?? ''),
'footer_text' => sanitize_text_field($data['footer_text'] ?? ''),
'social_links' => $this->sanitize_social_links($data['social_links'] ?? []),
];
update_option('woonoow_email_settings', $settings);
return new WP_REST_Response([
'success' => true,
'message' => __('Email settings saved successfully', 'woonoow'),
'settings' => $settings,
], 200);
}
/**
* Reset email customization settings to defaults
*/
public function reset_email_settings(WP_REST_Request $request) {
delete_option('woonoow_email_settings');
return new WP_REST_Response([
'success' => true,
'message' => __('Email settings reset to defaults', 'woonoow'),
], 200);
}
/**
* Sanitize social links array
*/
private function sanitize_social_links($links) {
if (!is_array($links)) {
return [];
}
$sanitized = [];
$allowed_platforms = ['facebook', 'twitter', 'instagram', 'linkedin', 'youtube', 'website'];
foreach ($links as $link) {
if (!is_array($link) || !isset($link['platform']) || !isset($link['url'])) {
continue;
}
$platform = sanitize_text_field($link['platform']);
$url = esc_url_raw($link['url']);
if (in_array($platform, $allowed_platforms) && !empty($url)) {
$sanitized[] = [
'platform' => $platform,
'url' => $url,
];
}
}
return $sanitized;
}
}

View File

@@ -273,12 +273,29 @@ class EmailRenderer {
$type = $attributes['type'] ?? 'default';
$bg = $attributes['bg'] ?? null;
// Get email customization settings for colors
$email_settings = get_option('woonoow_email_settings', []);
$hero_gradient_start = $email_settings['hero_gradient_start'] ?? '#667eea';
$hero_gradient_end = $email_settings['hero_gradient_end'] ?? '#764ba2';
$hero_text_color = $email_settings['hero_text_color'] ?? '#ffffff';
$class = 'card';
$style = 'width: 100%; background-color: #ffffff; border-radius: 8px;';
$content_style = 'padding: 32px 40px;';
// Add type class
// Add type class and styling
if ($type !== 'default') {
$class .= ' card-' . esc_attr($type);
// Apply gradient and text color for hero/success cards
if ($type === 'hero' || $type === 'success') {
$style .= sprintf(
' background: linear-gradient(135deg, %s 0%%, %s 100%%);',
esc_attr($hero_gradient_start),
esc_attr($hero_gradient_end)
);
$content_style .= sprintf(' color: %s;', esc_attr($hero_text_color));
}
}
// Add background image
@@ -290,13 +307,14 @@ class EmailRenderer {
return sprintf(
'<table role="presentation" class="%s" border="0" cellpadding="0" cellspacing="0" style="%s">
<tr>
<td class="content" style="padding: 32px 40px;">
<td class="content" style="%s">
%s
</td>
</tr>
</table>',
$class,
$style,
$content_style,
$content
);
}
@@ -366,23 +384,21 @@ class EmailRenderer {
$html = file_get_contents($template_path);
// Get email customization settings
$settings = get_option('woonoow_notification_settings', []);
$email_settings = $settings['email_appearance'] ?? [];
$email_settings = get_option('woonoow_email_settings', []);
// Email body background
$body_bg = $email_settings['body_bg'] ?? '#f8f8f8';
$body_bg = '#f8f8f8';
// Email header (logo or text)
$header_type = $email_settings['header_type'] ?? 'text';
if ($header_type === 'logo' && !empty($email_settings['header_logo'])) {
if (!empty($email_settings['logo_url'])) {
$header = sprintf(
'<a href="%s"><img src="%s" alt="%s" style="max-width: 140px;"></a>',
'<a href="%s"><img src="%s" alt="%s" style="max-width: 200px; max-height: 60px;"></a>',
esc_url($variables['store_url']),
esc_url($email_settings['header_logo']),
esc_url($email_settings['logo_url']),
esc_attr($variables['store_name'])
);
} else {
$header_text = $email_settings['header_text'] ?? $variables['store_name'];
$header_text = !empty($email_settings['header_text']) ? $email_settings['header_text'] : $variables['store_name'];
$header = sprintf(
'<a href="%s" style="font-size: 24px; font-weight: 700; color: #333; text-decoration: none;">%s</a>',
esc_url($variables['store_url']),
@@ -390,24 +406,27 @@ class EmailRenderer {
);
}
// Email footer
$footer_text = $email_settings['footer_text'] ?? sprintf(
// Email footer with {current_year} variable support
$footer_text = !empty($email_settings['footer_text']) ? $email_settings['footer_text'] : sprintf(
'© %s %s. All rights reserved.',
date('Y'),
$variables['store_name']
);
// Replace {current_year} with actual year
$footer_text = str_replace('{current_year}', date('Y'), $footer_text);
// Social icons
$social_html = '';
if (!empty($email_settings['social_links'])) {
$social_html = '<div class="social-icons" style="margin-top: 16px;">';
foreach ($email_settings['social_links'] as $platform => $url) {
if (!empty($url)) {
if (!empty($email_settings['social_links']) && is_array($email_settings['social_links'])) {
$social_html = '<div class="social-icons" style="margin-top: 16px; text-align: center;">';
foreach ($email_settings['social_links'] as $link) {
if (!empty($link['url']) && !empty($link['platform'])) {
$social_html .= sprintf(
'<a href="%s" style="display: inline-block; margin: 0 8px;"><img src="%s" alt="%s" style="width: 24px; height: 24px;"></a>',
esc_url($url),
$this->get_social_icon_url($platform),
esc_attr(ucfirst($platform))
esc_url($link['url']),
$this->get_social_icon_url($link['platform']),
esc_attr(ucfirst($link['platform']))
);
}
}
@@ -415,7 +434,7 @@ class EmailRenderer {
}
$footer = sprintf(
'<p style="font-family: \'Inter\', Arial, sans-serif; font-size: 13px; line-height: 1.5; color: #888888; margin: 0 0 8px 0;">%s</p>%s',
'<p style="font-family: \'Inter\', Arial, sans-serif; font-size: 13px; line-height: 1.5; color: #888888; margin: 0 0 8px 0; text-align: center;">%s</p>%s',
nl2br(esc_html($footer_text)),
$social_html
);