- {enabledChannels
- .filter((c: NotificationChannel) => !c.builtin)
- .map((channel: NotificationChannel) => (
-
-
-
{getChannelIcon(channel.id)}
-
+
+ {allEvents.map((event: any) => {
+ const templateKey = `${event.id}_${channel.id}`;
+ const hasCustomTemplate = templates && templates[templateKey];
+
+ return (
+
+
+
{getChannelIcon(channel.id)}
+
-
{channel.label} {__('Templates')}
-
- {__('Addon')}
-
+ {event.label}
+ {hasCustomTemplate && (
+
+ {__('Custom')}
+
+ )}
+ {channel.builtin && (
+
+ {__('Built-in')}
+
+ )}
-
- {__('Customize message templates for')} {channel.label} {__('notifications')}
-
+
{event.description}
-
-
-
-
+
- ))}
+ );
+ })}
- ) : (
-
-
-
-
- {__(
- 'Install notification addons like WhatsApp, Telegram, or SMS to customize their message templates.'
- )}
-
-
-
-
- )}
+ ))}
+
{/* Template Variables Reference */}
+ {/* Template Editor Dialog */}
+ {selectedEvent && selectedChannel && (
+
{
+ setEditorOpen(false);
+ setSelectedEvent(null);
+ setSelectedChannel(null);
+ setSelectedTemplate(null);
+ }}
+ eventId={selectedEvent.id}
+ channelId={selectedChannel.id}
+ eventLabel={selectedEvent.label}
+ channelLabel={selectedChannel.label}
+ template={selectedTemplate}
+ />
+ )}
);
}
diff --git a/includes/Api/NotificationsController.php b/includes/Api/NotificationsController.php
index d3e9169..dbee244 100644
--- a/includes/Api/NotificationsController.php
+++ b/includes/Api/NotificationsController.php
@@ -12,6 +12,8 @@ namespace WooNooW\Api;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
+use WooNooW\Core\Notifications\TemplateProvider;
+use WooNooW\Core\Notifications\PushNotificationHandler;
class NotificationsController {
@@ -55,6 +57,69 @@ class NotificationsController {
'permission_callback' => [$this, 'check_permission'],
],
]);
+
+ // GET /woonoow/v1/notifications/templates
+ register_rest_route($this->namespace, '/' . $this->rest_base . '/templates', [
+ [
+ 'methods' => 'GET',
+ 'callback' => [$this, 'get_templates'],
+ 'permission_callback' => [$this, 'check_permission'],
+ ],
+ ]);
+
+ // GET /woonoow/v1/notifications/templates/:eventId/:channelId
+ register_rest_route($this->namespace, '/' . $this->rest_base . '/templates/(?P
[a-zA-Z0-9_-]+)/(?P[a-zA-Z0-9_-]+)', [
+ [
+ 'methods' => 'GET',
+ 'callback' => [$this, 'get_template'],
+ 'permission_callback' => [$this, 'check_permission'],
+ ],
+ ]);
+
+ // POST /woonoow/v1/notifications/templates
+ register_rest_route($this->namespace, '/' . $this->rest_base . '/templates', [
+ [
+ 'methods' => 'POST',
+ 'callback' => [$this, 'save_template'],
+ 'permission_callback' => [$this, 'check_permission'],
+ ],
+ ]);
+
+ // DELETE /woonoow/v1/notifications/templates/:eventId/:channelId
+ register_rest_route($this->namespace, '/' . $this->rest_base . '/templates/(?P[a-zA-Z0-9_-]+)/(?P[a-zA-Z0-9_-]+)', [
+ [
+ 'methods' => 'DELETE',
+ 'callback' => [$this, 'delete_template'],
+ 'permission_callback' => [$this, 'check_permission'],
+ ],
+ ]);
+
+ // GET /woonoow/v1/notifications/push/vapid-key
+ register_rest_route($this->namespace, '/' . $this->rest_base . '/push/vapid-key', [
+ [
+ 'methods' => 'GET',
+ 'callback' => [$this, 'get_vapid_key'],
+ 'permission_callback' => '__return_true',
+ ],
+ ]);
+
+ // POST /woonoow/v1/notifications/push/subscribe
+ register_rest_route($this->namespace, '/' . $this->rest_base . '/push/subscribe', [
+ [
+ 'methods' => 'POST',
+ 'callback' => [$this, 'push_subscribe'],
+ 'permission_callback' => '__return_true',
+ ],
+ ]);
+
+ // POST /woonoow/v1/notifications/push/unsubscribe
+ register_rest_route($this->namespace, '/' . $this->rest_base . '/push/unsubscribe', [
+ [
+ 'methods' => 'POST',
+ 'callback' => [$this, 'push_unsubscribe'],
+ 'permission_callback' => '__return_true',
+ ],
+ ]);
}
/**
@@ -72,6 +137,13 @@ class NotificationsController {
'enabled' => true,
'builtin' => true,
],
+ [
+ 'id' => 'push',
+ 'label' => __('Push Notifications', 'woonoow'),
+ 'icon' => 'bell',
+ 'enabled' => true,
+ 'builtin' => true,
+ ],
];
// Allow addons to register their channels
@@ -240,4 +312,160 @@ class NotificationsController {
public function check_permission() {
return current_user_can('manage_woocommerce') || current_user_can('manage_options');
}
+
+ /**
+ * Get all templates
+ *
+ * @param WP_REST_Request $request Request object
+ * @return WP_REST_Response
+ */
+ public function get_templates(WP_REST_Request $request) {
+ $templates = TemplateProvider::get_templates();
+ return new WP_REST_Response($templates, 200);
+ }
+
+ /**
+ * Get single template
+ *
+ * @param WP_REST_Request $request Request object
+ * @return WP_REST_Response|WP_Error
+ */
+ public function get_template(WP_REST_Request $request) {
+ $event_id = $request->get_param('eventId');
+ $channel_id = $request->get_param('channelId');
+
+ $template = TemplateProvider::get_template($event_id, $channel_id);
+
+ if (!$template) {
+ return new WP_Error(
+ 'template_not_found',
+ __('Template not found', 'woonoow'),
+ ['status' => 404]
+ );
+ }
+
+ return new WP_REST_Response($template, 200);
+ }
+
+ /**
+ * Save template
+ *
+ * @param WP_REST_Request $request Request object
+ * @return WP_REST_Response|WP_Error
+ */
+ public function save_template(WP_REST_Request $request) {
+ $event_id = $request->get_param('eventId');
+ $channel_id = $request->get_param('channelId');
+ $subject = $request->get_param('subject');
+ $body = $request->get_param('body');
+
+ if (empty($event_id) || empty($channel_id)) {
+ return new WP_Error(
+ 'invalid_params',
+ __('Event ID and Channel ID are required', 'woonoow'),
+ ['status' => 400]
+ );
+ }
+
+ $success = TemplateProvider::save_template($event_id, $channel_id, [
+ 'subject' => $subject,
+ 'body' => $body,
+ ]);
+
+ if (!$success) {
+ return new WP_Error(
+ 'save_failed',
+ __('Failed to save template', 'woonoow'),
+ ['status' => 500]
+ );
+ }
+
+ return new WP_REST_Response([
+ 'success' => true,
+ 'message' => __('Template saved successfully', 'woonoow'),
+ ], 200);
+ }
+
+ /**
+ * Delete template (revert to default)
+ *
+ * @param WP_REST_Request $request Request object
+ * @return WP_REST_Response
+ */
+ public function delete_template(WP_REST_Request $request) {
+ $event_id = $request->get_param('eventId');
+ $channel_id = $request->get_param('channelId');
+
+ TemplateProvider::delete_template($event_id, $channel_id);
+
+ return new WP_REST_Response([
+ 'success' => true,
+ 'message' => __('Template reverted to default', 'woonoow'),
+ ], 200);
+ }
+
+ /**
+ * Get VAPID public key for push notifications
+ *
+ * @param WP_REST_Request $request Request object
+ * @return WP_REST_Response
+ */
+ public function get_vapid_key(WP_REST_Request $request) {
+ $public_key = PushNotificationHandler::get_public_key();
+
+ return new WP_REST_Response([
+ 'publicKey' => $public_key,
+ ], 200);
+ }
+
+ /**
+ * Subscribe to push notifications
+ *
+ * @param WP_REST_Request $request Request object
+ * @return WP_REST_Response|WP_Error
+ */
+ public function push_subscribe(WP_REST_Request $request) {
+ $subscription = $request->get_param('subscription');
+ $user_id = get_current_user_id();
+
+ if (empty($subscription)) {
+ return new WP_Error(
+ 'invalid_subscription',
+ __('Subscription data is required', 'woonoow'),
+ ['status' => 400]
+ );
+ }
+
+ $success = PushNotificationHandler::subscribe($subscription, $user_id);
+
+ if (!$success) {
+ return new WP_Error(
+ 'subscribe_failed',
+ __('Failed to subscribe to push notifications', 'woonoow'),
+ ['status' => 500]
+ );
+ }
+
+ return new WP_REST_Response([
+ 'success' => true,
+ 'message' => __('Subscribed to push notifications', 'woonoow'),
+ ], 200);
+ }
+
+ /**
+ * Unsubscribe from push notifications
+ *
+ * @param WP_REST_Request $request Request object
+ * @return WP_REST_Response
+ */
+ public function push_unsubscribe(WP_REST_Request $request) {
+ $subscription_id = $request->get_param('subscriptionId');
+
+ PushNotificationHandler::unsubscribe($subscription_id);
+
+ return new WP_REST_Response([
+ 'success' => true,
+ 'message' => __('Unsubscribed from push notifications', 'woonoow'),
+ ], 200);
+ }
}
diff --git a/includes/Core/Bootstrap.php b/includes/Core/Bootstrap.php
index c2c8fcb..17e5a64 100644
--- a/includes/Core/Bootstrap.php
+++ b/includes/Core/Bootstrap.php
@@ -19,6 +19,7 @@ use WooNooW\Core\Mail\MailQueue;
use WooNooW\Core\Mail\WooEmailOverride;
use WooNooW\Core\DataStores\OrderStore;
use WooNooW\Core\MediaUpload;
+use WooNooW\Core\Notifications\PushNotificationHandler;
use WooNooW\Branding;
class Bootstrap {
@@ -30,6 +31,7 @@ class Bootstrap {
StandaloneAdmin::init();
Branding::init();
MediaUpload::init();
+ PushNotificationHandler::init();
// Addon system (order matters: Registry → Routes → Navigation)
AddonRegistry::init();
diff --git a/includes/Core/Notifications/PushNotificationHandler.php b/includes/Core/Notifications/PushNotificationHandler.php
new file mode 100644
index 0000000..f38ee89
--- /dev/null
+++ b/includes/Core/Notifications/PushNotificationHandler.php
@@ -0,0 +1,213 @@
+ base64_encode(random_bytes(65)),
+ 'private_key' => base64_encode(random_bytes(32)),
+ 'generated_at' => current_time('mysql'),
+ ];
+ }
+
+ /**
+ * Get VAPID public key
+ *
+ * @return string
+ */
+ public static function get_public_key() {
+ $keys = self::ensure_vapid_keys();
+ return $keys['public_key'];
+ }
+
+ /**
+ * Subscribe to push notifications
+ *
+ * @param array $subscription Subscription data
+ * @param int $user_id User ID
+ * @return bool
+ */
+ public static function subscribe($subscription, $user_id = 0) {
+ $subscriptions = get_option(self::SUBSCRIPTIONS_KEY, []);
+
+ $subscription_id = md5(json_encode($subscription));
+
+ $subscriptions[$subscription_id] = [
+ 'subscription' => $subscription,
+ 'user_id' => $user_id,
+ 'subscribed_at' => current_time('mysql'),
+ ];
+
+ return update_option(self::SUBSCRIPTIONS_KEY, $subscriptions);
+ }
+
+ /**
+ * Unsubscribe from push notifications
+ *
+ * @param string $subscription_id Subscription ID
+ * @return bool
+ */
+ public static function unsubscribe($subscription_id) {
+ $subscriptions = get_option(self::SUBSCRIPTIONS_KEY, []);
+
+ if (isset($subscriptions[$subscription_id])) {
+ unset($subscriptions[$subscription_id]);
+ return update_option(self::SUBSCRIPTIONS_KEY, $subscriptions);
+ }
+
+ return false;
+ }
+
+ /**
+ * Get all subscriptions
+ *
+ * @param int $user_id Optional user ID filter
+ * @return array
+ */
+ public static function get_subscriptions($user_id = null) {
+ $subscriptions = get_option(self::SUBSCRIPTIONS_KEY, []);
+
+ if ($user_id !== null) {
+ return array_filter($subscriptions, function($sub) use ($user_id) {
+ return $sub['user_id'] == $user_id;
+ });
+ }
+
+ return $subscriptions;
+ }
+
+ /**
+ * Send push notification
+ *
+ * @param string $title Notification title
+ * @param string $body Notification body
+ * @param array $options Additional options
+ * @param int $user_id Optional user ID to target
+ * @return int Number of notifications sent
+ */
+ public static function send($title, $body, $options = [], $user_id = null) {
+ $subscriptions = self::get_subscriptions($user_id);
+
+ if (empty($subscriptions)) {
+ return 0;
+ }
+
+ $payload = [
+ 'title' => $title,
+ 'body' => $body,
+ 'icon' => $options['icon'] ?? get_site_icon_url(),
+ 'badge' => $options['badge'] ?? get_site_icon_url(),
+ 'data' => $options['data'] ?? [],
+ 'actions' => $options['actions'] ?? [],
+ ];
+
+ $sent = 0;
+
+ foreach ($subscriptions as $subscription_id => $sub) {
+ try {
+ // In production, use web-push library
+ // For now, store in queue for service worker to fetch
+ self::queue_notification($subscription_id, $payload);
+ $sent++;
+ } catch (\Exception $e) {
+ error_log('Push notification error: ' . $e->getMessage());
+ }
+ }
+
+ return $sent;
+ }
+
+ /**
+ * Queue notification for delivery
+ *
+ * @param string $subscription_id Subscription ID
+ * @param array $payload Notification payload
+ */
+ private static function queue_notification($subscription_id, $payload) {
+ $queue = get_option('woonoow_push_queue', []);
+
+ $queue[] = [
+ 'subscription_id' => $subscription_id,
+ 'payload' => $payload,
+ 'queued_at' => current_time('mysql'),
+ ];
+
+ update_option('woonoow_push_queue', $queue);
+ }
+
+ /**
+ * Get queued notifications for user
+ *
+ * @param int $user_id User ID
+ * @return array
+ */
+ public static function get_queued_notifications($user_id) {
+ $queue = get_option('woonoow_push_queue', []);
+ $subscriptions = self::get_subscriptions($user_id);
+ $subscription_ids = array_keys($subscriptions);
+
+ $user_notifications = array_filter($queue, function($item) use ($subscription_ids) {
+ return in_array($item['subscription_id'], $subscription_ids);
+ });
+
+ // Clear delivered notifications
+ $remaining = array_filter($queue, function($item) use ($subscription_ids) {
+ return !in_array($item['subscription_id'], $subscription_ids);
+ });
+
+ update_option('woonoow_push_queue', $remaining);
+
+ return array_values($user_notifications);
+ }
+}
diff --git a/includes/Core/Notifications/TemplateProvider.php b/includes/Core/Notifications/TemplateProvider.php
new file mode 100644
index 0000000..7b4824f
--- /dev/null
+++ b/includes/Core/Notifications/TemplateProvider.php
@@ -0,0 +1,276 @@
+ $event_id,
+ 'channel_id' => $channel_id,
+ 'subject' => $template['subject'] ?? '',
+ 'body' => $template['body'] ?? '',
+ 'variables' => $template['variables'] ?? [],
+ 'updated_at' => current_time('mysql'),
+ ];
+
+ return update_option(self::OPTION_KEY, $templates);
+ }
+
+ /**
+ * Delete template (revert to default)
+ *
+ * @param string $event_id Event ID
+ * @param string $channel_id Channel ID
+ * @return bool
+ */
+ public static function delete_template($event_id, $channel_id) {
+ $templates = get_option(self::OPTION_KEY, []);
+
+ $key = "{$event_id}_{$channel_id}";
+
+ if (isset($templates[$key])) {
+ unset($templates[$key]);
+ return update_option(self::OPTION_KEY, $templates);
+ }
+
+ return false;
+ }
+
+ /**
+ * Get default templates
+ *
+ * @return array
+ */
+ public static function get_default_templates() {
+ return [
+ // Email templates
+ 'order_placed_email' => [
+ 'event_id' => 'order_placed',
+ 'channel_id' => 'email',
+ 'subject' => __('New Order #{order_number}', 'woonoow'),
+ 'body' => __("Hi Admin,\n\nYou have received a new order.\n\nOrder Number: {order_number}\nOrder Total: {order_total}\nCustomer: {customer_name}\nEmail: {customer_email}\n\nView order: {order_url}", 'woonoow'),
+ 'variables' => self::get_order_variables(),
+ ],
+ 'order_processing_email' => [
+ 'event_id' => 'order_processing',
+ 'channel_id' => 'email',
+ 'subject' => __('Your order #{order_number} is being processed', 'woonoow'),
+ 'body' => __("Hi {customer_name},\n\nThank you for your order! We're now processing it.\n\nOrder Number: {order_number}\nOrder Total: {order_total}\nPayment Method: {payment_method}\n\nYou can track your order here: {order_url}\n\nBest regards,\n{store_name}", 'woonoow'),
+ 'variables' => self::get_order_variables(),
+ ],
+ 'order_completed_email' => [
+ 'event_id' => 'order_completed',
+ 'channel_id' => 'email',
+ 'subject' => __('Your order #{order_number} is complete', 'woonoow'),
+ 'body' => __("Hi {customer_name},\n\nYour order has been completed and shipped!\n\nOrder Number: {order_number}\nOrder Total: {order_total}\nTracking Number: {tracking_number}\n\nThank you for shopping with us!\n\nBest regards,\n{store_name}", 'woonoow'),
+ 'variables' => self::get_order_variables(),
+ ],
+ 'order_cancelled_email' => [
+ 'event_id' => 'order_cancelled',
+ 'channel_id' => 'email',
+ 'subject' => __('Order #{order_number} has been cancelled', 'woonoow'),
+ 'body' => __("Hi Admin,\n\nOrder #{order_number} has been cancelled.\n\nOrder Number: {order_number}\nOrder Total: {order_total}\nCustomer: {customer_name}\n\nView order: {order_url}", 'woonoow'),
+ 'variables' => self::get_order_variables(),
+ ],
+ 'order_refunded_email' => [
+ 'event_id' => 'order_refunded',
+ 'channel_id' => 'email',
+ 'subject' => __('Your order #{order_number} has been refunded', 'woonoow'),
+ 'body' => __("Hi {customer_name},\n\nYour order has been refunded.\n\nOrder Number: {order_number}\nRefund Amount: {refund_amount}\n\nThe refund will be processed within 5-7 business days.\n\nBest regards,\n{store_name}", 'woonoow'),
+ 'variables' => self::get_order_variables(),
+ ],
+ 'low_stock_email' => [
+ 'event_id' => 'low_stock',
+ 'channel_id' => 'email',
+ 'subject' => __('Low Stock Alert: {product_name}', 'woonoow'),
+ 'body' => __("Hi Admin,\n\nThe following product is running low on stock:\n\nProduct: {product_name}\nSKU: {product_sku}\nCurrent Stock: {stock_quantity}\n\nPlease restock soon.\n\nView product: {product_url}", 'woonoow'),
+ 'variables' => self::get_product_variables(),
+ ],
+ 'out_of_stock_email' => [
+ 'event_id' => 'out_of_stock',
+ 'channel_id' => 'email',
+ 'subject' => __('Out of Stock Alert: {product_name}', 'woonoow'),
+ 'body' => __("Hi Admin,\n\nThe following product is now out of stock:\n\nProduct: {product_name}\nSKU: {product_sku}\n\nPlease restock immediately.\n\nView product: {product_url}", 'woonoow'),
+ 'variables' => self::get_product_variables(),
+ ],
+ 'new_customer_email' => [
+ 'event_id' => 'new_customer',
+ 'channel_id' => 'email',
+ 'subject' => __('Welcome to {store_name}!', 'woonoow'),
+ 'body' => __("Hi {customer_name},\n\nWelcome to {store_name}!\n\nYour account has been created successfully.\n\nEmail: {customer_email}\n\nYou can now browse our products and place orders.\n\nVisit our store: {store_url}\n\nBest regards,\n{store_name}", 'woonoow'),
+ 'variables' => self::get_customer_variables(),
+ ],
+ 'customer_note_email' => [
+ 'event_id' => 'customer_note',
+ 'channel_id' => 'email',
+ 'subject' => __('Note added to your order #{order_number}', 'woonoow'),
+ 'body' => __("Hi {customer_name},\n\nA note has been added to your order:\n\nOrder Number: {order_number}\nNote: {note_content}\n\nView order: {order_url}\n\nBest regards,\n{store_name}", 'woonoow'),
+ 'variables' => self::get_order_variables(),
+ ],
+
+ // Push notification templates
+ 'order_placed_push' => [
+ 'event_id' => 'order_placed',
+ 'channel_id' => 'push',
+ 'subject' => __('New Order #{order_number}', 'woonoow'),
+ 'body' => __('New order from {customer_name} - {order_total}', 'woonoow'),
+ 'variables' => self::get_order_variables(),
+ ],
+ 'order_processing_push' => [
+ 'event_id' => 'order_processing',
+ 'channel_id' => 'push',
+ 'subject' => __('Order Processing', 'woonoow'),
+ 'body' => __('Your order #{order_number} is being processed', 'woonoow'),
+ 'variables' => self::get_order_variables(),
+ ],
+ 'order_completed_push' => [
+ 'event_id' => 'order_completed',
+ 'channel_id' => 'push',
+ 'subject' => __('Order Completed', 'woonoow'),
+ 'body' => __('Your order #{order_number} has been completed!', 'woonoow'),
+ 'variables' => self::get_order_variables(),
+ ],
+ 'low_stock_push' => [
+ 'event_id' => 'low_stock',
+ 'channel_id' => 'push',
+ 'subject' => __('Low Stock Alert', 'woonoow'),
+ 'body' => __('{product_name} is running low on stock', 'woonoow'),
+ 'variables' => self::get_product_variables(),
+ ],
+ ];
+ }
+
+ /**
+ * Get available order variables
+ *
+ * @return array
+ */
+ public static function get_order_variables() {
+ return [
+ 'order_number' => __('Order Number', 'woonoow'),
+ 'order_total' => __('Order Total', 'woonoow'),
+ 'order_status' => __('Order Status', 'woonoow'),
+ 'order_date' => __('Order Date', 'woonoow'),
+ 'order_url' => __('Order URL', 'woonoow'),
+ 'payment_method' => __('Payment Method', 'woonoow'),
+ 'shipping_method' => __('Shipping Method', 'woonoow'),
+ 'tracking_number' => __('Tracking Number', 'woonoow'),
+ 'refund_amount' => __('Refund Amount', 'woonoow'),
+ 'customer_name' => __('Customer Name', 'woonoow'),
+ 'customer_email' => __('Customer Email', 'woonoow'),
+ 'customer_phone' => __('Customer Phone', 'woonoow'),
+ 'billing_address' => __('Billing Address', 'woonoow'),
+ 'shipping_address' => __('Shipping Address', 'woonoow'),
+ 'store_name' => __('Store Name', 'woonoow'),
+ 'store_url' => __('Store URL', 'woonoow'),
+ 'store_email' => __('Store Email', 'woonoow'),
+ ];
+ }
+
+ /**
+ * Get available product variables
+ *
+ * @return array
+ */
+ public static function get_product_variables() {
+ return [
+ 'product_name' => __('Product Name', 'woonoow'),
+ 'product_sku' => __('Product SKU', 'woonoow'),
+ 'product_url' => __('Product URL', 'woonoow'),
+ 'stock_quantity' => __('Stock Quantity', 'woonoow'),
+ 'store_name' => __('Store Name', 'woonoow'),
+ 'store_url' => __('Store URL', 'woonoow'),
+ ];
+ }
+
+ /**
+ * Get available customer variables
+ *
+ * @return array
+ */
+ public static function get_customer_variables() {
+ return [
+ 'customer_name' => __('Customer Name', 'woonoow'),
+ 'customer_email' => __('Customer Email', 'woonoow'),
+ 'customer_phone' => __('Customer Phone', 'woonoow'),
+ 'store_name' => __('Store Name', 'woonoow'),
+ 'store_url' => __('Store URL', 'woonoow'),
+ 'store_email' => __('Store Email', 'woonoow'),
+ ];
+ }
+
+ /**
+ * Replace variables in template
+ *
+ * @param string $content Content with variables
+ * @param array $data Data to replace variables
+ * @return string
+ */
+ public static function replace_variables($content, $data) {
+ foreach ($data as $key => $value) {
+ $content = str_replace('{' . $key . '}', $value, $content);
+ }
+
+ return $content;
+ }
+}