<?php
|
namespace JVBase\managers;
|
|
use WP_User;
|
|
/**
|
* Edmonton Ink Email Customization
|
*
|
* This file customizes all default WordPress emails to match the Edmonton Ink brand aesthetic.
|
* - Sets emails to HTML format
|
* - Applies consistent branding (logo, colors, typography)
|
* - Customizes content for each email type
|
*
|
*/
|
|
if (!defined('ABSPATH')) {
|
exit; // Exit if accessed directly
|
}
|
|
/**
|
* Hooks into wp_mail to ensure a consistent look in our automatic emails
|
*/
|
|
class EmailManager
|
{
|
|
// Brand colors
|
private string $action_color = '#FF0080';
|
private string $bg_light = '#F9F9F9';
|
private string $bg_dark = '#1B1B1B';
|
private string $text_color = '#151515';
|
private string $text_light = '#F9F9F9';
|
|
// Site info
|
private string $site_name;
|
private string $site_url;
|
private string $signature;
|
|
/**
|
* Constructor - sets up all filters
|
*/
|
public function __construct()
|
{
|
$this->site_name = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES);
|
$this->site_url = get_site_url();
|
$this->signature = jvbSignature();
|
|
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);
|
|
// Password reset emails
|
add_filter('retrieve_password_message', [$this, 'customizePasswordResetEmail'], 10, 4);
|
add_filter('retrieve_password_title', [$this, 'customizePasswordResetTitle'], 10, 3);
|
|
// User email change emails
|
add_filter('email_change_email', [$this, 'customizeEmailChangeEmail'], 10, 3);
|
add_filter('new_user_email_content', [$this, 'customizeNewUserEmailContent'], 10, 2);
|
|
// Password change notification
|
add_filter('password_change_email', [$this, 'customizePasswordChangeEmail'], 10, 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);
|
}
|
|
/**
|
* Helper to set content type to HTML
|
* @return string
|
*/
|
public function setHtmlContentType():string
|
{
|
return 'text/html';
|
}
|
|
/**
|
* Send a styled email using the common template
|
*
|
* @param string $to Recipient email address
|
* @param string $subject Email subject line
|
* @param string $message Email body content (can contain HTML)
|
* @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
|
{
|
// 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);
|
|
// Send the email
|
$result = wp_mail($to, $subject, $formatted_message);
|
|
// Reset content type filter to avoid affecting other emails
|
remove_filter('wp_mail_content_type', [$this, 'setHtmlContentType']);
|
|
return $result;
|
}
|
/**
|
* Common email wrapper template
|
* @param string $content
|
* @param string $header
|
*
|
* @return string
|
*/
|
private function getEmailTemplate(string $content, string $header = ''):string
|
{
|
// Get logo URL (replace with your actual logo path)
|
$logo_url = esc_url(wp_get_attachment_image_url(get_theme_mod('custom_logo'), 'medium'));
|
|
// Default header if none provided
|
if (empty($header)) {
|
$header = 'edmonton.ink';
|
}
|
|
return '<!DOCTYPE html>
|
<html>
|
<head>
|
<meta charset="UTF-8">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>' . $this->site_name . '</title>
|
<style>
|
body, table, td, p, a, li, blockquote {
|
line-height: 1.5;
|
}
|
|
body {
|
background-color: ' . $this->bg_light . ';
|
margin: 0;
|
padding: 0;
|
color: ' . $this->text_color . ';
|
}
|
|
.email-container {
|
max-width: 600px;
|
margin: 0 auto;
|
padding: 20px;
|
}
|
|
.header img {
|
max-width: 120px;
|
height: auto;
|
}
|
|
.header {
|
background-color: ' . $this->bg_dark . ';
|
padding: 5px;
|
text-align: center;
|
color: ' . $this->text_light . ';
|
font-size: 24px;
|
font-weight: 900;
|
letter-spacing: 1.5px;
|
text-transform: uppercase;
|
border-top-left-radius: 5px;
|
border-top-right-radius: 5px;
|
}
|
.header p,
|
.header a {
|
display: inline-block;
|
vertical-align: middle;
|
}
|
.header p {
|
margin: 0;
|
margin-left: auto;
|
}
|
|
.content {
|
background-color: #ffffff;
|
padding: 30px;
|
border-bottom-left-radius: 5px;
|
border-bottom-right-radius: 5px;
|
}
|
|
.button {
|
display: inline-block;
|
padding: 12px 24px;
|
background-color: ' . $this->action_color . ';
|
color: ' . $this->text_light . ' !important;
|
text-decoration: none;
|
border-radius: 3px;
|
font-weight: bold;
|
margin: 15px 0;
|
letter-spacing: 1px;
|
text-transform: uppercase;
|
}
|
|
.text-link {
|
color: ' . $this->action_color . ';
|
text-decoration: underline;
|
}
|
|
.footer {
|
text-align: center;
|
margin-top: 20px;
|
font-size: 12px;
|
color: #666666;
|
padding: 10px;
|
line-height: 1.6;
|
}
|
|
.divider {
|
border-top: 1px solid #dddddd;
|
margin: 25px 0;
|
}
|
|
@media screen and (max-width: 600px) {
|
.email-container {
|
width: 100% !important;
|
padding: 10px !important;
|
}
|
|
.content, .header {
|
padding: 15px !important;
|
}
|
}
|
|
@media screen and (max-width: 600px) {
|
/* Table adjustments for content rows */
|
table, tbody, tr, td {
|
display: block !important;
|
width: 100% !important;
|
clear: both !important;
|
}
|
|
/* Make images responsive */
|
img {
|
max-width: 100% !important;
|
height: auto !important;
|
}
|
|
/* Increase tap target sizes */
|
.button {
|
display: block !important;
|
text-align: center !important;
|
padding: 16px 10px !important;
|
margin: 15px auto !important;
|
width: 80% !important;
|
}
|
|
/* Typography adjustments */
|
h1 {
|
font-size: 24px !important;
|
}
|
h2 {
|
font-size: 20px !important;
|
}
|
h3 {
|
font-size: 18px !important;
|
}
|
|
/* Content spacing */
|
.content {
|
padding: 15px 10px !important;
|
}
|
|
/* Stack list items better */
|
li {
|
margin-bottom: 10px !important;
|
}
|
|
/* Adjust content cells for better stacking */
|
td {
|
padding: 10px 0 !important;
|
text-align: center !important;
|
}
|
|
/* Header adjustments */
|
.header img {
|
max-width: 100px !important;
|
}
|
|
.header p {
|
font-size: 18px !important;
|
margin: 5px 0 !important;
|
display: block !important; /* Force stacking */
|
}
|
}
|
|
/* Dark mode support for clients that support it */
|
@media (prefers-color-scheme: dark) {
|
.email-container {
|
background-color: #1B1B1B !important;
|
}
|
|
.content {
|
background-color: #2A2A2A !important;
|
color: #F9F9F9 !important;
|
}
|
|
h1, h2, h3, p, li, a.text-link {
|
color: #F9F9F9 !important;
|
}
|
|
.footer {
|
color: #999999 !important;
|
}
|
|
.divider {
|
border-color: #444444 !important;
|
}
|
}
|
</style>
|
</head>
|
<body>
|
<div class="email-container">
|
|
<div class="header">
|
<a href="' . $this->site_url . '">
|
<img src="' . $logo_url . '" alt="Edmonton Ink">
|
</a>
|
<p>' . $header . '</p>
|
</div>
|
<div class="content">
|
' . $content . '
|
</div>
|
<div class="footer">
|
<p>© ' . date('Y') . ' edmonton.ink — Your tattoo scene on your screen.</p>
|
<p><a href="' . $this->site_url . '" class="text-link">edmonton.ink</a></p>
|
</div>
|
</div>
|
</body>
|
</html>';
|
}
|
|
/**
|
* New user registration email to user
|
*/
|
public function customizeNewUserEmail($wp_new_user_notification_email, $user, $blogname)
|
{
|
$user_login = $user->user_login;
|
|
// Only create the password key if the user can change their password
|
$key = get_password_reset_key($user);
|
if (!is_wp_error($key)) {
|
$reset_url = network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login');
|
|
$message = sprintf('<p>Hey %s!</p>', $user->first_name);
|
$message .= '<p>Thanks for joining Edmonton\'s tattoo scene. Here\'s your login information:</p>';
|
$message .= sprintf('<p><strong>Username:</strong> %s</p>', $user_login);
|
$message .= '<p>To set your password and access your account, click the button below:</p>';
|
$message .= sprintf('<p style="text-align: center;"><a href="%s" class="button">Set Your Password</a></p>', $reset_url);
|
$message .= '<p>Or copy and paste this link into your browser:</p>';
|
$message .= sprintf('<p style="user-select:all;">%s</p>', $reset_url);
|
$message .= '<p>This link will expire in 24 hours, for security reasons.</p>';
|
if (in_array('jvb_artist', array_values($user->roles))) {
|
$message .= '<div class="divider"></div>';
|
$message .= '<h3>NOTE:</h3>
|
<p>Once you set your password, you\'ll have access to your custom dashboard where you can:</p>
|
<ul>
|
<li>Manage your profile information</li>
|
<li>Upload tattoos/piercings, and artwork</li>
|
</ul>
|
<p>Nothing will be published until you\'ve been approved by 3 already approved artists, or the admin.</p>
|
<p>Admins check every day or three, but, if you are in a rush, you can contact us directly by replying to this email, or texting us at 825-925-9916.</p>';
|
} elseif (in_array('jvb_partner', array_values($user->roles))) {
|
$message .= '<div class="divider"></div>';
|
$message .= '<h3>NOTE:</h3>
|
<p>Once you set your password, you\'ll have access to your custom dashboard where you can:</p>
|
<ul>
|
<li>Manage your profile information</li>
|
<li>Create offers for enthusiasts or partners or both</li>
|
</ul>
|
<p>Nothing will be published until you\'ve been approved by the admin.</p>
|
<p>Admins check every day or three, but, if you are in a rush, you can contact us directly by replying to this email, or texting us at 825-925-9916.</p>
|
<p><strong>Note:</strong>Even after approval by admin, your ability to publish depends on your karmic standing by artists. Artists each have a vote they can cast (UP or DOWN) - if your karmic score dips too far in the negative, you account is subject to reconsideration or even a ban.</p>';
|
}
|
$message .= '<div class="divider"></div>';
|
$message = apply_filters(BASE.'new_user_email_content', $message, $user);
|
|
$message .= '<p>If you didn\'t create this account, please ignore this email and the link will expire.</p>';
|
$message .= sprintf('<p>Ink on, %s</p>', $user->first_name);
|
$message .= $this->signature;
|
$wp_new_user_notification_email['message'] = $this->getEmailTemplate($message, 'Welcome to edmonton.ink');
|
}
|
|
// Change the subject line
|
$wp_new_user_notification_email['subject'] = sprintf('Welcome to %s! Confirm your account', $this->site_name);
|
|
return $wp_new_user_notification_email;
|
}
|
|
/**
|
* New user registration email to admin
|
* @param array $emailData
|
* @param WP_User $user
|
* @param string $blogname
|
*
|
* @return array
|
*/
|
public function customizeNewUserEmailAdmin(array $emailData, WP_User $user, string $blogname):array
|
{
|
$message = '<p>A new user has registered on <strong>' . $this->site_name . '</strong>:</p>';
|
$message .= '<p><strong>Username:</strong> ' . $user->user_login . '<br>';
|
$message .= '<strong>Email:</strong> ' . $user->user_email . '<br>';
|
$message .= '<strong>Role:</strong> ' . str_replace(BASE, '', array_values($user->roles)[0]).'</p>';
|
$message .= $this->signature;
|
$emailData['message'] = $this->getEmailTemplate($message, 'New User Registration');
|
$emailData['subject'] = '[' . $this->site_name . '] New ' . str_replace(BASE, '', array_values($user->roles)[0]).': '.$user->display_name;
|
|
return $emailData;
|
}
|
|
/**
|
* Password reset email
|
* @param string $message
|
* @param string $key
|
* @param string $user_login
|
* @param WP_User $user_data
|
*
|
* @return string
|
*/
|
public function customizePasswordResetEmail(string $message, string $key, string $user_login, WP_User $user_data):string
|
{
|
$reset_url = network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login');
|
|
$content = '<p>Hey bud,</p>';
|
$content = '<p>We received a request to reset the password for an account associated with this email:</p>';
|
$content .= '<p><strong>Username:</strong> ' . $user_login . '</p>';
|
$content .= '<p>If you didn\'t make this request, you can safely ignore this email and nothing will happen to your account.</p>';
|
$content .= '<p>To reset your password, click the button below:</p>';
|
$content .= '<p style="text-align: center;"><a href="' . $reset_url . '" class="button">Reset Password</a></p>';
|
$content .= '<p>Or copy and paste this link into your browser:</p>';
|
$content .= '<p style="user-select:all;">' . $reset_url . '</p>';
|
$content .= '<div class="divider"></div>';
|
$content .= '<p>This password reset link is only valid for 24 hours.</p>';
|
$content .= $this->signature;
|
return $this->getEmailTemplate($content, 'Password Reset');
|
}
|
|
/**
|
* Customize the password reset email title
|
* @param string $title
|
* @param string $user_login
|
* @param WP_User $user_data
|
* @return string
|
*/
|
public function customizePasswordResetTitle(string $title, string $user_login, WP_User $user_data):string
|
{
|
return '[' . $this->site_name . '] Password Reset Request';
|
}
|
|
/**
|
* Email change notification to admin
|
* @param array $email_change_email
|
* @param array $user
|
* @param array $userdata
|
*
|
* @return array
|
*/
|
public function customizeEmailChangeEmail(array $email_change_email, array $user, array $userdata):array
|
{
|
$content = sprintf('<p>Hi %s,</p>', $user['first_name']);
|
$content .= '<p>Ideally you already know this: you asked to change your email, and here we are.</p>';
|
$content .= '<p><strong>Old Email:</strong> ' . $email_change_email['to'] . '<br>';
|
$content .= '<strong>New Email:</strong> ' . $user['user_email'] . '</p>';
|
|
$content .= '<p>If this is news, or you did not change your email, please contact us immediately. You can <a href="sms:+18258239916">text us</a> or reply to this email."></a></p>';
|
$content .= '<p style="text-align: center;"><a href="' . wp_login_url() . '" class="button">Log In to Your Account</a></p>';
|
$content .= $this->signature;
|
$email_change_email['message'] = $this->getEmailTemplate($content, 'Email Address Changed');
|
$email_change_email['subject'] = '[' . $this->site_name . '] Email Address Changed';
|
|
return $email_change_email;
|
}
|
|
/**
|
* New email address confirmation
|
* @param string $email_text
|
* @param array $user
|
*
|
* @return string
|
*/
|
public function customizeNewUserEmailContent(string $email_text, array $user):string
|
{
|
|
// Extract the confirmation link from the default message
|
$pattern = '/###CONFIRM_URL### (.*?)(?=\n|\r|$)/';
|
preg_match($pattern, $email_text, $matches);
|
$confirm_url = isset($matches[1]) ? $matches[1] : admin_url('profile.php?newuseremail=' . $user['hash']);
|
|
$content = '<p>Hey human,</p>';
|
$content .= '<p>Seems you want to change the email associated with your account.</p>';
|
$content .= '<p>You\'d like to change your email to: <strong>' . $user['newemail'] . '</strong></p>';
|
$content .= '<p>Please confirm this change by clicking the button below:</p>';
|
$content .= '<p style="text-align: center;"><a href="' . $confirm_url . '" class="button">Confirm Email Change</a></p>';
|
$content .= '<p>Or copy and paste this link into your browser:</p>';
|
$content .= '<p style="user-select:all">' . $confirm_url . '</p>';
|
$content .= '<div class="divider"></div>';
|
$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');
|
}
|
|
/**
|
* Password change notification
|
* @param array $pass_change_email
|
* @param array $user
|
* @param array $userdata
|
*
|
* @return array
|
*/
|
public function customizePasswordChangeEmail(array $pass_change_email, array $user, array $userdata):array
|
{
|
|
$content = '<p>Hey bud,</p>';
|
$content .= '<p>Ideally, you\'re expecting this email. You wanted to change your password, and this is to let you know that it\'s definitely updated.</p>';
|
$content .= '<p>If you did not change your password, please contact us immediately.</p>';
|
$content .= '<p>You can <a href="sms:+18259257398">text us</a>, or reply to this email.</p>';
|
$content .= '<p style="text-align: center;"><a href="' . wp_login_url() . '" class="button">Log In to Your Account</a></p>';
|
$content .= $this->signature;
|
|
$pass_change_email['message'] = $this->getEmailTemplate($content, 'Password Changed');
|
$pass_change_email['subject'] = '[' . $this->site_name . '] Password Changed';
|
|
return $pass_change_email;
|
}
|
|
/**
|
* User data request confirmation email
|
* @param string $content
|
* @param array $request_data
|
*
|
* @return string
|
*/
|
public function customizeUserRequestEmail(string $content, array $request_data):string
|
{
|
|
// Extract the confirmation URL from the content
|
$pattern = '/<a href="([^"]+)"[^>]*>/';
|
preg_match($pattern, $content, $matches);
|
$confirm_url = isset($matches[1]) ? $matches[1] : '';
|
|
$request_type = $request_data['action_name'];
|
$request_name = '';
|
|
switch ($request_type) {
|
case 'export_personal_data':
|
$request_name = 'Export Personal Data';
|
break;
|
case 'remove_personal_data':
|
$request_name = 'Erase Personal Data';
|
break;
|
default:
|
$request_name = 'Data Request';
|
}
|
|
$message = '<p>Hi privacy enthusiast,</p>';
|
$message .= '<p>A request has been made to perform the following action on your account:</p>';
|
$message .= '<p><strong>' . $request_name . '</strong></p>';
|
$message .= '<p>If you made this request, you can confirm it by clicking the button below:</p>';
|
$message .= '<p style="text-align: center;"><a href="' . $confirm_url . '" class="button">Confirm Request</a></p>';
|
$message .= '<p>Or copy and paste this link into your browser:</p>';
|
$message .= '<p style="user-select:all;">' . $confirm_url . '</p>';
|
$message .= '<div class="divider"></div>';
|
$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');
|
}
|
|
/**
|
* Personal data export email
|
* @param string $content
|
* @param int $request_id
|
* @param array $email_data
|
*
|
* @return string
|
*/
|
public function customizePersonalDataEmail(string $content, int $request_id, array $email_data):string
|
{
|
|
// Extract the download link
|
$pattern = '/<a href="([^"]+)"[^>]*>/';
|
preg_match($pattern, $content, $matches);
|
$download_url = isset($matches[1]) ? $matches[1] : '';
|
|
$message = '<p>Your request for an export of personal data has been completed.</p>';
|
$message .= '<p>You can download your personal data by clicking the button below.</p>';
|
$message .= '<p style="text-align: center;"><a href="' . $download_url . '" class="button">Download Personal Data</a></p>';
|
$message .= '<p>Or copy and paste this link into your browser:</p>';
|
$message .= '<p style="user-select:all;">' . $download_url . '</p>';
|
$message .= '<div class="divider"></div>';
|
$message .= '<p><strong>Important:</strong> For privacy reasons, this link will expire in ' . $email_data['expiration'] . '.</p>';
|
$message .= $this->signature;
|
|
return $this->getEmailTemplate($message, 'Your Personal Data Export');
|
}
|
}
|
|
new EmailManager();
|