Passed
Push — develop ( 7457f7...67cda5 )
by Brent
03:05
created

Htaccess::rewriteHttps()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
rs 9.4285
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
13
class Htaccess
14
{
15
    /**
16
     * @var Filesystem
17
     */
18
    private $fs;
19
20
    /**
21
     * @var Parser
22
     */
23
    private $parser;
24
25
    /**
26
     * @var array|\ArrayAccess|HtaccessContainer
27
     */
28
    private $contents;
29
30
    /**
31
     * @var bool
32
     */
33
    private $redirectHttps = false;
34
35
    /**
36
     * @var bool
37
     */
38
    private $redirectWww = false;
39
40
    /**
41
     * @var array
42
     */
43
    private $redirects = [];
44
45
    /**
46
     * Htaccess constructor.
47
     *
48
     * @param string $path
49
     *
50
     * @throws ConfigurationException
51
     */
52
    public function __construct(string $path) {
53
        $this->fs = new Filesystem();
54
55
        if (!$this->fs->exists($path)) {
56
            $this->fs->dumpFile($path, file_get_contents(__DIR__ . '/../../../../.htaccess'));
57
        }
58
59
        $this->parser = new Parser(new \SplFileObject($path));
60
        $this->parser->ignoreWhitelines(false);
61
        $this->contents = $this->parser->parse();
62
    }
63
64
    /**
65
     * @param bool $redirectHttps
66
     *
67
     * @return Htaccess
68
     */
69
    public function setRedirectHttps(bool $redirectHttps = false) : Htaccess {
70
        $this->redirectHttps = $redirectHttps;
71
72
        return $this;
73
    }
74
75
    /**
76
     * @param bool $redirectWww
77
     *
78
     * @return Htaccess
79
     */
80
    public function setRedirectWww(bool $redirectWww = false) : Htaccess {
81
        $this->redirectWww = $redirectWww;
82
83
        return $this;
84
    }
85
86
    /**
87
     * Add custom redirects handles by htaccess
88
     *
89
     * @param string $from
90
     * @param string $to
91
     *
92
     * @return Htaccess
93
     */
94
    public function addRedirect(string $from, string $to) : Htaccess {
95
        $this->redirects[$from] = $to;
96
97
        return $this;
98
    }
99
100
    /**
101
     * Parse the modified .htaccess
102
     *
103
     * @return string
104
     */
105
    public function parse() : string {
106
        $this->clearRewriteBlock();
107
108
        if ($this->redirectWww) {
109
            $this->rewriteWww();
110
        }
111
112
        if ($this->redirectHttps) {
113
            $this->rewriteHttps();
114
        }
115
116
        $this->rewriteCustomRedirects();
117
        $this->rewriteHtml();
118
119
        return (string) $this->contents;
120
    }
121
122
    /**
123
     * Get or create the headers block
124
     *
125
     * @return Block
126
     */
127 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...
128
        $headerBlock = $this->findHeaderBlockByModName('mod_headers.c');
129
130
        if (!$headerBlock) {
131
            $headerBlock = new Block('ifmodule');
132
            $headerBlock->addArgument('mod_headers.c');
133
134
            if ($this->contents instanceof HtaccessContainer) {
135
                $this->contents->append($headerBlock);
136
            }
137
        }
138
139
        return $headerBlock;
140
    }
141
142
    /**
143
     * Get or create a page block within the headers block
144
     *
145
     * @param Page $page
146
     *
147
     * @return Block
148
     */
149
    public function &getPageBlock(Page $page) : Block {
150
        $headerBlock = $this->getHeaderBlock();
151
        $pageId = trim($page->getId(), '/') ?? 'index';
152
        $pageId = pathinfo($pageId !== '' ? "{$pageId}" : 'index', PATHINFO_BASENAME);
153
        $pageName = '"^' . $pageId . '\.html$"';
154
155
        $pageBlock = $this->findPageBlockByParentAndName($headerBlock, $pageName);
156
157
        if (!$pageBlock) {
158
            $pageBlock = new Block('filesmatch');
159
            $pageBlock->addArgument($pageName);
160
            $headerBlock->addChild($pageBlock);
161
        }
162
163
        return $pageBlock;
164
    }
