Passed
Push — master ( 23837d...bb8911 )
by Tim
04:53
created

CDNMiddleware::canRun()   B

Complexity

Conditions 7
Paths 24

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 1
Metric Value
cc 7
eloc 8
c 5
b 0
f 1
nc 24
nop 2
dl 0
loc 13
rs 8.8333
1
<?php
2
3
namespace DorsetDigital\CDNRewrite;
4
5
use SilverStripe\Admin\AdminRootController;
6
use SilverStripe\Control\Director;
7
use SilverStripe\Control\HTTPRequest;
8
use SilverStripe\Control\HTTPResponse;
9
use SilverStripe\Control\Middleware\HTTPMiddleware;
10
use SilverStripe\Core\Config\Configurable;
11
use SilverStripe\Core\Extensible;
12
use SilverStripe\Core\Injector\Injectable;
13
use SilverStripe\View\HTML;
14
15
class CDNMiddleware implements HTTPMiddleware
16
{
17
18
    use Injectable;
19
    use Configurable;
20
    use Extensible;
21
22
    /**
23
     * @config
24
     *
25
     * Enable rewriting
26
     * @var bool
27
     */
28
    private static $cdn_rewrite = false;
29
30
    /**
31
     * @config
32
     *
33
     * The cdn domain incl. protocol
34
     * @var string
35
     */
36
    private static $cdn_domain = '';
37
38
    /**
39
     * @config
40
     *
41
     * Enable rewrite in dev mode
42
     * @var bool
43
     */
44
    private static $enable_in_dev = false;
45
46
    /**
47
     * Enable in CMS preview
48
     * @var bool
49
     */
50
    private static $enable_in_preview = false;
51
52
    /**
53
     * @config
54
     *
55
     * Add debug headers for each operation
56
     * @var bool
57
     */
58
    private static $add_debug_headers = false;
59
60
    /**
61
     * @config
62
     *
63
     * Subdirectory name for the site
64
     * @var string
65
     */
66
    private static $subdirectory = '';
67
68
    /**
69
     * @config
70
     *
71
     * Add dns-prefetch links to the html head
72
     * @var boolean
73
     */
74
    private static $add_prefetch = false;
75
76
    /**
77
     * @config
78
     *
79
     * Array of prefixes we wish to rewrite
80
     * @var array
81
     */
82
    private static $rewrites = [];
83
84
    /**
85
     * Process the request
86
     * @param HTTPRequest $request
87
     * @param $delegate
88
     * @return
89
     */
90
    public function process(HTTPRequest $request, callable $delegate)
91
    {
92
        $response = $delegate($request);
93
        $response->addHeader('X-CDN-Module', 'Installed');
94
95
        if (($response !== null) && ($this->canRun($request, $response) === true)) {
96
            $response->addHeader('X-CDN-Rewrites', 'Enabled');
97
98
            $body = $response->getBody() ?: '';
99
            $this->rewriteTags($body, $response);
100
            $this->addPrefetch($body, $response);
101
            $response->setBody($body);
102
103
            if ($this->config()->get('add_debug_headers') == true) {
104
                $response->addHeader('X-CDN-Domain', $this->config()->get('cdn_domain'));
105
                $response->addHeader('X-CDN-Dir', $this->getSubdirectory());
106
            }
107
108
            if ($this->config()->get('cdn_rewrite') === true) {
109
                $response->addHeader('X-CDN-Module', 'Active');
110
            }
111
        }
112
113
        return $response;
114
    }
115
116
    /**
117
     * Check if we're OK to execute
118
     * @return bool
119
     */
120
    private function canRun(HTTPRequest $request, HTTPResponse $response)
0 ignored issues
show
Unused Code introduced by
The parameter $response is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

120
    private function canRun(HTTPRequest $request, /** @scrutinizer ignore-unused */ HTTPResponse $response)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
121
    {
122
        $confEnabled = $this->config()->get('cdn_rewrite');
123
        $devEnabled = ((!Director::isDev()) || ($this->config()->get('enable_in_dev')));
124
        $notAdmin = !$this->getIsAdmin($request);
125
        $previewOK = true;
126
        if ($request->getVar('CMSPreview') == 1) {
127
            if (!$this->config()->get('enable_in_preview')) {
128
                $previewOK = false;
129
            }
130
        }
131
132
        return ($confEnabled && $devEnabled && $previewOK && $notAdmin);
133
    }
134
135
136
    /**
137
     * Rewrite all the tags we need
138
     * @param $body
139
     * @param $response
140
     */
141
    private function rewriteTags(&$body, &$response)
0 ignored issues
show
Unused Code introduced by
The parameter $response is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

141
    private function rewriteTags(&$body, /** @scrutinizer ignore-unused */ &$response)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
142
    {
143
        $cdn = $this->config()->get('cdn_domain');
144
        $subDir = $this->getSubdirectory();
145
        $prefixes = $this->config()->get('rewrites');
146
147
        foreach ($prefixes as $prefix) {
148
            $cleanPrefix = trim($prefix, '/');
149
150
            $patterns = [
151
                '/src=(["\'])?' . preg_quote('/' . $subDir . $cleanPrefix, '/') . '/i', // Match src attribute with optional quotes
152
                '/href=(["\'])?' . preg_quote('/' . $subDir . $cleanPrefix, '/') . '/i', // Match href attribute with optional quotes
153
                '/background-image:\s*url\((["\'])?\/?' . preg_quote($subDir . $cleanPrefix, '/') . '/i', // Match background-image with optional quotes and leading slash
154
                '/' . preg_quote(Director::absoluteBaseURL() . $cleanPrefix, '/') . '/i' // Match absolute URL
155
            ];
156
157
            $replacements = [
158
                'src=$1' . $cdn . '/' . $subDir . $cleanPrefix, // Use backreference to preserve matching quote style
159
                'href=$1' . $cdn . '/' . $subDir . $cleanPrefix, // Use backreference to preserve matching quote style
160
                'background-image: url($1' . $cdn . '/' . $subDir . $cleanPrefix, // Use backreference for quotes
161
                $cdn . '/' . $subDir . $cleanPrefix // Replace absolute URL
162
            ];
163
164
            $this->extend('updateRewriteSearch', $patterns);
165
            $this->extend('updateRewriteReplacements', $replacements);
166
167
            $body = preg_replace($patterns, $replacements, $body);
168
        }
169
    }
170
171
172
    private function addPrefetch(&$body, &$response)
173
    {
174
        if ($this->config()->get('add_prefetch') === true) {
175
            $prefetchTag = $this->getPrefetchTag();
176
            $body = str_replace('<head>', "<head>" . $prefetchTag, $body);
177
            if ($this->config()->get('add_debug_headers') == true) {
178
                $response->addHeader('X-CDN-Prefetch', 'Enabled');
179
            }
180
        }
181
    }
182
183
    private function getSubdirectory()
184
    {
185
        $subDir = trim($this->config()->get('subdirectory'), '/');
186
        if ($subDir != "") {
187
            $subDir = $subDir . '/';
188
        }
189
        return $subDir;
190
    }
191
192
    private function getPrefetchTag()
193
    {
194
        $atts = [
195
            'rel' => 'dns-prefetch',
196
            'href' => $this->config()->get('cdn_domain')
197
        ];
198
        $pfTag = "\n" . HTML::createTag('link', $atts);
199
200
        return $pfTag;
201
    }
202
203
    /**
204
     * Determine whether the website is being viewed from an admin protected area or not
205
     * (shamelessly based on https://github.com/silverstripe/silverstripe-subsites)
206
     *
207
     * @param HTTPRequest $request
208
     * @return bool
209
     */
210
    private function getIsAdmin(HTTPRequest $request)
211
    {
212
        $adminPath = AdminRootController::admin_url();
213
        $currentPath = rtrim($request->getURL(), '/') . '/';
214
        if (substr($currentPath, 0, strlen($adminPath)) === $adminPath) {
215
            return true;
216
        }
217
        return false;
218
    }
219
}
220