['label' => 'Avakode', 'accent' => '#7C5CFC', 'subtle' => '#F5F3FF', 'on_accent' => '#FFFFFF'], 'bundle' => ['label' => 'Forge Suite', 'accent' => '#7C5CFC', 'subtle' => '#F5F3FF', 'on_accent' => '#FFFFFF'], 'support' => ['label' => 'Forge Support', 'accent' => '#7C5CFC', 'subtle' => '#F5F3FF', 'on_accent' => '#FFFFFF'], 'alerts' => ['label' => 'Forge Alerts', 'accent' => '#EF4444', 'subtle' => '#FEF2F2', 'on_accent' => '#FFFFFF'], 'langforge' => ['label' => 'Lang Forge', 'accent' => '#0EC49E', 'subtle' => '#F0FDF9', 'on_accent' => '#0C1A3A'], 'rankforge' => ['label' => 'Rank Forge', 'accent' => '#FF6B35', 'subtle' => '#FFF7ED', 'on_accent' => '#FFFFFF'], 'formforge' => ['label' => 'Form Forge', 'accent' => '#3B82F6', 'subtle' => '#EFF6FF', 'on_accent' => '#FFFFFF'], 'fieldforge' => ['label' => 'Field Forge', 'accent' => '#F43F5E', 'subtle' => '#FFF1F2', 'on_accent' => '#FFFFFF'], // The four unpaused products, 2026-09-03. Without an entry here a // product's mail silently falls back to the Avakode palette, which // is how it reads as someone else's e-mail. 'guardforge' => ['label' => 'Guard Forge', 'accent' => '#2ABE55', 'subtle' => '#EBFCED', 'on_accent' => '#1A1A2E'], 'velocityforge' => ['label' => 'Velocity Forge', 'accent' => '#A2C700', 'subtle' => '#F3FAE5', 'on_accent' => '#1A1A2E'], 'draftforge' => ['label' => 'Draft Forge', 'accent' => '#0D9296', 'subtle' => '#EDFAFA', 'on_accent' => '#FFFFFF'], 'flowforge' => ['label' => 'Cascade Forge', 'accent' => '#DA76BB', 'subtle' => '#FFF3FA', 'on_accent' => '#1A1A2E'], ]; } public static function render(array $args) { $args = wp_parse_args($args, [ 'product' => 'avakode', 'title' => '', 'preheader' => '', 'eyebrow' => '', 'intro' => '', 'blocks' => [], 'cta' => null, 'footer_note' => '', ]); $palettes = self::palettes(); $palette = $palettes[$args['product']] ?? $palettes['avakode']; $preheader = $args['preheader'] ?: ($args['intro'] ?: $args['title']); $blocks = is_array($args['blocks']) ? $args['blocks'] : []; $blocks_html = self::blocks_html($blocks); $cta_html = ''; if (is_array($args['cta']) && !empty($args['cta']['label']) && !empty($args['cta']['url'])) { $cta_html = '' . '
' . '' . self::esc($args['cta']['label']) . '' . '
'; } $footer_note = $args['footer_note'] ? '

' . self::esc($args['footer_note']) . '

' : ''; $intro_html = $args['intro'] ? '

' . self::esc($args['intro']) . '

' : ''; $html = ' ' // wporg-advisory-ok: the head of an HTML email, not of a page: mail clients read the style from the message itself, there is no page to enqueue it into . '
' . self::esc($preheader) . '
'; return [ 'html' => $html, 'text' => self::text_from_parts($args, $palette), ]; } public static function send_wp($to, $subject, array $args, array $headers = []) { $rendered = self::render($args); $headers = self::html_headers($headers); if (!empty($args['from'])) { $headers[] = 'From: ' . preg_replace('/[\r\n]+/', ' ', (string) $args['from']); } $subject = preg_replace('/[\r\n]+/', ' ', wp_specialchars_decode((string) $subject, ENT_QUOTES)); self::prime_alt_body($rendered['text']); add_filter('wp_mail_content_type', [__CLASS__, 'html_content_type']); try { return (bool) wp_mail($to, $subject, $rendered['html'], $headers); } finally { remove_filter('wp_mail_content_type', [__CLASS__, 'html_content_type']); remove_action('phpmailer_init', [__CLASS__, 'set_alt_body']); self::$alt_body = ''; } } public static function html_headers(array $headers = []) { foreach ($headers as $header) { if (stripos((string) $header, 'content-type:') === 0) { return $headers; } } $headers[] = 'Content-Type: text/html; charset=UTF-8'; return $headers; } public static function prime_alt_body($text) { self::$alt_body = (string) $text; add_action('phpmailer_init', [__CLASS__, 'set_alt_body']); } public static function html_content_type() { return 'text/html'; } public static function set_alt_body($phpmailer) { if (self::$alt_body !== '' && is_object($phpmailer) && property_exists($phpmailer, 'AltBody')) { $phpmailer->AltBody = self::$alt_body; } remove_action('phpmailer_init', [__CLASS__, 'set_alt_body']); self::$alt_body = ''; } private static function text_from_parts(array $args, array $palette) { $parts = array_filter([ $args['eyebrow'] ?: $palette['label'], $args['title'], $args['intro'], self::text_from_blocks(is_array($args['blocks']) ? $args['blocks'] : []), is_array($args['cta'] ?? null) && !empty($args['cta']['label']) && !empty($args['cta']['url']) ? $args['cta']['label'] . ': ' . $args['cta']['url'] : '', $args['footer_note'], ], function($part) { return (string) $part !== ''; }); return implode("\n\n", array_map('wp_strip_all_tags', $parts)); } private static function text_from_blocks(array $blocks) { $parts = []; foreach ($blocks as $block) { if (is_array($block)) { if (!empty($parts)) { $last = end($parts); if (strpos((string) $last, ':') === false) { $parts[] = ''; } } $parts[] = ($block['label'] ?? '') . ': ' . self::format_value($block['value'] ?? ''); } else { $parts[] = (string) $block; } } return implode("\n", $parts); } private static function format_value($value) { if (is_array($value)) { return implode(', ', array_map('strval', $value)); } return (string) $value; } private static function blocks_html(array $blocks) { $html = ''; $rows = []; foreach ($blocks as $block) { if (is_array($block)) { $rows[] = $block; continue; } $html .= self::rows_table_html($rows); $rows = []; $html .= '

' . nl2br(self::esc($block), false) . '

'; } $html .= self::rows_table_html($rows); return $html; } private static function rows_table_html(array $rows) { if (empty($rows)) { return ''; } $row_html = ''; foreach ($rows as $row) { $label = self::esc($row['label'] ?? ''); $value = self::esc(self::format_value($row['value'] ?? '')); $row_html .= '' . '' . $label . '' . '' . $value . '' . ''; } return '' . $row_html . '
'; } private static function esc($value) { return esc_html(self::format_value($value)); } } }