165
166
    /**
167
     * Clear all page header blocks
168
     */
169
    public function clearPageBlocks() {
170
        $headerBlock = $this->getHeaderBlock();
171
172
        foreach ($headerBlock as $content) {
173
            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...
174
                $headerBlock->removeChild($content);
175
            }
176
        }
177
    }
178
179
    /**
180
     * Clear all rewrites
181
     */
182
    public function clearRewriteBlock() {
183
        $rewriteBlock = $this->getRewriteBlock();
184
185
        foreach ($rewriteBlock as $content) {
186
            if ($content instanceof Block) {
187
                $rewriteBlock->removeChild($content);
188
            }
189
        }
190
    }
191
192
    /**
193
     * Get or create the rewrite block
194
     *
195
     * @return Block
196
     */
197 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...
198
        $rewriteBlock = $this->findHeaderBlockByModName('mod_rewrite.c');
199
200
        if (!$rewriteBlock) {
201
            $rewriteBlock = new Block('ifmodule');
202
            $rewriteBlock->addArgument('mod_rewrite.c');
203
204
            if ($this->contents instanceof HtaccessContainer) {
205
                $this->contents->append($rewriteBlock);
206
            }
207
        }
208
209
        return $rewriteBlock;
210
    }
211
212
    /**
213
     * @param Block  $headerBlock
214
     * @param string $pageName
215
     *
216
     * @return null|Block
217
     */
218
    private function findPageBlockByParentAndName(Block $headerBlock, string $pageName) {
219
        foreach ($headerBlock as $content) {
220
            $arguments = $content->getArguments();
221
222
            if (reset($arguments) === $pageName) {
223
                return $content;
224
            }
225
        }
226
227
        return null;
228
    }
229
230
    /**
231
     * Add www rewrite
232
     */
233
    private function rewriteWww() {
234
        $rewriteBlock = $this->getRewriteBlock();
235
236
        $this->createConditionalRewrite($rewriteBlock, '%{HTTP_HOST} !^www\.', '^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]');
237
    }
238
239
    /**
240
     * Add https rewrite
241
     */
242
    private function rewriteHttps() {
243
        $rewriteBlock = $this->getRewriteBlock();
244
245
        $this->createConditionalRewrite($rewriteBlock, '%{HTTPS} off', '(.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]');
246
    }
247
248
    /**
249
     * Add custom rewrites
250
     */
251
    private function rewriteCustomRedirects() {
252
        $rewriteBlock = $this->getRewriteBlock();
253
        $rewriteBlock->addLineBreak(1);
254
255
        foreach ($this->redirects as $from => $to) {
256
            $ruleLine = new Directive();
257
            $ruleLine->setName("RewriteRule ^{$from}$ {$to} [R=301,QSA,L]");
258
            $rewriteBlock->addChild($ruleLine);
259
        }
260
    }
261
262
    /**
263
     * Add .html rewrite
264
     */
265
    private function rewriteHtml() {
266
        $rewriteBlock = $this->getRewriteBlock();
267
268
        $this->createConditionalRewrite($rewriteBlock, '%{DOCUMENT_ROOT}/$1.html -f', '^(.+?)/?$ /$1.html [L]');
269
    }
270
271
    /**
272
     * @param Block  $rewriteBlock
273
     * @param string $condition
274
     * @param string $rule
275
     */
276
    private function createConditionalRewrite(Block &$rewriteBlock, string $condition, string $rule) {
277
        $rewriteBlock->addLineBreak(1);
278
279
        $conditionLine = new Directive();
280
        $conditionLine->setName("RewriteCond {$condition}");
281
        $rewriteBlock->addChild($conditionLine);
282
283
        $ruleLine = new Directive();
284
        $ruleLine->setName("RewriteRule {$rule}");
285
        $rewriteBlock->addChild($ruleLine);
286
    }
287
288
    /**
289
     * @param string $modName
290
     *
291
     * @return null|Block
292
     */
293
    private function findHeaderBlockByModName(string $modName) {
294
        foreach ($this->contents as $content) {
295
            $arguments = $content->getArguments();
296
            if (reset($arguments) === $modName) {
297
                return $content;
298
            }
299
        }
300
301
        return null;
302
    }
303
}
304