| | |
| | | class EmailManager |
| | | { |
| | | |
| | | private array $colours = JVB_EMAIL['colours']; |
| | | public array $colours; |
| | | private string $title = JVB_EMAIL['content']['title']; |
| | | private string $prefix = JVB_EMAIL['content']['subjectPrefix']; |
| | | private string $signature = JVB_EMAIL['content']['signature']; |
| | |
| | | $this->site_name = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES); |
| | | $this->site_url = get_site_url(); |
| | | $this->footer = (is_array(JVB_EMAIL['content']['footer'])) ? implode('', JVB_EMAIL['content']['footer']) : JVB_EMAIL['content']['footer']; |
| | | |
| | | $this->colours = JVB_COLOURS; |
| | | add_filter('wp_mail_content_type', [$this, 'setHtmlContentType']); |
| | | // User registration emails |
| | | add_filter('wp_new_user_notification_email', [$this, 'customizeNewUserEmail'], 10, 3); |
| | | add_filter('wp_new_user_notification_email_admin', [$this, 'customizeNewUserEmailAdmin'], 10, 3); |
| | | add_filter('wp_new_user_notification_email', [$this, 'customizeNewUserEmail'], 999, 3); |
| | | add_filter('wp_new_user_notification_email_admin', [$this, 'customizeNewUserEmailAdmin'], 999, 3); |
| | | |
| | | // Password reset emails |
| | | add_filter('retrieve_password_message', [$this, 'customizePasswordResetEmail'], 10, 4); |
| | | add_filter('retrieve_password_title', [$this, 'customizePasswordResetTitle'], 10, 3); |
| | | add_filter('retrieve_password_message', [$this, 'customizePasswordResetEmail'], 999, 4); |
| | | add_filter('retrieve_password_title', [$this, 'customizePasswordResetTitle'], 999, 3); |
| | | |
| | | // User email change emails |
| | | add_filter('email_change_email', [$this, 'customizeEmailChangeEmail'], 10, 3); |
| | | add_filter('new_user_email_content', [$this, 'customizeNewUserEmailContent'], 10, 2); |
| | | add_filter('email_change_email', [$this, 'customizeEmailChangeEmail'], 999, 3); |
| | | add_filter('new_user_email_content', [$this, 'customizeNewUserEmailContent'], 999, 2); |
| | | |
| | | // Password change notification |
| | | add_filter('password_change_email', [$this, 'customizePasswordChangeEmail'], 10, 3); |
| | | add_filter('password_change_email', [$this, 'customizePasswordChangeEmail'], 999, 3); |
| | | |
| | | // User request/export data emails |
| | | add_filter('user_request_action_email_content', [$this, 'customizeUserRequestEmail'], 10, 2); |
| | | add_filter('wp_privacy_personal_data_email_content', [$this, 'customizePersonalDataEmail'], 10, 3); |
| | | add_filter('user_request_action_email_content', [$this, 'customizeUserRequestEmail'], 999, 2); |
| | | add_filter('wp_privacy_personal_data_email_content', [$this, 'customizePersonalDataEmail'], 999, 3); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @param string $header Optional header text for the template |
| | | * @return bool Whether the email was sent successfully |
| | | */ |
| | | public function sendEmail(string $to, string $subject, string $message, string $header = ''):bool |
| | | public function sendEmail(string $to, string $subject, string $message, string $header = '', string $preheader = '', array $headers = [], array $attachments = []):bool |
| | | { |
| | | // Make sure the content type is set to HTML |
| | | add_filter('wp_mail_content_type', [$this, 'setHtmlContentType']); |
| | | |
| | | // Format the message with our template |
| | | $formatted_message = $this->getEmailTemplate($message, $header); |
| | | $formatted_message = $this->getEmailTemplate($message, $header, $preheader); |
| | | |
| | | // Send the email |
| | | $result = wp_mail($to, $subject, $formatted_message); |
| | | $result = wp_mail($to, $subject, $formatted_message, $headers, $attachments); |
| | | |
| | | // Reset content type filter to avoid affecting other emails |
| | | remove_filter('wp_mail_content_type', [$this, 'setHtmlContentType']); |
| | |
| | | * |
| | | * @return string |
| | | */ |
| | | private function getEmailTemplate(string $content, string $header = ''):string |
| | | private function getEmailTemplate(string $content, string $headerText = '', string $preheader = ''):string |
| | | { |
| | | $logo = get_custom_logo(); |
| | | $custom_logo_id = get_theme_mod( 'custom_logo' ); |
| | | $logo_thumbnail = wp_get_attachment_image_src( $custom_logo_id, 'custom-logo-thumbnail' ); |
| | | |
| | | $logo = ($logo_thumbnail) ? '<img src="' . esc_url( $logo_thumbnail[0] ) . '" alt="Site Logo" width="150" height="150" style="max-width:120px;height:auto;">' : ''; |
| | | |
| | | |
| | | // Default header if none provided |
| | | if (empty($header)) { |
| | | $header = $this->title; |
| | | if (empty($headerText)) { |
| | | $headerText = $this->title; |
| | | } |
| | | |
| | | $preheaderHtml = ''; |
| | | if (!empty($preheader)) { |
| | | $preheaderHtml = ' |
| | | <div style="display:none;font-size:1px;color:#fefefe;line-height:1px;font-family:Helvetica,Arial,sans-serif;max-height:0px;max-width:0px;opacity:0;overflow:hidden;"> |
| | | ' . esc_html($preheader) . ' |
| | | </div>'; |
| | | } |
| | | |
| | | return '<!DOCTYPE html> |
| | | <html> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>' . $this->title . ' | ' . $this->site_name .'</title> |
| | | <style> |
| | | </head> |
| | | <body style="font-family:Segoe UI, Tahoma, Geneva, Verdana, sans-serif;background-color: '.$this->colours['light'].';color:'.$this->colours['dark'].';line-height:1.5;margin:0;padding:0;"> |
| | | <div style="background-color:'.$this->colours['dark'].';color:'.$this->colours['light'].';padding:5px;text-align:center;font-size:24px;font-weight:900;letter-spacing:1.5px;text-transform:uppercase;border-radius:5px 5px 0 0;"> |
| | | <a href="' . $this->site_url . '" style="display:inline-block;vertical-align:middle;"> |
| | | ' . $logo . ' |
| | | </a> |
| | | <p style="display:inline-block;vertical-align:middle;margin: 0 auto 0 0;">' . $headerText . '</p> |
| | | </div> |
| | | <div style="padding:4rem 0;width:100%;max-width:600px;margin:0 auto;"> |
| | | ' . $content . ' |
| | | '.$this->signature().' |
| | | </div> |
| | | <div style="border-radius:0 0 5px 5px;text-align:center;margin-top:20px;font-size:12px;background-color:'.$this->colours['light-200'].'; color: '.$this->colours['dark-200'].';padding:10px;line-height:1.6;"> |
| | | ' . $this->footer . ' |
| | | </div> |
| | | </body> |
| | | </html>'; |
| | | } |
| | | |
| | | private function oldStyle(){ |
| | | $oldStyle =' |
| | | <style> |
| | | body, table, td, p, a, li, blockquote { |
| | | line-height: 1.5; |
| | | } |
| | |
| | | vertical-align: middle; |
| | | } |
| | | .header p { |
| | | margin: 0; |
| | | margin-left: auto; |
| | | margin: 0 auto 0 0; |
| | | } |
| | | |
| | | .content { |
| | |
| | | text-align: center; |
| | | margin-top: 20px; |
| | | font-size: 12px; |
| | | backgorund-color: ' . $this->colours['light-200'] . '; |
| | | background-color: ' . $this->colours['light-200'] . '; |
| | | color: ' . $this->colours['dark-200'] . '; |
| | | padding: 10px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .notice { |
| | | text-align: center; |
| | | border-radius: 0 8px 8px 0; |
| | | margin: 1rem 0; |
| | | border-left: 4px solid '.$this->colours['action-0'].'; |
| | | padding: 1rem; |
| | | background-color: '.$this->colours['light-100'].'; |
| | | } |
| | | .notice strong { |
| | | color: '.$this->colours['action-0'].'; |
| | | } |
| | | |
| | | .divider { |
| | | border-top: 1px solid ' . $this->colours['dark-200'] . '; |
| | | margin: 25px 0; |
| | |
| | | } |
| | | } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <div class="email-container"> |
| | | |
| | | <div class="header"> |
| | | <a href="' . $this->site_url . '"> |
| | | ' . $logo . ' |
| | | </a> |
| | | <p>' . $header . '</p> |
| | | </div> |
| | | <div class="content"> |
| | | ' . $content . ' |
| | | </div> |
| | | <div class="footer"> |
| | | ' . $this->footer . ' |
| | | </div> |
| | | </div> |
| | | </body> |
| | | </html>'; |
| | | } |
| | | '; |
| | | } |
| | | |
| | | /** |
| | | * New user registration email to user |
| | |
| | | $user->user_login, |
| | | ); |
| | | $message = apply_filters('jvbNewUserEmail', $message, $user); |
| | | |
| | | $message .= $this->signature; |
| | | $wp_new_user_notification_email['message'] = $this->getEmailTemplate($message, JVB_EMAIL['types']['newUser']['subject']?:'New User'); |
| | | |
| | | // Change the subject line |
| | |
| | | $message .= '<p><strong>Username:</strong> ' . $user->user_login . '</p>'; |
| | | $message .= '<p><strong>Email:</strong> ' . $user->user_email . '</p>'; |
| | | $message = apply_filters('jvbNewUserAdminEmail', $message, $user); |
| | | $message .= $this->signature; |
| | | $emailData['message'] = $this->getEmailTemplate($message, 'New User Registration'); |
| | | $emailData['subject'] = $this->prefix .'New ' . str_replace(BASE, '', array_values($user->roles)[0]).': '.$user->display_name; |
| | | |
| | |
| | | */ |
| | | public function customizePasswordResetEmail(string $message, string $key, string $user_login, WP_User $user):string |
| | | { |
| | | $reset_url = network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login'); |
| | | return $this->passwordResetEmail($user, $key); |
| | | } |
| | | |
| | | public function passwordResetEmail(WP_User $user, string $key):string { |
| | | $reset_url = network_site_url("login/?action=resetpass&key=$key&login=" . rawurlencode($user->user_login), 'login'); |
| | | $content = sprintf( |
| | | '<p>Hi %s!</p> |
| | | <p>We received a request to reset the password for an account associated with this email:</p> |
| | |
| | | %s |
| | | <p>Or copy and paste this link into your browser:</p> |
| | | %s |
| | | <div class="divider"></div> |
| | | %s |
| | | <p>This password reset link is only valid for 24 hours.</p>', |
| | | $user->display_name, |
| | | $user_login, |
| | | JVB()->email()->button($reset_url,'Reset Password'), |
| | | JVB()->email()->link($reset_url) |
| | | $user->user_login, |
| | | $this->button($reset_url,'Reset Password'), |
| | | $this->link($reset_url), |
| | | $this->divider() |
| | | ); |
| | | $content = apply_filters('jvbPasswordResetEmail', $content, $user_login, $user, $reset_url); |
| | | $content .= $this->signature; |
| | | return $this->getEmailTemplate($content, 'Password Reset'); |
| | | } |
| | | return apply_filters('jvbPasswordResetEmail', $content, $user->user_login, $user, $reset_url); |
| | | } |
| | | |
| | | public function sendPasswordResetEmail(WP_User $user, string $key):bool |
| | | { |
| | | return $this->sendEmail($user->user_email, $this->passwordResetTitle(), $this->passwordResetEmail($user, $key), '', 'Reset your Password'); |
| | | } |
| | | |
| | | /** |
| | | * Customize the password reset email title |
| | |
| | | */ |
| | | public function customizePasswordResetTitle(string $title, string $user_login, WP_User $user_data):string |
| | | { |
| | | return $this->passwordResetTitle(); |
| | | } |
| | | |
| | | public function passwordResetTitle():string |
| | | { |
| | | $prefix = JVB_EMAIL['types']['resetPass']['showPrefix']??true; |
| | | $prefix = ($prefix) ? $this->prefix : ''; |
| | | return $prefix.JVB_EMAIL['types']['resetPass']['subject']?:'Password Reset'; |
| | | |
| | | } |
| | | |
| | | /** |
| | |
| | | <p>Ideally you already know this: someone asked to change the email for your account.</p> |
| | | <p><strong>Old Email:</strong> %s</p> |
| | | <p><strong>New Email:</strong> %s</p> |
| | | <div class="divider"></div> |
| | | %s |
| | | <p>If this is news to you, or you did not request this - please contact us immediately. You can <a href="sms:+18258239916">text us</a> or reply to this email."></a></p> |
| | | %s', |
| | | $newUser['first_name'], |
| | | $oldUser['user_email'], |
| | | $newUser['user_email'], |
| | | JVB()->email()->button(wp_login_url(), 'Log In To Your Account') |
| | | $this->divider(), |
| | | $this->button(wp_login_url(), 'Log In To Your Account') |
| | | ); |
| | | $content = apply_filters('jvbEmailChangeRequestEmail', $content, $oldUser, $newUser); |
| | | $content .= $this->signature; |
| | | $email_change_email['message'] = $this->getEmailTemplate($content, 'Email Address Changed'); |
| | | $prefix = JVB_EMAIL['types']['emailChange']['showPrefix']??true; |
| | | $prefix = ($prefix) ? $this->prefix : ''; |
| | |
| | | %s |
| | | <p>Or copy and paste this link into your browser:</p> |
| | | %s', |
| | | JVB()->email()->button($confirm_url, 'Confirm this Email'), |
| | | JVB()->email()->link($confirm_url) |
| | | $this->button($confirm_url, 'Confirm this Email'), |
| | | $this->link($confirm_url) |
| | | ); |
| | | |
| | | $content = apply_filters('jvbEmailChangedEmail', $content, $confirm_url); |
| | | |
| | | $content .= '<div class="divider"></div>'; |
| | | $content .= $this->divider(); |
| | | $content .= '<p>If you did not request this change, you can safely ignore this email and nothing will change.</p>'; |
| | | $content .= $this->signature; |
| | | |
| | | return $this->getEmailTemplate($content, 'Confirm Email Change'); |
| | | } |
| | |
| | | <p>You can <a href="sms:+18259257398">text us</a>, or reply to this email.</p> |
| | | %s', |
| | | $oldUser['first_name'], |
| | | JVB()->email()->button(wp_login_url(), 'Log In to Your Account') |
| | | $this->button(wp_login_url(), 'Log In to Your Account') |
| | | ); |
| | | $content = apply_filters('jvbPasswordChangeEmail', $content, $oldUser, $newUser); |
| | | $content .= $this->signature; |
| | | |
| | | $pass_change_email['message'] = $this->getEmailTemplate($content, 'Password Changed'); |
| | | $prefix = JVB_EMAIL['types']['passwordChange']['showPrefix']??true; |
| | |
| | | <p>Or copy and paste this link into your browser:</p> |
| | | %s', |
| | | $request_name, |
| | | JVB()->email()->button($confirm_url, 'Confirm'), |
| | | JVB()->email()->link($confirm_url) |
| | | $this->button($confirm_url, 'Confirm'), |
| | | $this->link($confirm_url) |
| | | ); |
| | | $message = apply_filters('jvbPersonalDataExport', $message, $request_type, $confirm_url, $email_data); |
| | | |
| | | $message .= '<div class="divider"></div>'; |
| | | $message .= $this->divider(); |
| | | $message .= '<p>If you did not make this request, you can safely ignore this email.</p>'; |
| | | $message .= $this->signature; |
| | | |
| | | return $this->getEmailTemplate($message, 'Action Confirmation'); |
| | | } |
| | |
| | | %s |
| | | <p>Or you can copy and paste this link into your browser:</p> |
| | | %s |
| | | <div class="divider"></div> |
| | | %s |
| | | <p><strong>Important:</strong> For privacy and security, this link will expire at %s.</p>', |
| | | JVB()->email()->button($download_url, 'Download Your Data'), |
| | | JVB()->email()->link($download_url), |
| | | $this->button($download_url, 'Download Your Data'), |
| | | $this->link($download_url), |
| | | $this->divider(), |
| | | $expiresAt |
| | | ); |
| | | $message = apply_filters('jvbPersonalDataExported', $message, $download_url, $expiresAt, $email_data); |
| | | $message .= $this->signature; |
| | | |
| | | return $this->getEmailTemplate($message, 'Your Personal Data Export'); |
| | | } |
| | | |
| | | public function signature():string |
| | | { |
| | | return $this->signature; |
| | | return '<p><i>'.$this->signature.'</i></p>'; |
| | | } |
| | | |
| | | public function button(string $link, string $title):string |
| | | { |
| | | return sprintf( |
| | | '<p style="text-align: center;"><a href="%s" class="button">%s</a></p>', |
| | | '<p style="text-align: center;"><a href="%s" style="display:inline-block;padding:16px 10px;border-radius:4px;background-color:%s;color:%s;text-decoration:none;font-weight:bold;margin:15px 0;letter-spacing:1px;text-transform:uppercase;">%s</a></p>', |
| | | $link, |
| | | $this->colours['action-0'], |
| | | $this->colours['action-contrast'], |
| | | $title |
| | | ); |
| | | } |
| | |
| | | public function link(string $link):string |
| | | { |
| | | return sprintf( |
| | | '<p style="user-select:all;">%s</p>', |
| | | '<code style="color:%s;border:1px solid %s;background-color:%s;border-radius:4px;user-select:all;">%s</code>', |
| | | $this->colours['dark-200'], |
| | | $this->colours['dark-100'], |
| | | $this->colours['light-100'], |
| | | $link |
| | | ); |
| | | } |
| | | |
| | | public function divider():string |
| | | { |
| | | return '<div style="border-top:1px solid '.$this->colours['dark-200'].';margin:25px 0;"></div>'; |
| | | } |
| | | |
| | | public function notice(string $text):string |
| | | { |
| | | return '<div style="border-radius: 0 8px 8px 0; margin: 1rem 0; border-left: 4px solid '.$this->colours['action-0'].';padding:1rem;background-color:'.$this->colours['light-100'].';"> |
| | | '.str_replace('<strong>', '<strong style="color:'.$this->colours['action-0'].'">',$text).' |
| | | </div>'; |
| | | } |
| | | |
| | | public function callout(string $text):string |
| | | { |
| | | return sprintf( |
| | | '<div style="padding:2rem;margin:2rem 3rem;background-color:%s;color:%s;">%s</div>', |
| | | $this->colours['action-0'], |
| | | $this->colours['action-contrast'], |
| | | str_replace('<a', '<a style="background-color:'.$this->colours['action-contrast'].';padding: 0 .125rem;border-radius:4px;"', $text) |
| | | ); |
| | | } |
| | | |
| | | public function table(array $summarize, string $title = '', array $actions = []):string |
| | | { |
| | | if (empty($summarize)){ |
| | | return ''; |
| | | } |
| | | |
| | | if (!empty($title)) { |
| | | $title = sprintf( |
| | | '<h2 style="color:%s;border-bottom:2px solid %s;padding-bottom:15px;margin-bottom:20px;text-align:center;">%s</h2>', |
| | | $this->colours['dark-200'], |
| | | $this->colours['dark-200'], |
| | | $title |
| | | ); |
| | | } |
| | | $content = ''; |
| | | foreach ($summarize as $index=> $item) { |
| | | if (!array_key_exists('label', $item) && !array_key_exists('value', $item)) { |
| | | continue; |
| | | } |
| | | $content .= sprintf( |
| | | '<div style="padding:10px 0;border-bottom:1px solid %s;background-color:%s;"> |
| | | <span style="display:inline-block;vertical-align:top;font-weight:600;color:%s;width:%s;">%s</span> |
| | | <div style="display:inline-block;vertical-align:top;width:%s;">%s</div> |
| | | </div>', |
| | | $this->colours['dark-200'], |
| | | ($index%2 === 0) ? $this->colours['light-100'] : $this->colours['light-50'], |
| | | $this->colours['dark-200'], |
| | | '30%', // Changed from 19% to 30% |
| | | $item['label'], |
| | | '68%', // Changed from 80% to 68% (30% + 68% = 98%, leaving 2% for spacing) |
| | | $item['value'] |
| | | ); |
| | | } |
| | | |
| | | return sprintf( |
| | | '<div style="max-width:500px;margin:40px auto;padding:30px;background-color:%s;border-radius:10px;border:2px dashed %s;">%s%s</div>', |
| | | $this->colours['light-100'], |
| | | $this->colours['dark-200'], |
| | | $title, |
| | | $content |
| | | ); |
| | | } |
| | | |
| | | public function card(string $content, string $title = ''):string |
| | | { |
| | | $titleHtml = ''; |
| | | if (!empty($title)) { |
| | | $titleHtml = sprintf( |
| | | '<h3 style="margin:0 0 15px 0;color:%s;font-size:18px;font-weight:600;">%s</h3>', |
| | | $this->colours['dark'], |
| | | esc_html($title) |
| | | ); |
| | | } |
| | | |
| | | return sprintf( |
| | | '<div style="background-color:%s;border:1px solid %s;border-radius:8px;padding:20px;margin:15px 0;">%s%s</div>', |
| | | $this->colours['light-50'], |
| | | $this->colours['light-200'], |
| | | $titleHtml, |
| | | $content |
| | | ); |
| | | } |
| | | |
| | | public function stat(string $number, string $label, string $description = ''):string |
| | | { |
| | | $desc = $description ? sprintf( |
| | | '<p style="margin:5px 0 0 0;font-size:12px;color:%s;">%s</p>', |
| | | $this->colours['dark-200'], |
| | | esc_html($description) |
| | | ) : ''; |
| | | |
| | | return sprintf( |
| | | '<div style="text-align:center;padding:20px;background-color:%s;border-radius:8px;margin:10px 0;"> |
| | | <div style="font-size:36px;font-weight:700;color:%s;margin:0 0 5px 0;">%s</div> |
| | | <div style="font-size:14px;font-weight:600;color:%s;text-transform:uppercase;letter-spacing:1px;">%s</div> |
| | | %s |
| | | </div>', |
| | | $this->colours['light-100'], |
| | | $this->colours['action-0'], |
| | | esc_html($number), |
| | | $this->colours['dark-200'], |
| | | esc_html($label), |
| | | $desc |
| | | ); |
| | | } |
| | | |
| | | public function grid(array $items, int $columns = 2, string $title = '', string|array $description = '', string $after = ''):string |
| | | { |
| | | $width = floor(100 / $columns) - 2; // 2% gap |
| | | |
| | | $html = ''; |
| | | if (!empty($title) || !empty($description)) { |
| | | $html .= '<div>'; |
| | | if (!empty($title)) { |
| | | $html .= sprintf( |
| | | '<h2>%s</h2>', |
| | | $title |
| | | ); |
| | | } |
| | | if (!empty($description)) { |
| | | if (is_string($description)) { |
| | | if (str_starts_with($description, '<p>')) { |
| | | $html .= $description; |
| | | }else { |
| | | $html .= sprintf( |
| | | '<p>%s</p>', |
| | | $description |
| | | ); |
| | | } |
| | | } else { |
| | | $html .= implode('',array_map(function ($p) { return sprintf('<p>%s</p>', $p); }, $description)); |
| | | } |
| | | } |
| | | } |
| | | $html .= '<div style="display:table;width:100%;margin:20px 0;">'; |
| | | foreach ($items as $index => $item) { |
| | | if ($index > 0 && $index % $columns === 0) { |
| | | $html .= '</div><div style="display:table;width:100%;margin:20px 0;">'; |
| | | } |
| | | $html .= sprintf( |
| | | '<div style="display:table-cell;width:%d%%;padding:10px;vertical-align:top;">%s</div>', |
| | | $width, |
| | | $item |
| | | ); |
| | | } |
| | | $html .= '</div>'.$after; |
| | | |
| | | |
| | | if (!empty($title) || !empty($description)) { |
| | | $html .= '</div>'; |
| | | } |
| | | return $html; |
| | | } |
| | | |
| | | |
| | | public function image(string $src, string $alt = '', int $maxWidth = 600):string |
| | | { |
| | | return sprintf( |
| | | '<div style="text-align:center;margin:20px 0;"> |
| | | <img src="%s" alt="%s" style="max-width:%dpx;width:100%%;height:auto;border-radius:8px;" /> |
| | | </div>', |
| | | esc_url($src), |
| | | esc_attr($alt), |
| | | $maxWidth |
| | | ); |
| | | } |
| | | |
| | | public function h1(string $text):string |
| | | { |
| | | return sprintf( |
| | | '<h1 style="color:%s;font-size:24px;font-weight:700;margin:20px 0 10px 0;">%s</h1>', |
| | | $this->colours['dark'], |
| | | $text |
| | | ); |
| | | } |
| | | |
| | | public function h2(string $text):string |
| | | { |
| | | return sprintf( |
| | | '<h2 style="color:%s;font-size:20px;font-weight:600;margin:20px 0 10px 0;border-bottom:2px solid %s;padding-bottom:10px;">%s</h2>', |
| | | $this->colours['dark'], |
| | | $this->colours['dark-200'], |
| | | $text |
| | | ); |
| | | } |
| | | |
| | | public function h3(string $text):string |
| | | { |
| | | return sprintf( |
| | | '<h3 style="color:%s;font-size:18px;font-weight:600;margin:15px 0 10px 0;">%s</h3>', |
| | | $this->colours['dark-200'], |
| | | $text |
| | | ); |
| | | } |
| | | |
| | | public function list(array $items, bool $ordered = false):string |
| | | { |
| | | $tag = $ordered ? 'ol' : 'ul'; |
| | | $style = $ordered |
| | | ? 'list-style-type:decimal;padding-left:20px;margin:10px 0;' |
| | | : 'list-style-type:disc;padding-left:20px;margin:10px 0;'; |
| | | |
| | | $html = sprintf('<%s style="%s">', $tag, $style); |
| | | foreach ($items as $item) { |
| | | $html .= sprintf( |
| | | '<li style="margin:5px 0;line-height:1.6;">%s</li>', |
| | | $item |
| | | ); |
| | | } |
| | | $html .= sprintf('</%s>', $tag); |
| | | |
| | | return $html; |
| | | } |
| | | |
| | | public function codeBlock(string $code, string $language = ''):string |
| | | { |
| | | return sprintf( |
| | | '<pre style="background-color:%s;border:1px solid %s;border-radius:4px;padding:15px;overflow-x:auto;font-family:\'Courier New\',monospace;font-size:13px;line-height:1.4;"><code>%s</code></pre>', |
| | | $this->colours['light-100'], |
| | | $this->colours['dark-200'], |
| | | esc_html($code) |
| | | ); |
| | | } |
| | | |
| | | public function badge(string $text, string $type = 'default'):string |
| | | { |
| | | $colors = [ |
| | | 'success' => ['bg' => '#d4edda', 'text' => '#155724'], |
| | | 'warning' => ['bg' => '#fff3cd', 'text' => '#856404'], |
| | | 'error' => ['bg' => '#f8d7da', 'text' => '#721c24'], |
| | | 'info' => ['bg' => '#d1ecf1', 'text' => '#0c5460'], |
| | | 'default' => ['bg' => $this->colours['light-200'], 'text' => $this->colours['dark-200']] |
| | | ]; |
| | | |
| | | $color = $colors[$type] ?? $colors['default']; |
| | | |
| | | return sprintf( |
| | | '<span style="display:inline-block;padding:4px 12px;border-radius:12px;background-color:%s;color:%s;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;">%s</span>', |
| | | $color['bg'], |
| | | $color['text'], |
| | | esc_html($text) |
| | | ); |
| | | } |
| | | |
| | | public function alert(string $text, string $type = 'info'):string |
| | | { |
| | | $configs = [ |
| | | 'success' => ['bg' => '#d4edda', 'border' => '#28a745', 'text' => '#155724', 'icon' => '✓'], |
| | | 'warning' => ['bg' => '#fff3cd', 'border' => '#ffc107', 'text' => '#856404', 'icon' => '⚠'], |
| | | 'error' => ['bg' => '#f8d7da', 'border' => '#dc3545', 'text' => '#721c24', 'icon' => '✕'], |
| | | 'info' => ['bg' => '#d1ecf1', 'border' => $this->colours['action-0'], 'text' => '#0c5460', 'icon' => 'ℹ'] |
| | | ]; |
| | | |
| | | $config = $configs[$type] ?? $configs['info']; |
| | | |
| | | return sprintf( |
| | | '<div style="border-radius:8px;margin:1rem 0;border-left:4px solid %s;padding:1rem;background-color:%s;color:%s;"> |
| | | <strong style="font-size:16px;">%s</strong> %s |
| | | </div>', |
| | | $config['border'], |
| | | $config['bg'], |
| | | $config['text'], |
| | | $config['icon'], |
| | | $text |
| | | ); |
| | | } |
| | | |
| | | public function spacer(int $height = 20):string |
| | | { |
| | | return sprintf('<div style="height:%dpx;"></div>', $height); |
| | | } |
| | | |
| | | public function prefix():string |
| | | { |
| | | return $this->prefix; |
| | | } |
| | | } |
| | | |
| | | |