Completed
Pull Request — master (#64)
by Frederik
04:51
created

FixedQuotation::quoteHtml()   C

Complexity

Conditions 10
Paths 15

Size

Total Lines 69

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 11.1743

Importance

Changes 0
Metric Value
dl 0
loc 69
ccs 34
cts 44
cp 0.7727
rs 6.8096
c 0
b 0
f 0
cc 10
nc 15
nop 3
crap 11.1743

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
                $formatter = \IntlDateFormatter::create(
41 4
                    \Locale::getDefault(),
42 4
                    \IntlDateFormatter::MEDIUM,
43 4
                    \IntlDateFormatter::MEDIUM
44 4
                );
45
46
                if ($formatter === false) {
47
                    throw new \UnexpectedValueException('Cannot create date formatter');
48
                }
49 11
50 11
                $dateString = $formatter->format($date);
51 11
                break;
52 11
            } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
53
            }
54
        }
55 11
56
        $fromString = '';
57
        foreach ($originalMessage->getHeader('From') as $header) {
58 11
            $from = Address::fromString($header->getValue()->getRaw());
59 11
            $fromString = $from->getName() ? $from->getName() : (string)$from->getAddress();
60 11
        }
61 11
62 11
        $headerText = \sprintf($this->headerText, $fromString, $dateString);
63
64
        return $body
65 11
            ->withHtmlAndNoGeneratedAlternativeText(
66 11
                $this->quoteHtml(
67 11
                    $body->getHtml(),
68 11
                    $originalBody->getHtml(),
69 11
                    $headerText
70
                )
71
            )
72
            ->withAlternativeText(
73
                $this->quoteText(
74
                    $body->getText(),
75
                    $originalBody->getText(),
76
                    $headerText
77
                )
78
            );
79
    }
80
81 11
    /**
82
     * @param string $newHtml
83 11
     * @param string $originalHtml
84 11
     * @param string $headerText
85 2
     * @return string
86
     * @throws \DOMException
87
     */
88 9
    private function quoteHtml(string $newHtml, string $originalHtml, string $headerText): string
89 9
    {
90 9
        $originalHtml = \trim($originalHtml);
91
        if ($originalHtml === '') {
92 9
            return '';
93 9
        }
94
95
        $document = new \DOMDocument();
96
        $document->substituteEntities = false;
97 9
        $document->resolveExternals = false;
98 9
99
        $result = @$document->loadHTML($originalHtml);
100 9
        if ($result === false) {
101 1
            throw new \DOMException('Incorrect HTML');
102 1
        }
103
104
        if ($document->documentElement !== null) {
105 9
            $query = new \DOMXPath($document);
106 9
            $removeItems = $query->query('//head|//script|//body/@style|//html/@style', $document->documentElement);
107 9
            if ($removeItems instanceof \DOMNodeList) {
108
                /** @var \DOMElement $removeItem */
109 9
                foreach ($removeItems as $removeItem) {
110
                    /** @var \DOMElement $parent */
111
                    $parent = $removeItem->parentNode;
112 9
                    $parent->removeChild($removeItem);
113 9
                }
114 9
            }
115
116
            $body = $document->getElementsByTagName('body');
117
            $quote = $document->createElement('blockquote');
118 9
            $quote->setAttribute('type', 'cite');
119 9
120 9
            if ($body->length === 0) {
121 9
                $quote->appendChild($document->removeChild($document->documentElement));
122 9
            } else {
123
                $root = $body->item(0);
124
                if ($root instanceof \DOMElement) {
125
                    while ($root->childNodes->length !== 0) {
126 9
                        /** @var \DOMElement $child */
127 9
                        $child = $root->childNodes->item(0);
128 9
                        $quote->appendChild($child);
129
                    }
130 9
                }
131 9
            }
132
133 9
            $newDocument = new \DOMDocument();
134 9
            $newDocument->substituteEntities = false;
135
            $newDocument->resolveExternals = false;
136
            $result = @$newDocument->loadHTML($newHtml, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
137
            if ($result === false) {
138
                throw new \DOMException('Incorrect HTML');
139
            }
140
141 9
            $quotedNode = $newDocument->importNode($quote, true);
142
            $newBody = $this->prepareBody($newDocument);
143 9
            $newBody->appendChild($quotedNode);
144 9
145
            $header = $newDocument->createElement('p');
146
            $header->textContent = $headerText;
147
148
            /** @var \DOMElement $parent */
149
            $parent = $quotedNode->parentNode;
150
            $parent->insertBefore($header, $quotedNode);
151
152
            return \trim((string)$newDocument->saveHTML());
153
        }
154 9
155
        return '';
156 9
    }
157 9
158 9
    /**
159 9
     * @param \DOMDocument $document
160 9
     * @return \DOMElement
161 9
     */
162 9
    private function prepareBody(\DOMDocument $document): \DOMElement
163 9
    {
164
        if (!$document->documentElement) {
165
            throw new \UnexpectedValueException('Cannot prepare empty document');
166
        }
167
168
        $bodyList = $document->getElementsByTagName('body');
169
        if ($bodyList->length === 0) {
170
            $html = $document->createElement('html');
171
            $body = $document->createElement('body');
172
            $html->appendChild($body);
173
            $body->appendChild($document->documentElement);
174
            $document->removeChild($document->documentElement);
175
            $document->appendChild($html);
176
            return $body;
177
        }
178
179 11
        /** @var \DOMElement $body */
180
        $body = $bodyList->item(0);
181
182
        $queryHtml = new \DOMXPath($document);
183
        $htmlTags = $queryHtml->query('//html');
184 11
        if ($htmlTags && $htmlTags->length > 0) {
185 11
            /** @var \DOMElement $html */
186 11
            $html = $htmlTags->item(0);
187 11
            $html->appendChild($body);
188 11
            $document->removeChild($document->documentElement);
189 11
            $document->appendChild($html);
190
            return $body;
191
        }
192
193
        $html = $document->createElement('html');
194
        $html->appendChild($body);
195
        $document->removeChild($document->documentElement);
196
        $document->appendChild($html);
197
        return $body;
198
    }
199
200
    /**
201
     * @param AlternativeText $newText
202
     * @param AlternativeText $originalText
203
     * @param string $headerText
204
     * @return AlternativeText
205
     */
206
    private function quoteText(
207
        AlternativeText $newText,
208
        AlternativeText $originalText,
209
        string $headerText
210
    ): AlternativeText {
211
        return new AlternativeText(
212
            \sprintf(
213
                "%s\n\n%s\n>%s",
214
                (string)$newText,
215
                $headerText,
216
                \str_replace("\n", "\n>", $originalText->getRaw())
217
            )
218
        );
219
    }
220
}
221