Passed
Pull Request — master (#118)
by Nicolaas
02:37 queued 50s
created

FilesystemPublisher::compressFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 9
rs 10
1
<?php
2
3
namespace SilverStripe\StaticPublishQueue\Publisher;
4
5
use SilverStripe\Assets\Filesystem;
6
use SilverStripe\Control\HTTPResponse;
7
use function SilverStripe\StaticPublishQueue\PathToURL;
8
use SilverStripe\StaticPublishQueue\Publisher;
9
use function SilverStripe\StaticPublishQueue\URLtoPath;
10
11
class FilesystemPublisher extends Publisher
12
{
13
    /**
14
     * @var string
15
     */
16
    protected $destFolder = 'cache';
17
18
    /**
19
     * @var string
20
     */
21
    protected $fileExtension = 'html';
22
23
    /**
24
     * @var bool
25
     */
26
    private static $use_gzip_compression = true;
0 ignored issues
show
introduced by
The private property $use_gzip_compression is not used, and could be removed.
Loading history...
27
28
    /**
29
     * @return string
30
     */
31
    public function getDestPath()
32
    {
33
        $base = defined('PUBLIC_PATH') ? PUBLIC_PATH : BASE_PATH;
34
        return $base . DIRECTORY_SEPARATOR . $this->getDestFolder();
35
    }
36
37
    public function setDestFolder($destFolder)
38
    {
39
        $this->destFolder = $destFolder;
40
        return $this;
41
    }
42
43
    public function getDestFolder()
44
    {
45
        return $this->destFolder;
46
    }
47
48
    public function setFileExtension($fileExtension)
49
    {
50
        $fileExtension = strtolower($fileExtension);
51
        if (! in_array($fileExtension, ['html', 'php'], true)) {
52
            throw new \InvalidArgumentException(
53
                sprintf(
54
                    'Bad file extension "%s" passed to %s::%s',
55
                    $fileExtension,
56
                    static::class,
57
                    __FUNCTION__
58
                )
59
            );
60
        }
61
        $this->fileExtension = $fileExtension;
62
        return $this;
63
    }
64
65
    public function getFileExtension()
66
    {
67
        return $this->fileExtension;
68
    }
69
70
    public function purgeURL(string $url): array
71
    {
72
        if (! $url) {
73
            user_error('Bad url:' . var_export($url, true), E_USER_WARNING);
74
            return [];
75
        }
76
        if ($path = $this->URLtoPath($url)) {
77
            $success = $this->deleteFromPath($path . '.html') && $this->deleteFromPath($path . '.php');
78
            return [
79
                'success' => $success,
80
                'url' => $url,
81
                'path' => $this->getDestPath() . DIRECTORY_SEPARATOR . $path,
82
            ];
83
        }
84
        return [
85
            'success' => false,
86
            'url' => $url,
87
            'path' => false,
88
        ];
89
    }
90
91
    public function purgeAll(): bool
92
    {
93
        Filesystem::removeFolder($this->getDestPath());
94
95
        return file_exists($this->getDestPath()) ? false : true;
96
    }
97
98
    /**
99
     * @param string $url
100
     * @return array A result array
101
     */
102
    public function publishURL(string $url, ?bool $forcePublish = false): array
103
    {
104
        if (!$url) {
105
            user_error('Bad url:' . var_export($url, true), E_USER_WARNING);
106
            return [];
107
        }
108
        $success = false;
109
        $response = $this->generatePageResponse($url);
110
        $statusCode = $response->getStatusCode();
111
        $doPublish = ($forcePublish && $this->getFileExtension() === 'php') || $statusCode < 400;
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $doPublish = ($forcePubl...' || $statusCode < 400), Probably Intended Meaning: $doPublish = $forcePubli...' || $statusCode < 400)
Loading history...
112
113
        if ($statusCode < 300) {
114
            // publish success response
115
            $success = $this->publishPage($response, $url);
116
        } elseif ($statusCode < 400) {
117
            // publish redirect response
118
            $success = $this->publishRedirect($response, $url);
119
        } elseif ($doPublish) {
120
            // only publish error pages if we are able to send status codes via PHP
121
            $success = $this->publishPage($response, $url);
122
        }
123
        return [
124
            'published' => $doPublish,
125
            'success' => $success,
126
            'responsecode' => $statusCode,
127
            'url' => $url,
128
        ];
129
    }
130
131
    public function getPublishedURLs(?string $dir = null, array &$result = []): array
132
    {
133
        if ($dir === null) {
134
            $dir = $this->getDestPath();
135
        }
136
137
        $root = scandir($dir);
138
        foreach ($root as $fileOrDir) {
139
            if (strpos($fileOrDir, '.') === 0) {
140
                continue;
141
            }
142
            $fullPath = $dir . DIRECTORY_SEPARATOR . $fileOrDir;
143
            // we know html will always be generated, this prevents double ups
144
            if (is_file($fullPath) && pathinfo($fullPath, PATHINFO_EXTENSION) === 'html') {
145
                $result[] = $this->pathToURL($fullPath);
146
                continue;
147
            }
148
149
            if (is_dir($fullPath)) {
150
                $this->getPublishedURLs($fullPath, $result);
151
            }
152
        }
153
        return $result;
154
    }
155
156
    /**
157
     * @param HTTPResponse $response
158
     * @param string       $url
159
     * @return bool
160
     */
161
    protected function publishRedirect($response, $url)
162
    {
163
        $success = true;
164
        if ($path = $this->URLtoPath($url)) {
165
            $location = $response->getHeader('Location');
166
            if ($this->getFileExtension() === 'php') {
167
                $phpContent = $this->generatePHPCacheFile($response);
168
                $success = $this->saveToPath($phpContent, $path . '.php');
169
            }
170
            return $this->saveToPath($this->generateHTMLCacheRedirection($location), $path . '.html') && $success;
171
        }
172
        return false;
173
    }
174
175
    /**
176
     * @param HTTPResponse $response
177
     * @param string       $url
178
     * @return bool
179
     */
180
    protected function publishPage($response, $url)
181
    {
182
        $success = true;
183
        if ($path = $this->URLtoPath($url)) {
184
            // little hack to make sure we do not include pages with live forms.
185
            if (stripos($response->getBody(), 'name="SecurityID"')) {
186
                return false;
187
            }
188
            if ($this->getFileExtension() === 'php') {
189
                $phpContent = $this->generatePHPCacheFile($response);
190
                $success = $this->saveToPath($phpContent, $path . '.php');
191
            }
192
            return $this->saveToPath($response->getBody(), $path . '.html') && $success;
193
        }
194
        return false;
195
    }
196
197
    /**
198
     * returns true on access and false on failure
199
     * @param string $content
200
     * @param string $filePath
201
     * @return bool
202
     */
203
    protected function saveToPath($content, $filePath): bool
204
    {
205
        if (empty($content)) {
206
            return false;
207
        }
208
209
        // Write to a temporary file first
210
        $temporaryPath = tempnam(TEMP_PATH, 'filesystempublisher_');
211
        if (file_put_contents($temporaryPath, $content) === false) {
212
            return false;
213
        }
214
215
        // Move the temporary file to the desired location (prevents unlocked files from being read during write)
216
        $publishPath = $this->getDestPath() . DIRECTORY_SEPARATOR . $filePath;
217
        Filesystem::makeFolder(dirname($publishPath));
218
        $successWithPublish = rename($temporaryPath, $publishPath);
219
        if ($successWithPublish) {
220
            if (FilesystemPublisher::config()->get('use_gzip_compression')) {
221
                $publishPath = $this->compressFile($publishPath);
222
            }
223
        }
224
225
        return file_exists($publishPath);
226
    }
227
228
    protected function compressFile(string $publishPath): string
229
    {
230
        //we keep the html file for now ... so use second parameter to achieve this.
231
        $publishPathGZipped = $publishPath . '.gz';
232
        exec('rm ' . $publishPathGZipped . ' -rf');
233
        exec('gzip ' . $publishPath);
234
        exec('chmod 0777 ' . $publishPathGZipped);
235
236
        return $publishPathGZipped;
237
    }
238
239
    protected function deleteFromPath($filePath)
240
    {
241
        $deletePath = $this->getDestPath() . DIRECTORY_SEPARATOR . $filePath;
242
        if (file_exists($deletePath)) {
243
            $success = unlink($deletePath);
244
        } else {
245
            $success = true;
246
        }
247
        if (file_exists($deletePath . '.gz')) {
248
            return $this->deleteFromPath($deletePath . '.gz');
249
        }
250
251
        return $success;
252
    }
253
254
    protected function URLtoPath(string $url): string
255
    {
256
        return URLtoPath($url, BASE_URL, FilesystemPublisher::config()->get('domain_based_caching'));
257
    }
258
259
    protected function pathToURL(string $path): string
260
    {
261
        return PathToURL($path, $this->getDestPath(), FilesystemPublisher::config()->get('domain_based_caching'));
262
    }
263
}
264