From df40c2e7dcbf610eb4d238e21717e0a2c575e8bf Mon Sep 17 00:00:00 2001
From: Piotr Potrebka <piotr.potrebka@wpdesk.net>
Date: Tue, 6 Aug 2024 08:36:49 +0200
Subject: [PATCH] feat: email styles

---
 composer.json                                 |  10 +-
 phpunit-unit.xml                              |  14 +-
 src/Abstracts/ConditionInterface.php          |  11 -
 src/Abstracts/Email.php                       | 199 ++++++++++++
 src/Abstracts/EmailAbstract.php               | 306 ------------------
 src/Abstracts/EmailInterface.php              | 101 ------
 src/Abstracts/Mailer.php                      |  19 ++
 src/EmailTemplate.php                         |  42 +++
 src/{ => Exceptions}/MailerException.php      |   2 +-
 .../{Template.php => ColorConversion.php}     |   2 +-
 src/Helpers/{HTML.php => StyleInliner.php}    |  17 +-
 src/Mailer.php                                | 216 -------------
 src/WPMailer.php                              |  61 ++++
 templates/html/email-footer.php               |  12 -
 templates/html/email-header.php               |  12 -
 templates/html/email-styles.php               |  22 +-
 tests/unit/Mailer/ColorTest.php               |  51 +++
 tests/unit/Mailer/MailTest.php                |  87 +++++
 tests/unit/bootstrap.php                      |  17 +
 19 files changed, 514 insertions(+), 687 deletions(-)
 delete mode 100644 src/Abstracts/ConditionInterface.php
 create mode 100644 src/Abstracts/Email.php
 delete mode 100644 src/Abstracts/EmailAbstract.php
 delete mode 100644 src/Abstracts/EmailInterface.php
 create mode 100644 src/Abstracts/Mailer.php
 create mode 100644 src/EmailTemplate.php
 rename src/{ => Exceptions}/MailerException.php (88%)
 rename src/Helpers/{Template.php => ColorConversion.php} (99%)
 rename src/Helpers/{HTML.php => StyleInliner.php} (63%)
 delete mode 100644 src/Mailer.php
 create mode 100644 src/WPMailer.php
 create mode 100644 tests/unit/Mailer/ColorTest.php
 create mode 100644 tests/unit/Mailer/MailTest.php

diff --git a/composer.json b/composer.json
index 193f1c9..672f320 100644
--- a/composer.json
+++ b/composer.json
@@ -41,16 +41,18 @@
     "require-dev": {
         "10up/wp_mock": "*",
         "mockery/mockery": "*",
-        "phpunit/phpunit": "<7",
-        "wp-coding-standards/wpcs": "^2.3.0",
-        "squizlabs/php_codesniffer": "^3.0.2"
+        "phpunit/phpunit": "<7"
     },
     "autoload": {
         "psr-4": {
             "WPDesk\\Library\\WPEmail\\": "src"
         }
     },
