array( 'subject' => 'Your {service_name} is confirmed – {appointment_date}', 'body' => "Hi {customer_first_name},\n\nGreat news! Your {service_name} with {employee_name} is confirmed for {appointment_date} at {appointment_time}.\n\nWhere: {location}\nTotal: {appointment_price}\n\nNeed to change something? Manage your booking here:\n{manage_link}\n\nSee you soon,\n{company_name}", ), 'customer_pending' => array( 'subject' => 'We received your booking request', 'body' => "Hi {customer_first_name},\n\nThanks for booking {service_name} on {appointment_date} at {appointment_time}. We'll confirm it shortly.\n\nManage your booking: {manage_link}\n\n{company_name}", ), 'customer_canceled' => array( 'subject' => 'Your appointment was canceled', 'body' => "Hi {customer_first_name},\n\nYour {service_name} on {appointment_date} at {appointment_time} has been canceled.\n\nBook again anytime: {booking_page}\n\n{company_name}", ), 'customer_rescheduled' => array( 'subject' => 'Your appointment was moved to {appointment_date}', 'body' => "Hi {customer_first_name},\n\nYour {service_name} is now on {appointment_date} at {appointment_time} with {employee_name}.\n\nManage your booking: {manage_link}\n\n{company_name}", ), 'employee_new' => array( 'subject' => 'New booking: {service_name} on {appointment_date}', 'body' => "Hi {employee_name},\n\nYou have a new appointment.\n\nService: {service_name}\nWhen: {appointment_date} {appointment_time}\nCustomer: {customer_full_name} ({customer_email}, {customer_phone})\nNote: {customer_note}\n\n{company_name}", ), 'admin_new' => array( 'subject' => '[{company_name}] New booking #{appointment_id}', 'body' => "New booking received.\n\nService: {service_name}\nEmployee: {employee_name}\nWhen: {appointment_date} {appointment_time}\nCustomer: {customer_full_name} ({customer_email})\nStatus: {appointment_status}\nPrice: {appointment_price}", ), ); // Customers change bookings by contacting the business, so the emails carry no manage link. foreach ( $t as $k => $x ) { $t[ $k ]['body'] = preg_replace( '/\n\n[^\n]*\n?\{manage_link\}/', '', $x['body'] ); } return $t; } public static function placeholders() { return array( 'customer_first_name', 'customer_full_name', 'customer_email', 'customer_phone', 'customer_note', 'service_name', 'employee_name', 'appointment_id', 'appointment_date', 'appointment_time', 'appointment_end_time', 'appointment_price', 'appointment_status', 'location', 'booking_page', 'company_name', 'company_phone' ); } public function on_status( $appt, $old, $new ) { $n = Settings::get( 'notifications' ); $appt = Repository::appointment( $appt['id'] ) ?: $appt; // fresh copy with all joined fields if ( null === $old ) { // Brand new booking. if ( ! empty( $n['admin_new'] ) ) { self::send( 'admin_new', $appt, Settings::get( 'company_email' ) ); } if ( ! empty( $n['employee_new'] ) && ! empty( $appt['employee_email'] ) && ! self::$suppress_employee ) { self::send( 'employee_new', $appt, $appt['employee_email'] ); } } $map = array( 'approved' => 'customer_approved', 'pending' => 'customer_pending', 'canceled' => 'customer_canceled', 'rejected' => 'customer_canceled', ); if ( isset( $map[ $new ] ) && ! empty( $n[ $map[ $new ] ] ) && ! self::$suppress_customer ) { self::send( $map[ $new ], $appt, $appt['customer_email'], 'approved' === $new ); } } /** Plain email in the branded layout. */ public static function mail( $to, $subject, $text, array $attachments = array() ) { if ( ! is_email( $to ) ) { return false; } $body = self::layout( nl2br( esc_html( $text ) ) ); $body = preg_replace( '~(https?://[^\s<]+)~', '$1', $body ); return wp_mail( $to, $subject, $body, array( 'Content-Type: text/html; charset=UTF-8', 'From: ' . Settings::get( 'company_name' ) . ' <' . Settings::get( 'company_email' ) . '>' ), $attachments ); } public function on_reschedule( $appt ) { $appt = Repository::appointment( $appt['id'] ) ?: $appt; if ( ! self::$suppress_customer ) { self::send( 'customer_rescheduled', $appt, $appt['customer_email'], true ); } if ( ! empty( $appt['employee_email'] ) && ! self::$suppress_employee ) { self::send( 'employee_new', $appt, $appt['employee_email'] ); } } public static function render( $text, array $appt ) { $s = Settings::all(); $vars = array( 'customer_first_name' => $appt['customer_first_name'] ?? '', 'customer_full_name' => $appt['customer_name'] ?? '', 'customer_email' => $appt['customer_email'] ?? '', 'customer_phone' => $appt['customer_phone'] ?? '', 'customer_note' => $appt['customer_note'] ?? '', 'service_name' => $appt['service_name'] ?? '', 'employee_name' => trim( $appt['employee_name'] ?? '' ), 'appointment_id' => $appt['id'] ?? '', 'appointment_date' => isset( $appt['start_utc'] ) ? wp_date( get_option( 'date_format' ), strtotime( $appt['start_utc'] . ' UTC' ) ) : '', 'appointment_time' => isset( $appt['start_utc'] ) ? wp_date( get_option( 'time_format' ), strtotime( $appt['start_utc'] . ' UTC' ) ) : '', 'appointment_end_time' => isset( $appt['end_utc'] ) ? wp_date( get_option( 'time_format' ), strtotime( $appt['end_utc'] . ' UTC' ) ) : '', 'appointment_price' => $s['currency_symbol'] . number_format_i18n( (float) ( $appt['price'] ?? 0 ), 2 ), 'appointment_status' => ucfirst( $appt['status'] ?? '' ), 'location' => self::location( $appt ), 'booking_page' => home_url( '/' ), 'company_name' => $s['company_name'], 'company_phone' => $s['company_phone'], ); $vars = apply_filters( 'bookzenix_placeholders', $vars, $appt ); foreach ( $vars as $k => $v ) { $text = str_replace( '{' . $k . '}', (string) $v, $text ); } return $text; } /** Human-readable "where": meeting link, address, or business name. */ public static function location( array $appt ) { return ! empty( $appt['location_text'] ) ? $appt['location_text'] : Settings::get( 'company_name' ); } public static function send( $template, array $appt, $to, $attach_ics = false ) { if ( ! is_email( $to ) ) { return false; } $tpls = Settings::get( 'templates' ); $tpl = $tpls[ $template ] ?? ( self::default_templates()[ $template ] ?? null ); if ( ! $tpl ) { return false; } $subject = self::render( $tpl['subject'], $appt ); $body = self::layout( nl2br( esc_html( self::render( $tpl['body'], $appt ) ) ) ); // Make the manage link clickable. $body = preg_replace( '~(https?://[^\s<]+)~', '$1', $body ); $headers = array( 'Content-Type: text/html; charset=UTF-8', 'From: ' . Settings::get( 'company_name' ) . ' <' . Settings::get( 'company_email' ) . '>' ); $attachments = array(); if ( $attach_ics ) { $file = self::ics_file( $appt ); if ( $file ) { $attachments[] = $file; } } $ok = wp_mail( $to, $subject, $body, $headers, $attachments ); foreach ( $attachments as $f ) { wp_delete_file( $f ); } global $wpdb; $wpdb->insert( Plugin::table( 'notification_log' ), array( 'appointment_id' => $appt['id'] ?? null, 'template' => $template, 'recipient' => $to, 'success' => $ok ? 1 : 0, 'created_at' => current_time( 'mysql', true ), ) ); return $ok; } public static function layout( $inner ) { $c = esc_attr( Settings::get( 'primary_color' ) ); $name = esc_html( Settings::get( 'company_name' ) ); return '
' . '| ' . ' |