Test Failed
Push — master ( dff342...fb8e6c )
by Brent
05:42 queued 02:50
created

Htaccess::clearPageBlocks()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 5
nc 3
nop 0
dl 0
loc 9
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
namespace Brendt\Stitcher\Site\Http;
4
5
use Brendt\Stitcher\Exception\ConfigurationException;
6
use Brendt\Stitcher\Site\Page;
7
use Symfony\Component\Filesystem\Filesystem;
8
use Tivie\HtaccessParser\HtaccessContainer;
9
use Tivie\HtaccessParser\Parser;
10
use Tivie\HtaccessParser\Token\Block;
11
use Tivie\HtaccessParser\Token\Directive;
12
use Tivie\HtaccessParser\Token\WhiteLine;
13
14
class Htaccess
15
{
16
    /**
17
     * @var Filesystem
18
     */
19
    private $fs;
20
21
    /**
22
     * @var Parser
23
     */
24
    private $parser;
25
26
    /**
27
     * @var array|\ArrayAccess|HtaccessContainer
28
     */
29
    private $contents;
30
31
    /**
32
     * @var bool
33
     */
34
    private $redirectHttps = false;
35
36
    /**
37
     * @var bool
38
     */
39
    private $redirectWww = false;
40
41
    /**
42
     * @var array
43
     */
44
    private $redirects = [];
45
46
    /**
47
     * Htaccess constructor.
48
     *
49
     * @param string $path
50
     *
51
     * @throws ConfigurationException
52
     */
53
    public function __construct(string $path) {
54
        $this->fs = new Filesystem();
55
56
        if (!$this->fs->exists($path)) {
57
            $this->fs->dumpFile($path, file_get_contents(__DIR__ . '/../../../../.htaccess'));
58
        }
59
60
        $this->parser = new Parser(new \SplFileObject($path));
61
        $this->parser->ignoreWhitelines(false);
62
        $this->contents = $this->parser->parse();
63
    }
64
65
    /**
66
     * @param bool $redirectHttps
67
     *
68
     * @return Htaccess
69
     */
70
    public function setRedirectHttps(bool $redirectHttps = false) : Htaccess {
71
        $this->redirectHttps = $redirectHttps;
72
73
        return $this;
74
    }
75
76
    /**
77
     * @param bool $redirectWww
78
     *
79
     * @return Htaccess
80
     */
81
    public function setRedirectWww(bool $redirectWww = false) : Htaccess {
82
        $this->redirectWww = $redirectWww;
83
84
        return $this;
85
    }
86
87
    /**
88
     * Add custom redirects handles by htaccess
89
     *
90
     * @param string $from
91
     * @param string $to
92
     *
93
     * @return Htaccess
94
     */
95
    public function addRedirect(string $from, string $to) : Htaccess {
96
        $this->redirects[$from] = $to;
97
98
        return $this;
99
    }
100
101
    /**
102
     * Parse the modified .htaccess
103
     *
104
     * @return string
105
     */
106
    public function parse() : string {
107
        $this->clearRewriteBlock();
108
109
        if ($this->redirectWww) {
110
            $this->rewriteWww();
111
        }
112
113
        if ($this->redirectHttps) {
114
            $this->rewriteHttps();
115
        }
116
117
        $this->rewriteCustomRedirects();
118
        $this->rewriteHtml();
119
120
        return (string) $this->contents;
121
    }
122
123
    /**
124
     * Get or create the headers block
125
     *
126
     * @return Block
127
     */
128 View Code Duplication
    public function &getHeaderBlock() : Block {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
129
        $headerBlock = $this->findHeaderBlockByModName('mod_headers.c');
130
131
        if (!$headerBlock) {
132
            $headerBlock = new Block('ifmodule');
133
            $headerBlock->addArgument('mod_headers.c');
134
135
            if ($this->contents instanceof HtaccessContainer) {
136
                $this->contents->append($headerBlock);
137
            }
138
        }
139
140
        return $headerBlock;
141
    }
142
143
    /**
144
     * Get or create a page block within the headers block
145
     *
146
     * @param Page $page
147
     *
148
     * @return Block
149
     */
150
    public function &getPageBlock(Page $page) : Block {
151
        $headerBlock = $this->getHeaderBlock();
152
        $pageId = trim($page->getId(), '/') ?? 'index';
153
        $pageId = pathinfo($pageId !== '' ? "{$pageId}" : 'index', PATHINFO_BASENAME);
154
        $pageName = '"^' . $pageId . '\.html$"';
155
156
        $pageBlock = $this->findPageBlockByParentAndName($headerBlock, $pageName);
157
158
        if (!$pageBlock) {
159
            $pageBlock = new Block('filesmatch');
160
            $pageBlock->addArgument($pageName);
161
            $headerBlock->addChild($pageBlock);
162
        }
163
164
        return $pageBlock;
165
    }
166
167
    /**
168
     * Clear all page header blocks
169
     */
170
    public function clearPageBlocks() {
171
        $headerBlock = $this->getHeaderBlock();
172
173
        foreach ($headerBlock as $content) {
174
            if ($content instanceof Block && strtolower($content->getName()) === 'filesmatch') {
0 ignored issues
show
Bug introduced by
Consider using $content->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
175
                $headerBlock->removeChild($content);
176
            }
177
        }
178
    }
179
180
    /**
181
     * Clear all rewrites
182
     */
183
    public function clearRewriteBlock() {
184
        $rewriteBlock = $this->getRewriteBlock();
185
186
        foreach ($rewriteBlock as $content) {
187
            if (
188
                $content instanceof WhiteLine ||
189
                ($content instanceof Directive && $content->getName() !== 'RewriteEngine' && $content->getName() !== 'DirectorySlash')
0 ignored issues
show
Bug introduced by
Consider using $content->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
190
            ) {
191
                $rewriteBlock->removeChild($content);
192
            }
193
        }
194
    }
195
196
    /**
197
     * Get or create the rewrite block
198
     *
199
     * @return Block
200
     */
201 View Code Duplication
    public function &getRewriteBlock() : Block {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
202
        $rewriteBlock = $this->findHeaderBlockByModName('mod_rewrite.c');
203
204
        if (!$rewriteBlock) {
205
            $rewriteBlock = new Block('ifmodule');
206
            $rewriteBlock->addArgument('mod_rewrite.c');
207
208
            if ($this->contents instanceof HtaccessContainer) {
209
                $this->contents->append($rewriteBlock);
210
            }
211
        }
212
213
        return $rewriteBlock;
214
    }
215
216
    /**
217
     * @param Block  $headerBlock
218
     * @param string $pageName
219
     *
220
     * @return null|Block
221
     */
222
    private function findPageBlockByParentAndName(Block $headerBlock, string $pageName) {
223
        foreach ($headerBlock as $content) {
224
            $arguments = $content->getArguments();
225
226
            if (reset($arguments) === $pageName) {
227
                return $content;
228
            }
229
        }
230
231
        return null;
232
    }
233
234
    /**
235
     * Add www rewrite
236
     */
237
    private function rewriteWww() {
238
        $rewriteBlock = $this->getRewriteBlock();
239
240
        $this->createConditionalRewrite($rewriteBlock, '%{HTTP_HOST} !^www\.', '^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]');
241
    }
242
243
    /**
244
     * Add https rewrite
245
     */
246
    private function rewriteHttps() {
247
        $rewriteBlock = $this->getRewriteBlock();
248
249
        $this->createConditionalRewrite($rewriteBlock, '%{HTTPS} off', '(.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]');
250
    }
251
252
    /**
253
     * Add custom rewrites
254
     */
255
    private function rewriteCustomRedirects() {
256
        $rewriteBlock = $this->getRewriteBlock();
257
        $rewriteBlock->addLineBreak(1);
258
259
        foreach ($this->redirects as $from => $to) {
260
            $ruleLine = new Directive();
261
            $ruleLine->setName("RedirectMatch 301 ^{$from}$ {$to}");
262
            $rewriteBlock->addChild($ruleLine);
263
        }
264
    }
265
266
    /**
267
     * Add .html rewrite
268
     */
269
    private function rewriteHtml() {
270
        $rewriteBlock = $this->getRewriteBlock();
271
272
        $this->createConditionalRewrite($rewriteBlock, '%{DOCUMENT_ROOT}/$1.html -f', '^(.+?)/?$ /$1.html [L]');
273
    }
274
275
    /**
276
     * @param Block  $rewriteBlock
277
     * @param string $condition
278
     * @param string $rule
279
     */
280
    private function createConditionalRewrite(Block &$rewriteBlock, string $condition, string $rule) {
281
        $rewriteBlock->addLineBreak(1);
282
283
        $conditionLine = new Directive();
284
        $conditionLine->setName("RewriteCond {$condition}");
285
        $rewriteBlock->addChild($conditionLine);
286
287
        $ruleLine = new Directive();
288
        $ruleLine->setName("RewriteRule {$rule}");
289
        $rewriteBlock->addChild($ruleLine);
290
    }
291
292
    /**
293
     * @param string $modName
294
     *
295
     * @return null|Block
296
     */
297
    private function findHeaderBlockByModName(string $modName) {
298
        foreach ($this->contents as $content) {
299
            $arguments = $content->getArguments();
300
            if (reset($arguments) === $modName) {
301
                return $content;
302
            }
303
        }
304
305
        return null;
306
    }
307
}
308