-    "autoload-dev": {},
+    "autoload-dev": {
+        "psr-4": {
+            "Tests\\Mailer\\": "tests/unit"
+        }
+    },
     "extra": {
         "text-domain": "wp-email",
         "translations-folder": "lang",
diff --git a/phpunit-unit.xml b/phpunit-unit.xml
index f3b7ead..74290fa 100644
--- a/phpunit-unit.xml
+++ b/phpunit-unit.xml
@@ -1,21 +1,17 @@
 <phpunit bootstrap="tests/unit/bootstrap.php">
     <testsuites>
-        <testsuite>
-            <directory prefix="Test_" suffix=".php">./tests/unit/</directory>
+        <testsuite name="unit">
+            <directory suffix="Test.php">./tests/unit/</directory>
         </testsuite>
     </testsuites>
 
     <filter>
         <whitelist>
             <directory suffix=".php">src</directory>
+            <exclude>
+                <directory suffix=".php">src/templates</directory>
+            </exclude>
         </whitelist>
     </filter>
 
-    <logging>
-        <log type="junit" target="build-coverage/report.junit.xml"/>
-        <log type="coverage-html" target="build-coverage/coverage" charset="UTF-8" yui="true" highlight="true"/>
-        <log type="coverage-text" target="build-coverage/coverage.txt"/>
-        <log type="coverage-clover" target="build-coverage/clover.xml"/>
-    </logging>
-
 </phpunit>
diff --git a/src/Abstracts/ConditionInterface.php b/src/Abstracts/ConditionInterface.php
deleted file mode 100644
index 94bc256..0000000
--- a/src/Abstracts/ConditionInterface.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-namespace WPDesk\Library\WPEmail\Abstracts;
-
-use WPDesk\View\Renderer\Renderer;
-
-interface ConditionInterface {
-
-    public function is_valid(): bool;
-
-}
diff --git a/src/Abstracts/Email.php b/src/Abstracts/Email.php
new file mode 100644
index 0000000..e3c35d1
--- /dev/null
+++ b/src/Abstracts/Email.php
@@ -0,0 +1,199 @@
+<?php
+
+namespace WPDesk\Library\WPEmail\Abstracts;
+
+use Exception;
+
+class Email {
+
+    /**
+     * @var array
+     */
+    private $recipients = [];
+
+    /**
+     * @var string
+     */
+    private $subject = '';
+
+    /**
+     * @var array
+     */
+    private $attachments = [];
+
+    /**
+     * @var string
+     */
+    private $content = '';
+
+    /**
+     * @var array
+     */
+    private $headers = [ 'Content-Type' => 'text/html' ];
+
+    /**
+     * @var string
+     */
+    private $from_email;
+
+    /**
+     * @var string
+     */
+    private $from_name;
+
+    /**
+     * @param string $from_email
+     *
+     * @return self
+     */
+    public function set_from( string $from_email ): self {
+        $this->from_email = $from_email;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function get_from(): string {
+        return sanitize_email( $this->from_email );
+    }
+
+    /**
+     * @param string $from_name
+     *
+     * @return self
+     */
+    public function set_from_name( string $from_name ): self {
+        $this->from_name = $from_name;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function get_from_name(): string {
+        return wp_specialchars_decode( esc_html( $this->from_name ), ENT_QUOTES );
+    }
+
+    /**
+     * @param string $subject
+     *
+     * @return self
+     */
+    public function set_subject( string $subject ): self {
+        $this->subject = $subject;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     * @throws Exception
+     */
+    public function get_subject(): string {
+        return $this->subject;
+    }
+
+    /**
+     * @param array $recipients
+     *
+     * @return self
+     */
+    public function set_recipients( array $recipients = [] ): self {
+        $this->recipients = $recipients;
+
+        return $this;
+    }
+
+    /**
+     * @return string[]
+     */
+    public function get_recipients(): array {
+        return $this->recipients;
+    }
+
+    public function set_header( string $header, string $value ): self {
+        $this->headers[ $header ] = $value;
+
+        return $this;
+    }
+
+    /** @return string[] */
+    public function get_headers(): array {
+        $result = [];
+        foreach ( $this->headers as $header => $value ) {
+            $result[] = "$header: $value";
+        }
+
+        return $result;
+    }
+
+    public function get_header( string $header ): string {
+        return $this->headers[ $header ] ?? '';
+    }
+
+
+    /**
+     * @param array $attachments
+     *
+     * @return self
+     */
+    public function set_attachments( array $attachments ): self {
+        $this->attachments = $attachments;
+
+        return $this;
+    }
+
+    /**
+     * @return array
+     */
+    public function get_attachments(): array {
+        return $this->attachments;
+    }
+
+    public function is_html(): bool {
+        return $this->get_header( 'Content-Type' ) === 'text/html';
+    }
+
+    /**
+     * @return string
+     */
+    public function set_content_type( $type = 'html' ): self {
+        switch ( $type ) {
+            case 'plain':
+                $content_type = 'text/plain';
+                break;
+            case 'multipart':
+                $content_type = 'multipart/alternative';
+                break;
+            default:
+                $content_type = 'text/html';
+        }
+
+        $this->set_header( 'Content-Type', $content_type );
+
+        return $this;
+    }
+
+    /**
+     * @param string $content
+     *
+     * @return self
+     */
+    public function set_content( string $content ): self {
+        $this->content = $content;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     * @throws Exception
+     */
+    public function get_content(): string {
+        return $this->content;
+    }
+
+}
diff --git a/src/Abstracts/EmailAbstract.php b/src/Abstracts/EmailAbstract.php
deleted file mode 100644
index 2e326ea..0000000
--- a/src/Abstracts/EmailAbstract.php
+++ /dev/null
@@ -1,306 +0,0 @@
-<?php
-
-namespace WPDesk\Library\WPEmail\Abstracts;
-
-use Exception;
-
-abstract class EmailAbstract implements EmailInterface {
-
-    /**
-     * @var bool
-     */
-    private $is_enable;
-
-    /**
-     * @var array
-     */
-    private $recipients = [];
-
-    /**
-     * @var array
-     */
-    private $placeholders = [];
-
-    /**
-     * @var string
-     */
-    private $subject = '';
-
-    /**
-     * @var string
-     */
-    private $heading = '';
-
-    /**
-     * @var array
-     */
-    private $attachments = [];
-
-    /**
-     * @var string
-     */
-    private $content = '';
-
-    /**
-     * @var string
-     */
-    private $type = 'html';
-
-    /**
-     * @var array
-     */
-    private $headers = [ 'Content-Type: text/html; charset=UTF-8' ];
-
-    /**
-     * @var string
-     */
-    private $from_email;
-
-    /**
-     * @var string
-     */
-    private $from_name;
-
-    /**
-     * @return string
-     */
-    abstract public function get_id(): string;
-
-    /**
-     * @return string
-     */
-    public function get_template_name(): string {
-        return 'default';
-    }
-
-    /**
-     * @return string
-     */
-    public function get_from(): string {
-        return sanitize_email( $this->from_email );
-    }
-
-    /**
-     * @param string $from_email
-     *
-     * @return self
-     */
-    public function set_from( string $from_email ): self {
-        $this->from_email = $from_email;
-
-        return $this;
-    }
-
-    /**
-     * @param string $from_name
-     *
-     * @return self
-     */
-    public function set_from_name( string $from_name ): self {
-        $this->from_name = $from_name;
-
-        return $this;
-    }
-
-    /**
-     * @return string
-     */
-    public function get_from_name(): string {
-        return wp_specialchars_decode( esc_html( $this->from_name ), ENT_QUOTES );
-    }
-
-    /**
-     * @return bool
-     */
-    public function get_is_enable(): bool {
-        return $this->is_enable;
-    }
-
-    /**
-     * @param $enable
-     *
-     * @return EmailAbstract
-     */
-    public function set_is_enable( $enable ): self {
-        $this->is_enable = $enable;
-
-        return $this;
-    }
-
-    /**
-     * @param array $placeholders
-     *
-     * @return self
-     */
-    public function set_placeholders( array $placeholders = [] ): self {
-        $this->placeholders = array_merge( $this->placeholders, $placeholders );
-
-        return $this;
-    }
-
-    /**
-     * @return string[]
-     */
-    public function get_placeholders(): array {
-        return $this->placeholders;
-    }
-
-    /**
-     * @param string $subject
-     *
-     * @return self
-     */
-    public function set_subject( string $subject ): self {
-        $this->subject = $subject;
-
-        return $this;
-    }
-
-    /**
-     * @return string
-     * @throws Exception
-     */
-    public function get_subject(): string {
-        if ( ! $this->subject ) {
-            throw new Exception( 'Empty email subject' );
-        }
-
-        return $this->replace_placeholders( $this->subject );
-    }
-
-    /**
-     * @param string $heading
-     *
-     * @return self
-     */
-    public function set_heading( string $heading ): self {
-        $this->heading = $heading;
-
-        return $this;
-    }
-
-    /**
-     * @return string
-     */
-    public function get_heading(): string {
-        return $this->replace_placeholders( $this->heading );
-    }
-
-    /**
-     * @param array $recipients
-     *
-     * @return self
-     */
-    public function set_recipients( array $recipients = [] ): self {
-        $this->recipients = $recipients;
-
-        return $this;
-    }
-
-    /**
-     * @return string[]
-     */
-    public function get_recipients(): array {
-        return $this->recipients;
-    }
-
-    /**
-     * @param array $headers
-     *
-     * @return self
-     */
-    public function set_headers( array $headers = [] ): self {
-        $this->headers = $headers;
-
-        return $this;
-    }
-
-    /**
-     * @return string[]
-     */
-    public function get_headers(): array {
-        return $this->headers;
-    }
-
-    /**
-     * @param array $attachments
-     *
-     * @return self
-     */
-    public function set_attachments( array $attachments ): self {
-        $this->attachments = $attachments;
-
-        return $this;
-    }
-
-    /**
-     * @return array
-     */
-    public function get_attachments(): array {
-        return $this->attachments;
-    }
-
-    /**
-     * @param string $type
-     *
-     * @return self
-     */
-    public function set_type( string $type = 'html' ): self {
-        $this->type = $type;
-
-        return $this;
-    }
-
-    /**
-     * @return string
-     */
-    public function get_type(): string {
-        return $this->type;
-    }
-
-    /**
-     * @return string
-     */
-    public function get_content_type(): string {
-        switch ( $this->get_type() ) {
-            case 'html':
-                return 'text/html';
-            case 'multipart':
-                return 'multipart/alternative';
-            default:
-                return 'text/plain';
-        }
-    }
-
-    /**
-     * @param string $content
-     *
-     * @return self
-     */
-    public function set_content( string $content ): self {
-        $this->content = $content;
-
-        return $this;
-    }
-
-    /**
-     * @return string
-     * @throws Exception
-     */
-    public function get_content(): string {
-        return $this->replace_placeholders( $this->content );
-    }
-
-    /**
-     * @return array|string|string[]
-     */
-    protected function replace_placeholders( string $string ): string {
-        if ( empty( $this->placeholders ) ) {
-            return $string;
-        }
-
-        return (string) str_replace( array_keys( $this->placeholders ), array_values( $this->placeholders ), $string );
-    }
-
-
-}
diff --git a/src/Abstracts/EmailInterface.php b/src/Abstracts/EmailInterface.php
deleted file mode 100644
index 61a4ea8..0000000
--- a/src/Abstracts/EmailInterface.php
+++ /dev/null
@@ -1,101 +0,0 @@
-<?php
-
-namespace WPDesk\Library\WPEmail\Abstracts;
-
-interface EmailInterface {
-
-    /**
-     * Define unique email ID.
-     *
-     * @return string
-     */
-    public function get_id(): string;
-
-    /**
-     * Get template name.
-     *
-     * @return string
-     */
-    public function get_template_name(): string;
-
-    /**
-     * @return string
-     */
-    public function get_from(): string;
-
-    /**
-     * @return string
-     */
-    public function get_from_name(): string;
-
-    /**
-     * Is enable.
-     *
-     * @return bool
-     */
-    public function get_is_enable(): bool;
-
-    /**
-     * Get defined placeholders.
-     *
-     * @return array
-     */
-    public function get_placeholders(): array;
-
-    /**
-     * Get email subject.
-     *
-     * @return string
-     */
-    public function get_subject(): string;
-
-    /**
-     * Get email heading.
-     *
-     * @return string
-     */
-    public function get_heading(): string;
-
-    /**
-     * Get valid recipients.
-     *
-     * @return string[]
-     */
-    public function get_recipients(): array;
-
-    /**
-     * Get email headers.
-     *
-     * @return string[]
-     */
-    public function get_headers(): array;
-
-    /**
-     * Get email attachments.
-     *
-     * @return array
-     */
-    public function get_attachments(): array;
-
-    /**
-     * Get email type.
-     *
-     * @return string
-     */
-    public function get_type(): string;
-
-    /**
-     * Get content type.
-     *
-     * @return string
-     */
-    public function get_content_type(): string;
-
-    /**
-     * Get email content.
-     *
-     * @return string
-     */
-    public function get_content(): string;
-
-}
diff --git a/src/Abstracts/Mailer.php b/src/Abstracts/Mailer.php
new file mode 100644
index 0000000..5c02806
--- /dev/null
+++ b/src/Abstracts/Mailer.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace WPDesk\Library\WPEmail\Abstracts;
+
+
+use WPDesk\Library\WPEmail\Exceptions\MailerException;
+
+/**
+ * Object-oriented wp_mail wrapper.
+ */
+interface Mailer {
+
+    /**
+     * @throws MailerException
+     */
+    public function send( Email $email ): void;
+
+}
+
diff --git a/src/EmailTemplate.php b/src/EmailTemplate.php
new file mode 100644
index 0000000..8930c87
--- /dev/null
+++ b/src/EmailTemplate.php
@@ -0,0 +1,42 @@
+<?php
+
+use WPDesk\Library\WPEmail\Abstracts\Email;
+use WPDesk\Library\WPEmail\Helpers\StyleInliner;
+
+class EmailTemplate {
+
+
+    protected function get_email_template( Email $email ): string {
+        $output = $this->renderer->render(
+            'html/email-header',
+            [
+                'heading' => $this->template_attributes['heading'] ?? $email->get_heading(),
+                'logo'    => $this->template_attributes['logo']
+            ]
+        );
+        $output .= $this->renderer->render( 'html/' . $email->get_id(), [ 'content' => $email->get_content() ] );
+        $output .= $this->renderer->render( 'html/email-footer', [ 'footer' => $this->template_attributes['footer'] ] );
+
+        return $this->css_inline( $output );
+    }
+
+    /**
+     * @param string $content
+     *
+     * @return mixed|string
+     */
+    protected function css_inline( string $content ): string {
+        $styles = $this->renderer->render(
+            'html/email-styles',
+            [
+                'primary' => $this->template_attributes['primary'] ?? '#d15291',
+                'text'    => $this->template_attributes['text'] ?? '#303030',
+                'bg'      => $this->template_attributes['bg'] ?? '#f9f9f9',
+                'body'    => $this->template_attributes['body'] ?? '#ffffff',
+            ]
+        );
+
+        return StyleInliner::inline( $content, $styles );
+    }
+
+}
diff --git a/src/MailerException.php b/src/Exceptions/MailerException.php
similarity index 88%
rename from src/MailerException.php
rename to src/Exceptions/MailerException.php
index 9dd3154..6c005cd 100644
--- a/src/MailerException.php
+++ b/src/Exceptions/MailerException.php
@@ -2,7 +2,7 @@
 
 declare( strict_types=1 );
 
-namespace WPDesk\Library\WPEmail;
+namespace WPDesk\Library\WPEmail\Exceptions;
 
 class MailerException extends \RuntimeException {
 
diff --git a/src/Helpers/Template.php b/src/Helpers/ColorConversion.php
similarity index 99%
rename from src/Helpers/Template.php
rename to src/Helpers/ColorConversion.php
index 0096d9d..e8242ee 100644
--- a/src/Helpers/Template.php
+++ b/src/Helpers/ColorConversion.php
@@ -2,7 +2,7 @@
 
 namespace WPDesk\Library\WPEmail\Helpers;
 
-class Template {
+class ColorConversion {
 
     /**
      * Determine whether a hex color is light.
diff --git a/src/Helpers/HTML.php b/src/Helpers/StyleInliner.php
similarity index 63%
rename from src/Helpers/HTML.php
rename to src/Helpers/StyleInliner.php
index aace0af..9068dee 100644
--- a/src/Helpers/HTML.php
+++ b/src/Helpers/StyleInliner.php
@@ -6,9 +6,9 @@ use Pelago\Emogrifier\CssInliner;
 use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter;
 use Pelago\Emogrifier\HtmlProcessor\HtmlPruner;
 
-class HTML {
+class StyleInliner {
 
-    public static function style_inline( $content, $styles = '' ) {
+    public static function inline( string $content, string $styles = '' ): string {
         if ( class_exists( 'DOMDocument' ) ) {
             try {
                 $css_inliner  = CssInliner::fromHtml( $content )->inlineCss( $styles );
@@ -21,10 +21,21 @@ class HTML {
                 error_log( $e->getMessage() );
             }
         } else {
-            $content = '<style type="text/css">' . strip_tags( $styles ) . '</style>' . $content;
+            $content = '<style>' . strip_tags( $styles ) . '</style>' . $content;
         }
 
         return $content;
     }
 
+    /**
+     * @return array|string|string[]
+     */
+    protected function replace_placeholders( string $string ): string {
+        if ( empty( $this->placeholders ) ) {
+            return $string;
+        }
+
+        return (string) str_replace( array_keys( $this->placeholders ), array_values( $this->placeholders ), $string );
+    }
+
 }
diff --git a/src/Mailer.php b/src/Mailer.php
deleted file mode 100644
index be4ce75..0000000
--- a/src/Mailer.php
+++ /dev/null
@@ -1,216 +0,0 @@
-<?php
-
-namespace WPDesk\Library\WPEmail;
-
-use Exception;
-use WP_Error;
-use WPDesk\Library\WPEmail\Abstracts\EmailInterface;
-use WPDesk\Library\WPEmail\Helpers\HTML;
-use WPDesk\View\Renderer\Renderer;
-use WPDesk\View\Renderer\SimplePhpRenderer;
-use WPDesk\View\Resolver\ChainResolver;
-use WPDesk\View\Resolver\DirResolver;
-
-class Mailer {
-
-    /**
-     * @var EmailInterface[]
-     */
-    private $emails = [];
-
-    /**
-     * @var Renderer
-     */
-    private $renderer;
-
-    /**
-     * @var array
-     */
-    private $template_attributes;
-
-    /**
-     * @var string
-     */
-    private $from;
-
-    /**
-     * @var string
-     */
-    private $from_name;
-
-    /**
-     * @param array $dirs
-     * @param array $template_attributes
-     */
-    public function __construct(
-        array $dirs = [],
-        array $template_attributes = []
-    ) {
-        $this->template_attributes = $template_attributes;
-        $this->set_renderer( $this->init_renderer( $dirs ) );
-        $this->set_from( get_bloginfo( 'admin_email' ) );
-        $this->set_from_name( get_bloginfo( 'name', 'display' ) );
-    }
-
-    /**
-     * @param array $dirs
-     *
-     * @return Renderer
-     */
-    private function init_renderer( array $dirs = [] ): Renderer {
-        $resolver = new ChainResolver();
-        foreach ( $dirs as $dir ) {
-            $resolver->appendResolver( new DirResolver( $dir ) );
-        }
-        $resolver->appendResolver( new DirResolver( dirname( __DIR__ ) . '/templates' ) );
-
-        return new SimplePhpRenderer( $resolver );
-    }
-
-    /**
-     * @param Renderer $renderer
-     *
-     * @return void
-     */
-    public function set_renderer( Renderer $renderer ) {
-        $this->renderer = $renderer;
-    }
-
-    /**
-     * @param EmailInterface $email
-     *
-     * @return void
-     */
-    public function add_email( EmailInterface $email ) {
-        $this->emails[ $email->get_id() ] = $email;
-    }
-
-    /**
-     * @return EmailInterface[]
-     */
-    public function get_emails(): array {
-        return $this->emails;
-    }
-
-    /**
-     * @param string $from
-     *
-     * @return void
-     */
-    public function set_from( string $from ) {
-        $this->from = $from;
-    }
-
-    /**
-     * @return string
-     */
-    public function get_from(): string {
-        return $this->from;
-    }
-
-    /**
-     * @param string $from_name
-     *
-     * @return void
-     */
-    public function set_from_name( string $from_name ) {
-        $this->from_name = $from_name;
-    }
-
-    /**
-     * @return string
-     */
-    public function get_from_name(): string {
-        return $this->from_name;
-    }
-
-    /** @return void */
-    public function send( $include = [] ) {
-        foreach ( $this->get_emails() as $email ) {
-            if ( ! empty( $include ) && ! in_array( $email->get_id(), $include, true ) ) {
-                continue;
-            }
-            if ( $email->get_is_enable() ) {
-                $mailer_from = $this->get_from();
-                add_filter(
-                    'wp_mail_from',
-                    $from_cb = static function ( $from ) use ( $mailer_from ) {
-                        return $mailer_from;
-                    }
-                );
-
-                $mailer_from_name = $this->get_from_name();
-                add_filter(
-                    'wp_mail_from_name',
-                    $from_name_cb = static function ( $from_name ) use ( $mailer_from_name ) {
-                        return $mailer_from_name;
-                    }
-                );
-                add_action( 'wp_mail_failed', [ $this, 'catch_error' ] );
-
-                try {
-                    $success = wp_mail(
-                        $email->get_recipients(),
-                        $email->get_subject(),
-                        $this->get_email_template( $email ),
-                        $email->get_headers(),
-                        $email->get_attachments()
-                    );
-                    if ( ! $success ) {
-                        throw new MailerException( 'Count not send the mail with wp_mail()' );
-                    }
-                } catch ( Exception $e ) {
-                    if ( $e instanceof MailerException ) {
-                        throw $e;
-                    }
-
-                    throw new MailerException( sprintf( 'wp_mail() failure. Original error: %s', $e->getMessage() ), 0, $e );
-                } finally {
-                    remove_action( 'wp_mail_failed', [ $this, 'catch_error' ], 99999 );
-                    remove_filter( 'wp_mail_from', $from_cb );
-                    remove_filter( 'wp_mail_from_name', $from_name_cb );
-                }
-            }
-        }
-
-    }
-
-    /** @return void */
-    public function catch_error( WP_Error $error ) {
-        throw MailerException::with_wp_error( $error );
-    }
-
-    protected function get_email_template( EmailInterface $email ): string {
-        $output = $this->renderer->render(
-            'html/email-header',
-            [
-                'heading' => $this->template_attributes['heading'] ?? $email->get_heading(),
-                'logo'    => $this->template_attributes['logo']
-            ]
-        );
-        $output .= $this->renderer->render( 'html/' . $email->get_id(), [ 'content' => $email->get_content() ] );
-        $output .= $this->renderer->render( 'html/email-footer', [ 'footer' => $this->template_attributes['footer'] ] );
-
-        return $this->css_inline( $output );
-    }
-
-    /**
-     * @param string $content
-     *
-     * @return mixed|string
-     */
-    protected function css_inline( string $content ): string {
-        $styles = $this->renderer->render(
-            'html/email-styles',
-            [
-                'primary' => $this->template_attributes['primary'] ?? '#d15291',
-                'text'    => $this->template_attributes['text'] ?? '#303030',
-                'bg'      => $this->template_attributes['bg'] ?? '#f9f9f9',
-                'body'    => $this->template_attributes['body'] ?? '#ffffff',
-            ]
-        );
-
-        return HTML::style_inline( $content, $styles );
-    }
-
-}
diff --git a/src/WPMailer.php b/src/WPMailer.php
new file mode 100644
index 0000000..91299ba
--- /dev/null
+++ b/src/WPMailer.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace WPDesk\Library\WPEmail;
+
+use Exception;
+use WP_Error;
+use WPDesk\Library\WPEmail\Abstracts\Email;
+use WPDesk\Library\WPEmail\Abstracts\Mailer;
+use WPDesk\Library\WPEmail\Exceptions\MailerException;
+
+class WPMailer implements Mailer {
+
+    /** @return void */
+    public function send( Email $email ): void {
+        $mailer_from = $email->get_from();
+        add_filter(
+            'wp_mail_from',
+            $from_cb = static function ( $from ) use ( $mailer_from ) {
+                return $mailer_from;
+            }
+        );
+
+        $mailer_from_name = $email->get_from_name();
+        add_filter(
+            'wp_mail_from_name',
+            $from_name_cb = static function ( $from_name ) use ( $mailer_from_name ) {
+                return $mailer_from_name;
+            }
+        );
+        add_action( 'wp_mail_failed', [ $this, 'catch_error' ] );
+
+        try {
+            $success = wp_mail(
+                $email->get_recipients(),
+                $email->get_subject(),
+                $email->get_content(),
+                $email->get_headers(),
+                $email->get_attachments()
+            );
+            if ( ! $success ) {
+                throw new MailerException( 'Count not send the mail with wp_mail()' );
+            }
+        } catch ( Exception $e ) {
+            if ( $e instanceof MailerException ) {
+                throw $e;
+            }
+
+            throw new MailerException( sprintf( 'wp_mail() failure. Original error: %s', $e->getMessage() ), 0, $e );
+        } finally {
+            remove_action( 'wp_mail_failed', [ $this, 'catch_error' ], 99999 );
+            remove_filter( 'wp_mail_from', $from_cb );
+            remove_filter( 'wp_mail_from_name', $from_name_cb );
+        }
+    }
+
+    /** @return void */
+    public function catch_error( WP_Error $error ) {
+        throw MailerException::with_wp_error( $error );
+    }
+
+}
diff --git a/templates/html/email-footer.php b/templates/html/email-footer.php
index e5aab55..c6b9aea 100644
--- a/templates/html/email-footer.php
+++ b/templates/html/email-footer.php
@@ -1,18 +1,6 @@
 <?php
 /**
  * Email Footer
- *
- * This template can be overridden by copying it to yourtheme/woocommerce/emails/email-footer.php.
- *
- * HOWEVER, on occasion WooCommerce will need to update template files and you
- * (the theme developer) will need to copy the new files to your theme to
- * maintain compatibility. We try to do this as little as possible, but it does
- * happen. When this occurs the version of the template file will be bumped and
- * the readme will list any important changes.
- *
- * @see https://docs.woocommerce.com/document/template-structure/
- * @package WooCommerce\Templates\Emails
- * @version 3.7.0
  */
 
 defined( 'ABSPATH' ) || exit;
diff --git a/templates/html/email-header.php b/templates/html/email-header.php
index d73b5a0..10b8088 100644
--- a/templates/html/email-header.php
+++ b/templates/html/email-header.php
@@ -1,18 +1,6 @@
 <?php
 /**
  * Email Header
- *
- * This template can be overridden by copying it to yourtheme/woocommerce/emails/email-header.php.
- *
- * HOWEVER, on occasion WooCommerce will need to update template files and you
- * (the theme developer) will need to copy the new files to your theme to
- * maintain compatibility. We try to do this as little as possible, but it does
- * happen. When this occurs the version of the template file will be bumped and
- * the readme will list any important changes.
- *
- * @see     https://docs.woocommerce.com/document/template-structure/
- * @package WooCommerce\Templates\Emails
- * @version 4.0.0
  */
 
 if ( ! defined( 'ABSPATH' ) ) {
diff --git a/templates/html/email-styles.php b/templates/html/email-styles.php
index 2fae35b..5a204dc 100644
--- a/templates/html/email-styles.php
+++ b/templates/html/email-styles.php
@@ -1,6 +1,6 @@
 <?php
 
-use WPDesk\Library\WPEmail\Helpers\Template;
+use WPDesk\Library\WPEmail\Helpers\ColorConversion;
 
 $params = isset( $params ) ? $params : [];
 
@@ -12,22 +12,22 @@ if ( ! defined( 'ABSPATH' ) ) {
 $bg        = $params['bg'] ?? '#f8f8f8';
 $body      = $params['body'] ?? '#ffffff';
 $base      = $params['primary'] ?? '#5c9a2c';
-$base_text = Template::light_or_dark( $base, '#171717', '#ffffff' );
+$base_text = ColorConversion::light_or_dark( $base, '#171717', '#ffffff' );
 $text      = $params['text'] ?? '#33333';
 
 // Pick a contrasting color for links.
-$link_color = Template::is_hex_light( $base ) ? $base : $base_text;
+$link_color = ColorConversion::is_hex_light( $base ) ? $base : $base_text;
 
-if ( Template::is_hex_light( $body ) ) {
-    $link_color = Template::is_hex_light( $base ) ? $base_text : $base;
+if ( ColorConversion::is_hex_light( $body ) ) {
+    $link_color = ColorConversion::is_hex_light( $base ) ? $base_text : $base;
 }
 
-$bg_darker_10    = Template::hex_darker( $bg, 10 );
-$body_darker_10  = Template::hex_darker( $body, 10 );
-$base_lighter_20 = Template::hex_lighter( $base, 20 );
-$base_lighter_40 = Template::hex_lighter( $base, 40 );
-$text_lighter_20 = Template::hex_lighter( $text, 20 );
-$text_lighter_40 = Template::hex_lighter( $text, 40 );
+$bg_darker_10    = ColorConversion::hex_darker( $bg, 10 );
+$body_darker_10  = ColorConversion::hex_darker( $body, 10 );
+$base_lighter_20 = ColorConversion::hex_lighter( $base, 20 );
+$base_lighter_40 = ColorConversion::hex_lighter( $base, 40 );
+$text_lighter_20 = ColorConversion::hex_lighter( $text, 20 );
+$text_lighter_40 = ColorConversion::hex_lighter( $text, 40 );
 
 // !important; is a gmail hack to prevent styles being stripped if it doesn't like something.
 // body{padding: 0;} ensures proper scale/positioning of the email in the iOS native email app.
diff --git a/tests/unit/Mailer/ColorTest.php b/tests/unit/Mailer/ColorTest.php
new file mode 100644
index 0000000..940c1b7
--- /dev/null
+++ b/tests/unit/Mailer/ColorTest.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Tests\Mailer;
+
+use PHPUnit\Framework\TestCase;
+use WPDesk\Library\WPEmail\Helpers\ColorConversion;
+
+class ColorTest extends TestCase {
+
+    public function testShouldCheckHexConversion() {
+        $white_hex = ColorConversion::rgb_from_hex( '#FFFFFF' );
+        $white_rgb = [
+            'R' => 255,
+            'G' => 255,
+            'B' => 255,
+        ];
+        $this->assertSame( $white_hex, $white_rgb );
+
+        $black_hex = ColorConversion::rgb_from_hex( '#000000' );
+        $black_rgb = [
+            'R' => 0,
+            'G' => 0,
+            'B' => 0,
+        ];
+        $this->assertSame( $black_hex, $black_rgb );
+    }
+
+
+    public function testShouldCheckIsHexIsDarker() {
+        $dark_hex = ColorConversion::is_hex_light( '#333333' );
+        $this->assertFalse( $dark_hex );
+    }
+
+
+    public function testShouldCheckIsHexIsLighter() {
+        $light_hex = ColorConversion::is_hex_light( '#f8f8f8' );
+        $this->assertTrue( $light_hex );
+    }
+
+    public function testShouldMakeColorDarker() {
+        $dark_hex = ColorConversion::hex_darker( '#f8f8f8' );
+        $this->assertSame( $dark_hex, '#aeaeae' );
+    }
+
+
+    public function testShouldMakeColorLighter() {
+        $light_hex = ColorConversion::hex_lighter( '#f8f8f8' );
+        $this->assertSame( $light_hex, '#fafafa' );
+    }
+
+}
diff --git a/tests/unit/Mailer/MailTest.php b/tests/unit/Mailer/MailTest.php
new file mode 100644
index 0000000..5397f3a
--- /dev/null
+++ b/tests/unit/Mailer/MailTest.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Tests\Mailer;
+
+use PHPUnit\Framework\TestCase;
+use WPDesk\Library\WPEmail\Abstracts\Email;
+
+class MailTest extends TestCase {
+
+    const FROM = 'anna.nowak@gmail.com';
+    const FROM_NAME = 'Anna Nowak';
+    const RECIPIENTS = [ 'jan.nowak@gmail.com' ];
+    const SUBJECT = 'Some Subject';
+    const CONTENT = 'Lorem ipsum';
+
+    public $email;
+
+    public function setUp(): void {
+        parent::setUp();
+        \WP_Mock::setUp();
+        $this->email = new Email();
+    }
+
+    public function testSetAndGetFromEmail(): void {
+        $data = \WP_Mock::userFunction('sanitize_email')->andReturn('');
+        print_r( $data );
+        exit;
+        \WP_Mock::passthruFunction( 'sanitize_email' );
+        $this->email->set_from( 'john@example.com' );
+        $this->assertEquals( 'john@example.com', $this->email->get_from() );
+    }
+
+    public function testSetAndGetFromName(): void {
+        \WP_Mock::passthruFunction( 'wp_specialchars_decode' );
+        $this->email->set_from_name( 'John Doe' );
+        $this->assertEquals( 'John Doe', $this->email->get_from_name() );
+    }
+
+    public function testSetAndGetSubject(): void {
+        $this->email->set_subject( 'Hello World' );
+        $this->assertEquals( 'Hello World', $this->email->get_subject() );
+    }
+
+    public function testSetAndGetRecipients(): void {
+        $this->email->set_recipients( [ 'jane@example.com', 'mark@example.com' ] );
+        $this->assertEquals( [ 'jane@example.com', 'mark@example.com' ], $this->email->get_recipients() );
+    }
+
+    public function testSetAndGetHeader(): void {
+        $this->email->set_header( 'Return-Path', 'mail@testsite.com' );
+        $this->assertEquals( 'mail@testsite.com', $this->email->get_header( 'Return-Path' ) );
+    }
+
+    public function testSetAndGetAttachments(): void {
+        $this->email->set_attachments( [ 'file1.txt', 'file2.jpg' ] );
+        $this->assertEquals( [ 'file1.txt', 'file2.jpg' ], $this->email->get_attachments() );
+    }
+
+    public function testIsHtml(): void {
+        $this->email->set_content_type();
+        $this->assertTrue( $this->email->is_html() );
+
+        $this->email->set_content_type( 'plain' );
+        $this->assertFalse( $this->email->is_html() );
+    }
+
+    public function testSetAndGetContentType(): void {
+        $this->email->set_content_type( 'html' );
+        $this->assertEquals( 'text/html', $this->email->get_header( 'Content-Type' ) );
+
+        $this->email->set_content_type( 'plain' );
+        $this->assertEquals( 'text/plain', $this->email->get_header( 'Content-Type' ) );
+
+        $this->email->set_content_type( 'multipart' );
+        $this->assertEquals( 'multipart/alternative', $this->email->get_header( 'Content-Type' ) );
+    }
+
+    public function testSetAndGetContent(): void {
+        $this->email->set_content( 'Hello World' );
+        $this->assertEquals( 'Hello World', $this->email->get_content() );
+    }
+
+    public function tearDown(): void {
+        \WP_Mock::tearDown();
+    }
+
+}
diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php
index 76b8109..38a4eb0 100644
--- a/tests/unit/bootstrap.php
+++ b/tests/unit/bootstrap.php
@@ -5,5 +5,22 @@
 
 require_once __DIR__ . '/../../vendor/autoload.php';
 
+error_reporting( E_ALL );
+
+if ( getenv( 'PLUGIN_PATH' ) !== false ) {
+    define( 'PLUGIN_PATH', getenv( 'PLUGIN_PATH' ) );
+} else {
+    define( 'PLUGIN_PATH', __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR );
+}
+
+if ( getenv( 'ABSPATH' ) !== false ) {
+    define( 'ABSPATH', getenv( 'ABSPATH' ) );
+} else {
+    define( 'ABSPATH', PLUGIN_PATH . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR );
+}
+
 WP_Mock::setUsePatchwork( true );
 WP_Mock::bootstrap();
+
+
+
-- 
GitLab