feat: Newsletter system improvements and validation framework

- Fix: Marketing events now display in Staff notifications tab
- Reorganize: Move Coupons to Marketing/Coupons for better organization
- Add: Comprehensive email/phone validation with extensible filter hooks
  - Email validation with regex pattern (xxxx@xxxx.xx)
  - Phone validation with WhatsApp verification support
  - Filter hooks for external API integration (QuickEmailVerification, etc.)
- Fix: Newsletter template routes now use centralized notification email builder
- Add: Validation.php class for reusable validation logic
- Add: VALIDATION_HOOKS.md documentation with integration examples
- Add: NEWSLETTER_CAMPAIGN_PLAN.md architecture for future campaign system
- Fix: API delete method call in Newsletter.tsx (delete -> del)
- Remove: Duplicate EmailTemplates.tsx (using notification system instead)
- Update: Newsletter controller to use centralized Validation class

Breaking changes:
- Coupons routes moved from /routes/Coupons to /routes/Marketing/Coupons
- Legacy /coupons routes maintained for backward compatibility
This commit is contained in:
Dwindi Ramadhana
2025-12-26 10:59:48 +07:00
parent 0b08ddefa1
commit 0b2c8a56d6
23 changed files with 1132 additions and 232 deletions

View File

@@ -0,0 +1,218 @@
<?php
namespace WooNooW\Core;
use WP_Error;
/**
* Validation utilities for WooNooW
*
* Provides extensible validation for emails, phone numbers, and other data types
* with filter hooks for external API integration.
*
* @package WooNooW\Core
* @since 1.0.0
*/
class Validation {
/**
* Validate email address with extensible filter hooks
*
* @param string $email Email address to validate
* @param string $context Context of validation (e.g., 'newsletter_subscribe', 'checkout', 'registration')
* @return true|WP_Error True if valid, WP_Error if invalid
*/
public static function validate_email($email, $context = 'general') {
$email = sanitize_email($email);
// Basic format validation
if (!is_email($email)) {
return new WP_Error('invalid_email', __('Invalid email address', 'woonoow'), ['status' => 400]);
}
// Enhanced email validation with regex pattern (xxxx@xxxx.xx)
if (!preg_match('/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/', $email)) {
return new WP_Error('invalid_email_format', __('Email must be in format: xxxx@xxxx.xx', 'woonoow'), ['status' => 400]);
}
/**
* Filter to validate email address.
*
* Allows addons to extend validation using external APIs like quickemailverification.com
*
* @param bool|WP_Error $is_valid True if valid, WP_Error if invalid
* @param string $email The email address to validate
* @param string $context The context of validation
*
* @since 1.0.0
*
* Example usage in addon:
* ```php
* add_filter('woonoow/validate_email', function($is_valid, $email, $context) {
* if ($context !== 'newsletter_subscribe') return $is_valid;
*
* // Call external API (QuickEmailVerification)
* $api_key = get_option('woonoow_quickemail_api_key');
* if (!$api_key) return $is_valid;
*
* $response = wp_remote_get("https://api.quickemailverification.com/v1/verify?email={$email}&apikey={$api_key}");
*
* if (is_wp_error($response)) {
* return $is_valid; // Fallback to basic validation on API error
* }
*
* $data = json_decode(wp_remote_retrieve_body($response), true);
*
* if (isset($data['result']) && $data['result'] !== 'valid') {
* return new WP_Error('email_verification_failed', 'Email address could not be verified: ' . ($data['reason'] ?? 'Unknown'));
* }
*
* return true;
* }, 10, 3);
* ```
*/
$email_validation = apply_filters('woonoow/validate_email', true, $email, $context);
if (is_wp_error($email_validation)) {
return $email_validation;
}
if ($email_validation !== true) {
return new WP_Error('email_validation_failed', __('Email validation failed', 'woonoow'), ['status' => 400]);
}
return true;
}
/**
* Validate phone number with extensible filter hooks
*
* @param string $phone Phone number to validate
* @param string $context Context of validation (e.g., 'checkout', 'registration', 'shipping')
* @param string $country_code Optional country code (e.g., 'ID', 'US')
* @return true|WP_Error True if valid, WP_Error if invalid
*/
public static function validate_phone($phone, $context = 'general', $country_code = '') {
$phone = sanitize_text_field($phone);
// Remove common formatting characters
$clean_phone = preg_replace('/[\s\-\(\)\.]+/', '', $phone);
// Basic validation: must contain only digits, +, and be at least 8 characters
if (!preg_match('/^\+?[0-9]{8,15}$/', $clean_phone)) {
return new WP_Error('invalid_phone', __('Phone number must be 8-15 digits and may start with +', 'woonoow'), ['status' => 400]);
}
/**
* Filter to validate phone number.
*
* Allows addons to extend validation using external APIs or WhatsApp verification
*
* @param bool|WP_Error $is_valid True if valid, WP_Error if invalid
* @param string $phone The phone number to validate (cleaned)
* @param string $context The context of validation
* @param string $country_code Country code if available
*
* @since 1.0.0
*
* Example usage for WhatsApp verification:
* ```php
* add_filter('woonoow/validate_phone', function($is_valid, $phone, $context, $country_code) {
* if ($context !== 'checkout') return $is_valid;
*
* // Check if number is registered on WhatsApp
* $api_key = get_option('woonoow_whatsapp_verify_api_key');
* if (!$api_key) return $is_valid;
*
* $response = wp_remote_post('https://api.whatsapp.com/v1/contacts', [
* 'headers' => ['Authorization' => 'Bearer ' . $api_key],
* 'body' => json_encode(['blocking' => 'wait', 'contacts' => [$phone]]),
* ]);
*
* if (is_wp_error($response)) {
* return $is_valid; // Fallback on API error
* }
*
* $data = json_decode(wp_remote_retrieve_body($response), true);
*
* if (!isset($data['contacts'][0]['wa_id'])) {
* return new WP_Error('phone_not_whatsapp', 'Phone number is not registered on WhatsApp');
* }
*
* return true;
* }, 10, 4);
* ```
*
* Example usage for general phone validation API:
* ```php
* add_filter('woonoow/validate_phone', function($is_valid, $phone, $context, $country_code) {
* // Use numverify.com or similar service
* $api_key = get_option('woonoow_numverify_api_key');
* if (!$api_key) return $is_valid;
*
* $response = wp_remote_get("http://apilayer.net/api/validate?access_key={$api_key}&number={$phone}&country_code={$country_code}");
*
* if (is_wp_error($response)) return $is_valid;
*
* $data = json_decode(wp_remote_retrieve_body($response), true);
*
* if (!$data['valid']) {
* return new WP_Error('phone_invalid', 'Phone number validation failed: ' . ($data['error'] ?? 'Invalid number'));
* }
*
* return true;
* }, 10, 4);
* ```
*/
$phone_validation = apply_filters('woonoow/validate_phone', true, $clean_phone, $context, $country_code);
if (is_wp_error($phone_validation)) {
return $phone_validation;
}
if ($phone_validation !== true) {
return new WP_Error('phone_validation_failed', __('Phone number validation failed', 'woonoow'), ['status' => 400]);
}
return true;
}
/**
* Validate phone number and check WhatsApp registration
*
* Convenience method that validates phone and checks WhatsApp in one call
*
* @param string $phone Phone number to validate
* @param string $context Context of validation
* @param string $country_code Optional country code
* @return true|WP_Error True if valid and registered on WhatsApp, WP_Error otherwise
*/
public static function validate_phone_whatsapp($phone, $context = 'general', $country_code = '') {
// First validate the phone number format
$validation = self::validate_phone($phone, $context, $country_code);
if (is_wp_error($validation)) {
return $validation;
}
// Clean phone for WhatsApp check
$clean_phone = preg_replace('/[\s\-\(\)\.]+/', '', $phone);
/**
* Filter to check if phone is registered on WhatsApp
*
* @param bool|WP_Error $is_registered True if registered, WP_Error if not or error
* @param string $phone The phone number (cleaned)
* @param string $context The context of validation
* @param string $country_code Country code if available
*
* @since 1.0.0
*/
$whatsapp_check = apply_filters('woonoow/validate_phone_whatsapp', true, $clean_phone, $context, $country_code);
if (is_wp_error($whatsapp_check)) {
return $whatsapp_check;
}
return true;
}
}