1 | <?php |
||||
2 | |||||
3 | namespace SilverStripe\EnvironmentCheck\Checks; |
||||
4 | |||||
5 | use SilverStripe\Control\Director; |
||||
6 | use SilverStripe\Control\Controller; |
||||
7 | use SilverStripe\ORM\ValidationResult; |
||||
8 | use Psr\Http\Message\ResponseInterface; |
||||
9 | use SilverStripe\Core\Config\Configurable; |
||||
10 | use SilverStripe\EnvironmentCheck\Traits\Fetcher; |
||||
11 | use SilverStripe\EnvironmentCheck\EnvironmentCheck; |
||||
12 | |||||
13 | /** |
||||
14 | * Check cache headers for any response, can specify directives that must be included and |
||||
15 | * also must be excluded from Cache-Control headers in response. Also checks for |
||||
16 | * existence of ETag. |
||||
17 | * |
||||
18 | * @example SilverStripe\EnvironmentCheck\Checks\CacheHeadersCheck("/",["must-revalidate", "max-age=120"],["no-store"]) |
||||
19 | * @package environmentcheck |
||||
20 | */ |
||||
21 | class CacheHeadersCheck implements EnvironmentCheck |
||||
22 | { |
||||
23 | use Fetcher; |
||||
24 | |||||
25 | /** |
||||
26 | * Settings that must be included in the Cache-Control header |
||||
27 | * |
||||
28 | * @var array |
||||
29 | */ |
||||
30 | protected $mustInclude = []; |
||||
31 | |||||
32 | /** |
||||
33 | * Settings that must be excluded in the Cache-Control header |
||||
34 | * |
||||
35 | * @var array |
||||
36 | */ |
||||
37 | protected $mustExclude = []; |
||||
38 | |||||
39 | /** |
||||
40 | * Result to keep track of status and messages for all checks, reuses |
||||
41 | * ValidationResult for convenience. |
||||
42 | * |
||||
43 | * @var ValidationResult |
||||
44 | */ |
||||
45 | protected $result; |
||||
46 | |||||
47 | /** |
||||
48 | * Set up with URL, arrays of header settings to check. |
||||
49 | * |
||||
50 | * @param string $url |
||||
51 | * @param array $mustInclude Settings that must be included in Cache-Control |
||||
52 | * @param array $mustExclude Settings that must be excluded in Cache-Control |
||||
53 | */ |
||||
54 | public function __construct($url = '', $mustInclude = [], $mustExclude = []) |
||||
55 | { |
||||
56 | $this->setURL($url); |
||||
57 | $this->mustInclude = $mustInclude; |
||||
58 | $this->mustExclude = $mustExclude; |
||||
59 | } |
||||
60 | |||||
61 | /** |
||||
62 | * Check that correct caching headers are present. |
||||
63 | * |
||||
64 | * @return void |
||||
65 | */ |
||||
66 | public function check() |
||||
67 | { |
||||
68 | // Using a validation result to capture messages |
||||
69 | $this->result = new ValidationResult(); |
||||
70 | |||||
71 | $response = $this->client->get($this->getURL()); |
||||
72 | $fullURL = $this->getURL(); |
||||
73 | if ($response === null) { |
||||
74 | return [ |
||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||||
75 | EnvironmentCheck::ERROR, |
||||
76 | "Cache headers check request failed for $fullURL", |
||||
77 | ]; |
||||
78 | } |
||||
79 | |||||
80 | //Check that Etag exists |
||||
81 | $this->checkEtag($response); |
||||
82 | |||||
83 | // Check Cache-Control settings |
||||
84 | $this->checkCacheControl($response); |
||||
85 | |||||
86 | if ($this->result->isValid()) { |
||||
87 | return [ |
||||
0 ignored issues
–
show
|
|||||
88 | EnvironmentCheck::OK, |
||||
89 | $this->getMessage(), |
||||
90 | ]; |
||||
91 | } else { |
||||
92 | // @todo Ability to return a warning |
||||
93 | return [ |
||||
0 ignored issues
–
show
|
|||||
94 | EnvironmentCheck::ERROR, |
||||
95 | $this->getMessage(), |
||||
96 | ]; |
||||
97 | } |
||||
98 | } |
||||
99 | |||||
100 | /** |
||||
101 | * Collate messages from ValidationResult so that it is clear which parts |
||||
102 | * of the check passed and which failed. |
||||
103 | * |
||||
104 | * @return string |
||||
105 | */ |
||||
106 | private function getMessage() |
||||
107 | { |
||||
108 | $ret = ''; |
||||
109 | // Filter good messages |
||||
110 | $goodTypes = [ValidationResult::TYPE_GOOD, ValidationResult::TYPE_INFO]; |
||||
111 | $good = array_filter( |
||||
112 | $this->result->getMessages(), |
||||
113 | function ($val, $key) use ($goodTypes) { |
||||
0 ignored issues
–
show
The parameter
$key 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
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
114 | if (in_array($val['messageType'], $goodTypes)) { |
||||
115 | return true; |
||||
116 | } |
||||
117 | return false; |
||||
118 | }, |
||||
119 | ARRAY_FILTER_USE_BOTH |
||||
120 | ); |
||||
121 | if (!empty($good)) { |
||||
122 | $ret .= "GOOD: " . implode('; ', array_column($good, 'message')) . " "; |
||||
123 | } |
||||
124 | |||||
125 | // Filter bad messages |
||||
126 | $badTypes = [ValidationResult::TYPE_ERROR, ValidationResult::TYPE_WARNING]; |
||||
127 | $bad = array_filter( |
||||
128 | $this->result->getMessages(), |
||||
129 | function ($val, $key) use ($badTypes) { |
||||
0 ignored issues
–
show
The parameter
$key 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
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
130 | if (in_array($val['messageType'], $badTypes)) { |
||||
131 | return true; |
||||
132 | } |
||||
133 | return false; |
||||
134 | }, |
||||
135 | ARRAY_FILTER_USE_BOTH |
||||
136 | ); |
||||
137 | if (!empty($bad)) { |
||||
138 | $ret .= "BAD: " . implode('; ', array_column($bad, 'message')); |
||||
139 | } |
||||
140 | return $ret; |
||||
141 | } |
||||
142 | |||||
143 | /** |
||||
144 | * Check that ETag header exists |
||||
145 | * |
||||
146 | * @param ResponseInterface $response |
||||
147 | * @return void |
||||
148 | */ |
||||
149 | private function checkEtag(ResponseInterface $response) |
||||
150 | { |
||||
151 | $eTag = $response->getHeaderLine('ETag'); |
||||
152 | $fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url); |
||||
153 | |||||
154 | if ($eTag) { |
||||
155 | $this->result->addMessage( |
||||
156 | "$fullURL includes an Etag header in response", |
||||
157 | ValidationResult::TYPE_GOOD |
||||
158 | ); |
||||
159 | return; |
||||
160 | } |
||||
161 | $this->result->addError( |
||||
162 | "$fullURL is missing an Etag header", |
||||
163 | ValidationResult::TYPE_WARNING |
||||
164 | ); |
||||
165 | } |
||||
166 | |||||
167 | /** |
||||
168 | * Check that the correct header settings are either included or excluded. |
||||
169 | * |
||||
170 | * @param ResponseInterface $response |
||||
171 | * @return void |
||||
172 | */ |
||||
173 | private function checkCacheControl(ResponseInterface $response) |
||||
174 | { |
||||
175 | $cacheControl = $response->getHeaderLine('Cache-Control'); |
||||
176 | $vals = array_map('trim', explode(',', $cacheControl)); |
||||
177 | $fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url); |
||||
178 | |||||
179 | // All entries from must contain should be present |
||||
180 | if ($this->mustInclude == array_intersect($this->mustInclude, $vals)) { |
||||
181 | $matched = implode(",", $this->mustInclude); |
||||
182 | $this->result->addMessage( |
||||
183 | "$fullURL includes all settings: {$matched}", |
||||
184 | ValidationResult::TYPE_GOOD |
||||
185 | ); |
||||
186 | } else { |
||||
187 | $missing = implode(",", array_diff($this->mustInclude, $vals)); |
||||
188 | $this->result->addError( |
||||
189 | "$fullURL is excluding some settings: {$missing}" |
||||
190 | ); |
||||
191 | } |
||||
192 | |||||
193 | // All entries from must exclude should not be present |
||||
194 | if (empty(array_intersect($this->mustExclude, $vals))) { |
||||
195 | $missing = implode(",", $this->mustExclude); |
||||
196 | $this->result->addMessage( |
||||
197 | "$fullURL excludes all settings: {$missing}", |
||||
198 | ValidationResult::TYPE_GOOD |
||||
199 | ); |
||||
200 | } else { |
||||
201 | $matched = implode(",", array_intersect($this->mustExclude, $vals)); |
||||
202 | $this->result->addError( |
||||
203 | "$fullURL is including some settings: {$matched}" |
||||
204 | ); |
||||
205 | } |
||||
206 | } |
||||
207 | } |
||||
208 |