Passed
Push — master ( 477334...e6c7a1 )
by Brent
05:39 queued 02:38
created

Htaccess::setupIndex()   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->setupIndex();
108
        $this->clearRewriteBlock();
109
110
        if ($this->redirectWww) {
111
            $this->rewriteWww();
112
        }
113
114
        if ($this->redirectHttps) {
115
            $this->rewriteHttps();
116
        }
117
118
        $this->rewriteCustomRedirects();
119
        $this->rewriteHtml();
120
121
        return (string) $this->contents;
122
    }
123
124
    /**
125
     * Get or create the headers block
126
     *
127
     * @return Block
128
     */
129 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...
130
        $headerBlock = $this->findHeaderBlockByModName('mod_headers.c');
131
132
        if (!$headerBlock) {
133
            $headerBlock = new Block('ifmodule');
134
            $headerBlock->addArgument('mod_headers.c');
135
136
            if ($this->contents instanceof HtaccessContainer) {
137
                $this->contents->append($headerBlock);
138
            }
139
        }
140
141
        return $headerBlock;
142
    }
143
144
    /**
145
     * Get or create a page block within the headers block
146
     *
147
     * @param Page $page
148
     *
149
     * @return Block
150
     */
151
    public function &getPageBlock(Page $page) : Block {
152
        $headerBlock = $this->getHeaderBlock();
153
        $pageId = trim($page->getId(), '/') ?? 'index';
154
        $pageId = pathinfo($pageId !== '' ? "{$pageId}" : 'index', PATHINFO_BASENAME);
155
        $pageName = '"^' . $pageId . '\.html$"';
156
157
        $pageBlock = $this->findPageBlockByParentAndName($headerBlock, $pageName);
158
159
        if (!$pageBlock) {
160
            $pageBlock = new Block('filesmatch');
161
            $pageBlock->addArgument($pageName);
162
            $headerBlock->addChild($pageBlock);
163
        }
164
165
        return $pageBlock;
166
    }
167
168
    /**
169
     * Clear all page header blocks
170
     */
171
    public function clearPageBlocks() {
172
        $headerBlock = $this->getHeaderBlock();
173
174
        foreach ($headerBlock as $content) {
175
            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...
176
                $headerBlock->removeChild($content);
177
            }
178
        }
179
    }
180
181
    /**
182
     * Clear all rewrites
183
     */
184
    public function clearRewriteBlock() {
185
        $rewriteBlock = $this->getRewriteBlock();
186
187
        foreach ($rewriteBlock as $content) {
188
            if (
189
                $content instanceof WhiteLine ||
190
                ($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...
191
            ) {
192
                $rewriteBlock->removeChild($content);
193
            }
194
        }
195
    }
196
197
    /**
198
     * Get or create the rewrite block
199
     *
200
     * @return Block
201
     */
202 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...
203
        $rewriteBlock = $this->findHeaderBlockByModName('mod_rewrite.c');
204
205
        if (!$rewriteBlock) {
206
            $rewriteBlock = new Block('ifmodule');
207
            $rewriteBlock->addArgument('mod_rewrite.c');
208
209
            if ($this->contents instanceof HtaccessContainer) {
210
                $this->contents->append($rewriteBlock);
211
            }
212
        }
213
214
        return $rewriteBlock;
215
    }
216
217
    /**
218
     * @param Block  $headerBlock
219
     * @param string $pageName
220
     *
221
     * @return null|Block
222
     */
223
    private function findPageBlockByParentAndName(Block $headerBlock, string $pageName) {
224
        foreach ($headerBlock as $content) {
225
            $arguments = $content->getArguments();
226
227
            if (reset($arguments) === $pageName) {
228
                return $content;
229
            }
230
        }
231
232
        return null;
233
    }
234
235
    private function setupIndex() {
236
        foreach ($this->contents as $content) {
237
            if ($content instanceof Directive && $content->getName() === 'Options') {
0 ignored issues
show
Bug introduced by
Consider using $content->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
238
                return;
239
            }
240
        }
241
242
        $this->contents[] = new Directive('Options -Indexes');
243
    }
244
245
    /**
246
     * Add www rewrite
247
     */
248
    private function rewriteWww() {
249
        $rewriteBlock = $this->getRewriteBlock();
250
251
        $this->createConditionalRewrite($rewriteBlock, '%{HTTP_HOST} !^www\.', '^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]');
252
    }
253
254
    /**
255
     * Add https rewrite
256
     */
257
    private function rewriteHttps() {
258
        $rewriteBlock = $this->getRewriteBlock();
259
260
        $this->createConditionalRewrite($rewriteBlock, '%{HTTPS} off', '(.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]');
261
    }
262
263
    /**
264
     * Add custom rewrites
265
     */
266
    private function rewriteCustomRedirects() {
267
        $rewriteBlock = $this->getRewriteBlock();
268
        $rewriteBlock->addLineBreak(1);
269
270
        foreach ($this->redirects as $from => $to) {
271
            $ruleLine = new Directive();
272
            $ruleLine->setName("RedirectMatch 301 ^{$from}$ {$to}");
273
            $rewriteBlock->addChild($ruleLine);
274
        }
275
    }
276
277
    /**
278
     * Add .html rewrite
279
     */
280
    private function rewriteHtml() {
281
        $rewriteBlock = $this->getRewriteBlock();
282
283
        $this->createConditionalRewrite($rewriteBlock, '%{DOCUMENT_ROOT}/$1.html -f', '^(.+?)/?$ /$1.html [L]');
284
    }
285
286
    /**
287
     * @param Block  $rewriteBlock
288
     * @param string $condition
289
     * @param string $rule
290
     */
291
    private function createConditionalRewrite(Block &$rewriteBlock, string $condition, string $rule) {
292
        $rewriteBlock->addLineBreak(1);
293
294
        $conditionLine = new Directive();
295
        $conditionLine->setName("RewriteCond {$condition}");
296
        $rewriteBlock->addChild($conditionLine);
297
298
        $ruleLine = new Directive();
299
        $ruleLine->setName("RewriteRule {$rule}");
300
        $rewriteBlock->addChild($ruleLine);
301
    }
302
303
    /**
304
     * @param string $modName
305
     *
306
     * @return null|Block
307
     */
308
    private function findHeaderBlockByModName(string $modName) {
309
        foreach ($this->contents as $content) {
310
            $arguments = $content->getArguments();
311
            if (reset($arguments) === $modName) {
312
                return $content;
313
            }
314
        }
315
316
        return null;
317
    }
318
}
319