UpdateHtaccess   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 133
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 61
dl 0
loc 133
rs 10
c 0
b 0
f 0
wmc 25

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getSubscribedEvents() 0 4 1
B update() 0 20 7
A __construct() 0 2 1
A replaceFile() 0 9 2
B getLinesFromMarkedFile() 0 45 11
A updateByMarkers() 0 19 3
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\Framework\Update\Services;
4
5
use Shopware\Core\Framework\Log\Package;
6
use Shopware\Core\Framework\Update\Event\UpdatePostFinishEvent;
7
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
8
9
/**
10
 * @internal
11
 */
12
#[Package('system-settings')]
13
class UpdateHtaccess implements EventSubscriberInterface
14
{
15
    private const MARKER_START = '# BEGIN Shopware';
16
    private const MARKER_STOP = '# END Shopware';
17
    private const INSTRUCTIONS = '# The directives (lines) between "# BEGIN Shopware" and "# END Shopware" are dynamically generated. Any changes to the directives between these markers will be overwritten.';
18
19
    private const OLD_FILES = [
20
        '9ab5be8c4bbff3490f3ae367af8a30d7', // https://github.com/shopware/production/commit/bebf9adc90bf5d7b0d53a149cc5bdba328696086
21
        'ba812f2a64b337b032b10685ca6e2308', // https://github.com/shopware/production/commit/18ce6ffc904b8d2d237dc4ee6654c1fa9a6df719
22
    ];
23
24
    /**
25
     * @internal
26
     */
27
    public function __construct(private readonly string $htaccessPath)
28
    {
29
    }
30
31
    public static function getSubscribedEvents(): array
32
    {
33
        return [
34
            UpdatePostFinishEvent::class => 'update',
35
        ];
36
    }
37
38
    public function update(): void
39
    {
40
        if (!file_exists($this->htaccessPath) || !file_exists($this->htaccessPath . '.dist')) {
41
            return;
42
        }
43
44
        if (\in_array(md5_file($this->htaccessPath), self::OLD_FILES, true)) {
45
            $this->replaceFile($this->htaccessPath);
46
47
            return;
48
        }
49
50
        $content = file_get_contents($this->htaccessPath);
51
52
        // User has deleted the markers. So we will ignore the update process
53
        if (!$content || !str_contains($content, self::MARKER_START) || !str_contains($content, self::MARKER_STOP)) {
54
            return;
55
        }
56
57
        $this->updateByMarkers($this->htaccessPath);
58
    }
59
60
    /**
61
     * Replace entire .htaccess from dist
62
     */
63
    private function replaceFile(string $path): void
64
    {
65
        $dist = $path . '.dist';
66
67
        $perms = fileperms($dist);
68
        copy($dist, $path);
69
70
        if ($perms) {
71
            chmod($path, $perms | 0644);
72
        }
73
    }
74
75
    private function updateByMarkers(string $path): void
76
    {
77
        [$pre, $_, $post] = $this->getLinesFromMarkedFile($path);
78
        [$_, $existing, $_] = $this->getLinesFromMarkedFile($path . '.dist');
79
80
        if (!\in_array(self::INSTRUCTIONS, $existing, true)) {
81
            array_unshift($existing, self::INSTRUCTIONS);
82
        }
83
84
        array_unshift($existing, self::MARKER_START);
85
        $existing[] = self::MARKER_STOP;
86
87
        $newFile = implode("\n", [...$pre, ...$existing, ...$post]);
88
89
        $perms = fileperms($path);
90
        file_put_contents($path, $newFile);
91
92
        if ($perms) {
93
            chmod($path, $perms | 0644);
94
        }
95
    }
96
97
    /**
98
     * @return array{0: list<string>, 1: list<string>, 2: list<string>}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{0: list<string>, 1...ring>, 2: list<string>} at position 4 could not be parsed: Expected '}' at position 4, but found 'list'.
Loading history...
99
     */
100
    private function getLinesFromMarkedFile(string $path): array
101
    {
102
        $fp = fopen($path, 'rb+');
103
        if (!$fp) {
0 ignored issues
show
introduced by
$fp is of type resource, thus it always evaluated to false.
Loading history...
104
            // @codeCoverageIgnoreStart
105
            return [[], [], []];
106
            // @codeCoverageIgnoreEnd
107
        }
108
109
        $lines = [];
110
        while (!feof($fp)) {
111
            if ($line = fgets($fp)) {
112
                $lines[] = rtrim($line, "\r\n");
113
            }
114
        }
115
116
        $foundStart = false;
117
        $foundStop = false;
118
        $preLines = [];
119
        $postLines = [];
120
        $existingLines = [];
121
122
        foreach ($lines as $line) {
123
            if (!$foundStart && str_starts_with($line, self::MARKER_START)) {
124
                $foundStart = true;
125
126
                continue;
127
            }
128
129
            if (!$foundStop && str_starts_with($line, self::MARKER_STOP)) {
130
                $foundStop = true;
131
132
                continue;
133
            }
134
135
            if (!$foundStart) {
136
                $preLines[] = $line;
137
            } elseif ($foundStop) {
138
                $postLines[] = $line;
139
            } else {
140
                $existingLines[] = $line;
141
            }
142
        }
143
144
        return [$preLines, $existingLines, $postLines];
145
    }
146
}
147