Issues (1)

src/CDNMiddleware.php (1 issue)

Severity
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
94
        if (($response !== null) && ($this->canRun($request) === true) && (!$this->getIsXML($response))) {
95
            $response->addHeader('X-CDN-Rewrites', 'Enabled');
96
97
            $body = $response->getBody() ?: '';
98
            $this->rewriteTags($body, $response);
99
            $this->addPrefetch($body, $response);
100
            $response->setBody($body);
101
102
            if ($this->config()->get('add_debug_headers') == true) {
103
                $response->addHeader('X-CDN-Domain', $this->config()->get('cdn_domain'));
104
                $response->addHeader('X-CDN-Dir', $this->getSubdirectory());
105
            }
106
107
            if ($this->config()->get('cdn_rewrite') === true) {
108
                $response->addHeader('X-CDN-Module', 'Active');
109
            }
110
        }
111
112
        return $response;
113
    }
114
115
    /**
116
     * Check if we're OK to execute
117
     * @return bool
118
     */
119
    private function canRun(HTTPRequest $request)
120
    {
121
        $confEnabled = $this->config()->get('cdn_rewrite');
122
        $devEnabled = ((!Director::isDev()) || ($this->config()->get('enable_in_dev')));
123
        $notAdmin = !$this->getIsAdmin($request);
124
        $previewOK = true;
125
        if ($request->getVar('CMSPreview') == 1) {
126
            if (!$this->config()->get('enable_in_preview')) {
127
                $previewOK = false;
128
            }
129
        }
130
131
        return ($confEnabled && $devEnabled && $previewOK && $notAdmin);
132
    }
133
134
135
    /**
136
     * Try to determine if the response is XML / XSL
137
     * @param HTTPResponse $response
138
     * @return bool
139
     */
140
    public function getIsXML(HTTPResponse $response) {
141
        $ctHeader = $response->getHeader('Content-Type');
142
        if (stripos($ctHeader, 'xml') !== false || stripos($ctHeader, 'xsl') !== false) {
143
            return true;
144
        }
145
        return false;
146
    }
147
148
149
    /**
150
     * Rewrite all the tags we need
151
     * @param $body
152
     * @param $response
153
     */
154
    private function rewriteTags(&$body, &$response)
0 ignored issues
show
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

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