Completed
Pull Request — master (#65)
by Frederik
01:35
created

FixedQuotation::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail\Quotation;
5
6
use Genkgo\Mail\Address;
7
use Genkgo\Mail\AlternativeText;
8
use Genkgo\Mail\MessageBodyCollection;
9
use Genkgo\Mail\MessageInterface;
10
use Genkgo\Mail\QuotationInterface;
11
12
final class FixedQuotation implements QuotationInterface
13
{
14
    /**
15
     * @var string
16
     */
17
    private $headerText;
18
19
    /**
20
     * @param string $headerText
21
     */
22 11
    public function __construct(string $headerText = '%s (%s):')
23
    {
24 11
        $this->headerText = $headerText;
25 11
    }
26
27
    /**
28
     * @param MessageBodyCollection $body
29
     * @param MessageInterface $originalMessage
30
     * @return MessageBodyCollection
31
     * @throws \DOMException
32
     */
33 11
    public function quote(MessageBodyCollection $body, MessageInterface $originalMessage): MessageBodyCollection
34
    {
35 11
        $originalBody = MessageBodyCollection::extract($originalMessage);
36 11
        $dateString = 'unknown';
37 11
        foreach ($originalMessage->getHeader('Date') as $header) {
38
            try {
39 4
                $date = new \DateTimeImmutable($header->getValue()->getRaw());
40 4
                $dateString = \IntlDateFormatter::create(
41 4
                    \Locale::getDefault(),
42 4
                    \IntlDateFormatter::MEDIUM,
43 4
                    \IntlDateFormatter::MEDIUM
44 4
                )->format($date);
45 4
            } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
46
            }
47
        }
48
49 11
        $fromString = '';
50 11
        foreach ($originalMessage->getHeader('From') as $header) {
51 11
            $from = Address::fromString($header->getValue()->getRaw());
52 11
            $fromString = $from->getName() ? $from->getName() : (string)$from->getAddress();
53
        }
54
55 11
        $headerText = \sprintf($this->headerText, $fromString, $dateString);
56
57
        return $body
58 11
            ->withHtmlAndNoGeneratedAlternativeText(
59 11
                $this->quoteHtml(
60 11
                    $body->getHtml(),
61 11
                    $originalBody->getHtml(),
62 11
                    $headerText
63
                )
64
            )
65 11
            ->withAlternativeText(
66 11
                $this->quoteText(
67 11
                    $body->getText(),
68 11
                    $originalBody->getText(),
69 11
                    $headerText
70
                )
71
            );
72
    }
73
74
    /**
75
     * @param string $newHtml
76
     * @param string $originalHtml
77
     * @param string $headerText
78
     * @return string
79
     * @throws \DOMException
80
     */
81 11
    private function quoteHtml(string $newHtml, string $originalHtml, string $headerText): string
82
    {
83 11
        $originalHtml = \trim($originalHtml);
84 11
        if ($originalHtml === '') {
85 2
            return '';
86
        }
87
88 9
        $document = new \DOMDocument();
89 9
        $document->substituteEntities = false;
90 9
        $document->resolveExternals = false;
91
92 9
        $result = @$document->loadHTML($originalHtml);
93 9
        if ($result === false) {
94
            throw new \DOMException('Incorrect HTML');
95
        }
96
97 9
        $query = new \DOMXPath($document);
98 9
        $removeItems = $query->query('//head|//script|//body/@style|//html/@style', $document->documentElement);
99
        /** @var \DOMElement $removeItem */
100 9
        foreach ($removeItems as $removeItem) {
101 1
            $parent = $removeItem->parentNode;
102 1
            $parent->removeChild($removeItem);
103
        }
104
105 9
        $body = $document->getElementsByTagName('body');
106 9
        $quote = $document->createElement('blockquote');
107 9
        $quote->setAttribute('type', 'cite');
108
109 9
        if ($body->length === 0) {
110
            $quote->appendChild($document->removeChild($document->documentElement));
111
        } else {
112 9
            $root = $body->item(0);
113 9
            while ($root->childNodes->length !== 0) {
114 9
                $quote->appendChild($root->childNodes->item(0));
115
            }
116
        }
117
118 9
        $newDocument = new \DOMDocument();
119 9
        $newDocument->substituteEntities = false;
120 9
        $newDocument->resolveExternals = false;
121 9
        $result = @$newDocument->loadHTML($newHtml, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
122 9
        if ($result === false) {
123
            throw new \DOMException('Incorrect HTML');
124
        }
125
126 9
        $quotedNode = $newDocument->importNode($quote, true);
127 9
        $newBody = $this->prepareBody($newDocument);
128 9
        $newBody->appendChild($quotedNode);
129
130 9
        $header = $newDocument->createElement('p');
131 9
        $header->textContent = $headerText;
132
133 9
        $quotedNode->parentNode->insertBefore($header, $quotedNode);
134 9
        return \trim($newDocument->saveHTML());
135
    }
136
137
    /**
138
     * @param \DOMDocument $document
139
     * @return \DOMElement
140
     */
141 9
    private function prepareBody(\DOMDocument $document): \DOMElement
142
    {
143 9
        $bodyList = $document->getElementsByTagName('body');
144 9
        if ($bodyList->length === 0) {
145
            $html = $document->createElement('html');
146
            $body = $document->createElement('body');
147
            $html->appendChild($body);
148
            $body->appendChild($document->documentElement);
149
            $document->removeChild($document->documentElement);
150
            $document->appendChild($html);
151
            return $body;
152
        }
153
154 9
        $body = $bodyList->item(0);
155
156 9
        $queryHtml = new \DOMXPath($document);
157 9
        $htmlTags = $queryHtml->query('//html');
158 9
        if ($htmlTags->length > 0) {
159 9
            $html = $htmlTags->item(0);
160 9
            $html->appendChild($body);
161 9
            $document->removeChild($document->documentElement);
162 9
            $document->appendChild($html);
163 9
            return $body;
164
        }
165
166
        $html = $document->createElement('html');
167
        $html->appendChild($body);
168
        $document->removeChild($document->documentElement);
169
        $document->appendChild($html);
170
        return $body;
171
    }
172
173
    /**
174
     * @param AlternativeText $newText
175
     * @param AlternativeText $originalText
176
     * @param string $headerText
177
     * @return AlternativeText
178
     */
179 11
    private function quoteText(
180
        AlternativeText $newText,
181
        AlternativeText $originalText,
182
        string $headerText
183
    ): AlternativeText {
184 11
        return new AlternativeText(
185 11
            \sprintf(
186 11
                "%s\n\n%s\n>%s",
187 11
                (string)$newText,
188 11
                $headerText,
189 11
                \str_replace("\n", "\n>", $originalText->getRaw())
190
            )
191
        );
192
    }
193
}
194