Passed
Branch master (4f08c0)
by Takashi
07:18 queued 05:07
created

HtmlHandler::initialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Ttskch\Esa;
4
5
use Symfony\Component\DomCrawler\Crawler;
6
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
7
8
class HtmlHandler
9
{
10
    /**
11
     * @var Crawler
12
     */
13
    private $crawler;
14
15
    /**
16
     * @var UrlGeneratorInterface
17
     */
18
    private $urlGenerator;
19
20
    /**
21
     * @var EmojiManager
22
     */
23
    private $emojiManager;
24
25
    /**
26
     * @var string
27
     */
28
    private $teamName;
29
30
    /**
31
     * @param array $replacements
32
     */
33
    public function __construct(Crawler $crawler, UrlGeneratorInterface $urlGenerator, EmojiManager $emojiManager, $teamName)
34
    {
35
        $this->crawler = $crawler;
36
        $this->urlGenerator = $urlGenerator;
37
        $this->emojiManager = $emojiManager;
38
        $this->teamName = $teamName;
39
    }
40
41
    /**
42
     * @param string $html
43
     * @return $this
44
     */
45
    public function initialize($html)
46
    {
47
        $this->crawler->clear();
48
        $this->crawler->addHtmlContent($html);
49
50
        return $this;
51
    }
52
53
    /**
54
     * @return string
55
     */
56
    public function dumpHtml()
57
    {
58
        $this->ensureInitialized();
59
60
        return $this->crawler->html();
61
    }
62
63
    /**
64
     * Replace links to other post with links to see the post on esaba.
65
     *
66
     * @param string $routeName
67
     * @param string $routeVariableName
68
     */
69
    public function replacePostUrls($routeName, $routeVariableName)
70
    {
71
        $backReferenceNumberForPostId = null;
72
        $pattern = $this->getPostUrlPattern($backReferenceNumberForPostId);
73
        $walker = $this->getATagWalkerForPostUrls($pattern, $backReferenceNumberForPostId, $routeName, $routeVariableName);
74
75
        $this->replaceATagWithWalker($pattern, $walker);
76
    }
77
78
    /**
79
     * Disable @mention links.
80
     */
81
    public function disableMentionLinks()
82
    {
83
        $pattern = $this->getMentionLinkPattern();
84
        $walker = $this->getATagWalkerForMentionLinks($pattern);
85
86
        $this->replaceATagWithWalker($pattern, $walker);
87
    }
88
89
    /**
90
     * Replace <a> tag href values for specified regexp pattern with closure returns map of ['pattern' => regexp pattern, 'replacement' => replacement].
91
     *
92
     * @param string $pattern
93
     * @param \Closure $walker
94
     */
95
    public function replaceATagWithWalker($pattern, \Closure $walker)
96
    {
97
        $this->ensureInitialized();
98
99
        $targetATags = $this->crawler->filter('a')->reduce($this->getATagReducer($pattern));
100
        $replacements = $targetATags->each($walker);
101
        $replacements = array_combine(array_column($replacements, 'pattern'), array_column($replacements, 'replacement'));
102
103
        $this->replaceHtml($replacements);
104
    }
105
106
    /**
107
     * @param string $backReferenceNumberForPostId For returning position of post id in regexp pattern.
108
     * @return string
109
     */
110
    public function getPostUrlPattern(&$backReferenceNumberForPostId)
111
    {
112
        $backReferenceNumberForPostId = 3;
113
114
        return sprintf('#^((https?:)?//%s\.esa\.io)?/posts/(\d+)(/|/edit/?)?$#', $this->teamName);
115
    }
116
117
    /**
118
     * @return string
119
     */
120
    public function getMentionLinkPattern()
121
    {
122
        return '#/members/([^\'"]+)#';
123
    }
124
125
    /**
126
     * Return closure reduces ATags Crawler with regexp pattern for href value.
127
     *
128
     * @param string $pattern
129
     * @return \Closure
130
     */
131
    public function getATagReducer($pattern)
132
    {
133
        $reducer = function (Crawler $node) use ($pattern) {
134
            preg_match($pattern, $node->attr('href'), $matches);
135
136
            return boolval($matches);
137
        };
138
139
        return $reducer;
140
    }
141
142
    /**
143
     * Return closure returns map of ['pattern' => regexp pattern, 'replacement' => replacement] for href value of post urls.
144
     *
145
     * @param string $pattern
146
     * @param int $backReferenceNumberForPostId
147
     * @param string $routeName
148
     * @param string $routeVariableName
149
     * @return \Closure
150
     */
151
    public function getATagWalkerForPostUrls($pattern, $backReferenceNumberForPostId, $routeName, $routeVariableName)
152
    {
153
        $that = $this;
154
155
        $walker = function (Crawler $node) use ($pattern, $backReferenceNumberForPostId, $routeName, $routeVariableName, $that) {
156
            preg_match($pattern, $node->attr('href'), $matches);
157
            $href = $matches[0];
158
            $postId = $matches[$backReferenceNumberForPostId];
159
160
            $pattern = sprintf('/href=(\'|")%s\1/', str_replace('/', '\/', $href));
161
            $replacement = sprintf('href="%s"', $that->urlGenerator->generate($routeName, [$routeVariableName => $postId]));
162
163
            return [
164
                'pattern' => $pattern,
165
                'replacement' => $replacement,
166
            ];
167
        };
168
169
        return $walker;
170
    }
171
172
    /**
173
     * Return closure returns map of ['pattern' => regexp pattern, 'replacement' => replacement] for href value of mention links.
174
     *
175
     * @param string $pattern
176
     * @return \Closure
177
     */
178
    public function getATagWalkerForMentionLinks($pattern)
179
    {
180
        $walker = function (Crawler $node) use ($pattern) {
181
            preg_match($pattern, $node->attr('href'), $matches);
182
            $href = $matches[0];
183
184
            $pattern = sprintf('/href=(\'|")%s\1/', str_replace('/', '\/', $href));
185
            $replacement = '';
186
187
            return [
188
                'pattern' => $pattern,
189
                'replacement' => $replacement,
190
            ];
191
        };
192
193
        return $walker;
194
    }
195
196
    /**
197
     * Replace emoji codes with img tags.
198
     */
199
    public function replaceEmojiCodes()
200
    {
201
        $this->ensureInitialized();
202
203
        $html = $this->crawler->html();
204
205
        preg_match_all('/:(\w+):/', $html, $matches);
206
207
        for ($i = 0; $i < count($matches[0]); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
208
            $code = $matches[0][$i];
209
            $replacement = sprintf('<img src="%s" class="emoji" title="%s" alt="%s">', $this->emojiManager->getImageUrl($matches[1][$i]), $code, $code);
210
211
            $html = str_replace($code, $replacement, $html);
212
        }
213
214
        $this->initialize($html);
215
    }
216
217
    /**
218
     * @param array $replacements map of [regexp pattern => replacement].
219
     */
220
    public function replaceHtml(array $replacements)
221
    {
222
        $this->ensureInitialized();
223
224
        $html = $this->crawler->html();
225
226
        foreach ($replacements as $pattern => $replacement) {
227
            $html = preg_replace($pattern, $replacement, $html);
228
        }
229
230
        $this->initialize($html);
231
    }
232
233
    /**
234
     * Return map of ['id' => id, 'text' => text] of headings as TOC.
235
     *
236
     * @return array
237
     */
238
    public function getToc()
239
    {
240
        $this->ensureInitialized();
241
242
        $toc = $this->crawler->filter('h1, h2, h3')->each($this->getWalkerForToc());
243
244
        return $toc;
245
    }
246
247
    /**
248
     * Return closure returns map of ['id' => id, 'text' => text] of h tags.
249
     *
250
     * @return \Closure
251
     */
252
    public function getWalkerForToc()
253
    {
254
        $walker = function (Crawler $node) {
255
            return [
256
                'id' => $node->attr('id'),
257
                // 'text' => $node->text(),
258
                'text' => preg_replace('/^\s*>\s*/', '', $node->text()),    // workaround...
259
            ];
260
        };
261
262
        return $walker;
263
    }
264
265
    private function ensureInitialized()
266
    {
267
        if (!$this->crawler->count()) {
268
            throw new \LogicException('Initialize before using.');
269
        }
270
    }
271
}
272