|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* File containing the LocationAwareStore class. |
|
5
|
|
|
* |
|
6
|
|
|
* @copyright Copyright (C) eZ Systems AS. All rights reserved. |
|
7
|
|
|
* @license For full copyright and license information view LICENSE file distributed with this source code. |
|
8
|
|
|
*/ |
|
9
|
|
|
namespace eZ\Publish\Core\MVC\Symfony\Cache\Http; |
|
10
|
|
|
|
|
11
|
|
|
use Symfony\Component\HttpKernel\HttpCache\Store; |
|
12
|
|
|
use Symfony\Component\HttpFoundation\Response; |
|
13
|
|
|
use Symfony\Component\HttpFoundation\Request; |
|
14
|
|
|
use Symfony\Component\Filesystem\Filesystem; |
|
15
|
|
|
use Symfony\Component\Filesystem\Exception\IOException; |
|
16
|
|
|
|
|
17
|
|
|
/** |
|
18
|
|
|
* LocationAwareStore implements all the logic for storing cache metadata regarding locations. |
|
19
|
|
|
* |
|
20
|
|
|
* @deprecated since 6.8. Replaced by TagAwareStore from the http-cache multi-tagging package. |
|
21
|
|
|
*/ |
|
22
|
|
|
class LocationAwareStore extends Store implements ContentPurger |
|
|
|
|
|
|
23
|
|
|
{ |
|
24
|
|
|
const LOCATION_CACHE_DIR = 'ezlocation'; |
|
25
|
|
|
const LOCATION_STALE_CACHE_DIR = 'ezlocation_stale'; |
|
26
|
|
|
|
|
27
|
|
|
/** |
|
28
|
|
|
* @var \Symfony\Component\Filesystem\Filesystem |
|
29
|
|
|
*/ |
|
30
|
|
|
private $fs; |
|
31
|
|
|
|
|
32
|
|
|
/** |
|
33
|
|
|
* Injects a Filesystem instance |
|
34
|
|
|
* For unit tests only. |
|
35
|
|
|
* |
|
36
|
|
|
* @internal |
|
37
|
|
|
* |
|
38
|
|
|
* @param \Symfony\Component\Filesystem\Filesystem $fs |
|
39
|
|
|
*/ |
|
40
|
|
|
public function setFilesystem(Filesystem $fs) |
|
41
|
|
|
{ |
|
42
|
|
|
$this->fs = $fs; |
|
43
|
|
|
} |
|
44
|
|
|
|
|
45
|
|
|
/** |
|
46
|
|
|
* @return \Symfony\Component\Filesystem\Filesystem |
|
47
|
|
|
*/ |
|
48
|
|
|
public function getFilesystem() |
|
49
|
|
|
{ |
|
50
|
|
|
if (!isset($this->fs)) { |
|
51
|
|
|
$this->fs = new Filesystem(); |
|
52
|
|
|
} |
|
53
|
|
|
|
|
54
|
|
|
return $this->fs; |
|
55
|
|
|
} |
|
56
|
|
|
|
|
57
|
|
|
/** |
|
58
|
|
|
* Injects eZ Publish specific information in the content digest if needed. |
|
59
|
|
|
* X-Location-Id response header is set in the ViewController. |
|
60
|
|
|
* |
|
61
|
|
|
* @see \eZ\Publish\Core\MVC\Symfony\Controller\Content\ViewController::viewLocation() |
|
62
|
|
|
* |
|
63
|
|
|
* @param \Symfony\Component\HttpFoundation\Response $response |
|
64
|
|
|
* |
|
65
|
|
|
* @return string |
|
66
|
|
|
*/ |
|
67
|
|
|
protected function generateContentDigest(Response $response) |
|
68
|
|
|
{ |
|
69
|
|
|
$digest = parent::generateContentDigest($response); |
|
70
|
|
|
if (!$response->headers->has('X-Location-Id')) { |
|
71
|
|
|
return $digest; |
|
72
|
|
|
} |
|
73
|
|
|
|
|
74
|
|
|
return static::LOCATION_CACHE_DIR . "/{$response->headers->get('X-Location-Id')}/$digest"; |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* Returns the right path where cache is being stored. |
|
79
|
|
|
* Will detect if $key is eZ Publish specific. |
|
80
|
|
|
* |
|
81
|
|
|
* @param string $key |
|
82
|
|
|
* |
|
83
|
|
|
* @return string |
|
84
|
|
|
*/ |
|
85
|
|
|
public function getPath($key) |
|
86
|
|
|
{ |
|
87
|
|
|
if (strpos($key, static::LOCATION_CACHE_DIR) === false) { |
|
88
|
|
|
return parent::getPath($key); |
|
89
|
|
|
} |
|
90
|
|
|
|
|
91
|
|
|
$prefix = ''; |
|
92
|
|
|
if (($pos = strrpos($key, DIRECTORY_SEPARATOR)) !== false) { |
|
93
|
|
|
$prefix = substr($key, 0, $pos) . DIRECTORY_SEPARATOR; |
|
94
|
|
|
$key = substr($key, $pos + 1); |
|
95
|
|
|
|
|
96
|
|
|
list($locationCacheDir, $locationId) = explode(DIRECTORY_SEPARATOR, $prefix); |
|
97
|
|
|
unset($locationCacheDir); |
|
98
|
|
|
// If cache purge is in progress, serve stale cache instead of regular cache. |
|
99
|
|
|
// We first check for a global cache purge, then for the current location. |
|
100
|
|
|
foreach ([$this->getLocationCacheLockName(), $this->getLocationCacheLockName($locationId)] as $cacheLockFile) { |
|
101
|
|
|
if (is_file($cacheLockFile)) { |
|
102
|
|
|
if (function_exists('posix_kill')) { |
|
103
|
|
|
// Check if purge process is still running. If not, remove the lock file to unblock future cache purge |
|
104
|
|
|
if (!posix_kill(file_get_contents($cacheLockFile), 0)) { |
|
105
|
|
|
$fs = $this->getFilesystem(); |
|
106
|
|
|
$fs->remove([$cacheLockFile, $this->getLocationCacheDir($locationId)]); |
|
107
|
|
|
goto returnCachePath; |
|
108
|
|
|
} |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
$prefix = str_replace(static::LOCATION_CACHE_DIR, static::LOCATION_STALE_CACHE_DIR, $prefix); |
|
112
|
|
|
} |
|
113
|
|
|
} |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
|
|
returnCachePath: |
|
117
|
|
|
return $this->root . DIRECTORY_SEPARATOR . $prefix . |
|
118
|
|
|
substr($key, 0, 2) . DIRECTORY_SEPARATOR . |
|
119
|
|
|
substr($key, 2, 2) . DIRECTORY_SEPARATOR . |
|
120
|
|
|
substr($key, 4, 2) . DIRECTORY_SEPARATOR . |
|
121
|
|
|
substr($key, 6); |
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
|
|
/** |
|
125
|
|
|
* Purges data from $request. |
|
126
|
|
|
* If X-Location-Id or X-Group-Location-Id header is present, the store will purge cache for given locationId or group of locationIds. |
|
127
|
|
|
* If not, regular purge by URI will occur. |
|
128
|
|
|
* |
|
129
|
|
|
* @param \Symfony\Component\HttpFoundation\Request $request |
|
130
|
|
|
* |
|
131
|
|
|
* @return bool True if purge was successful. False otherwise |
|
132
|
|
|
*/ |
|
133
|
|
|
public function purgeByRequest(Request $request) |
|
134
|
|
|
{ |
|
135
|
|
|
if (!$request->headers->has('X-Location-Id') && !$request->headers->has('X-Group-Location-Id')) { |
|
136
|
|
|
return $this->purge($request->getUri()); |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
// Purge everything |
|
140
|
|
|
$locationId = $request->headers->get('X-Location-Id'); |
|
141
|
|
|
if ($locationId === '*' || $locationId === '.*') { |
|
142
|
|
|
return $this->purgeAllContent(); |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
// Usage of X-Group-Location-Id is deprecated. |
|
146
|
|
|
if ($request->headers->has('X-Group-Location-Id')) { |
|
147
|
|
|
$aLocationId = explode('; ', $request->headers->get('X-Group-Location-Id')); |
|
148
|
|
|
} elseif ($locationId[0] === '(' && substr($locationId, -1) === ')') { |
|
149
|
|
|
// Equivalent to X-Group-Location-Id, using a simple Regexp: |
|
150
|
|
|
// (123|456|789) => Purge for #123, #456 and #789 location IDs. |
|
151
|
|
|
$aLocationId = explode('|', substr($locationId, 1, -1)); |
|
152
|
|
|
} else { |
|
153
|
|
|
$aLocationId = [$locationId]; |
|
154
|
|
|
} |
|
155
|
|
|
|
|
156
|
|
|
if (empty($aLocationId)) { |
|
157
|
|
|
return false; |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
foreach ($aLocationId as $locationId) { |
|
161
|
|
|
$this->purgeLocation($locationId); |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
return true; |
|
165
|
|
|
} |
|
166
|
|
|
|
|
167
|
|
|
/** |
|
168
|
|
|
* Purges all cached content. |
|
169
|
|
|
* |
|
170
|
|
|
* @return bool |
|
171
|
|
|
*/ |
|
172
|
|
|
public function purgeAllContent() |
|
173
|
|
|
{ |
|
174
|
|
|
return $this->purgeLocation(null); |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
|
|
/** |
|
178
|
|
|
* Purges cache for $locationId. |
|
179
|
|
|
* |
|
180
|
|
|
* @param int|null $locationId. If null, all locations will be purged. |
|
|
|
|
|
|
181
|
|
|
* |
|
182
|
|
|
* @return bool |
|
183
|
|
|
*/ |
|
184
|
|
|
private function purgeLocation($locationId) |
|
185
|
|
|
{ |
|
186
|
|
|
$fs = $this->getFilesystem(); |
|
187
|
|
|
$locationCacheDir = $this->getLocationCacheDir($locationId); |
|
188
|
|
|
if ($fs->exists($locationCacheDir)) { |
|
189
|
|
|
// 1. Copy cache files to stale cache dir |
|
190
|
|
|
// 2. Place a lock file indicating to use the stale cache |
|
191
|
|
|
// 3. Remove real cache dir |
|
192
|
|
|
// 4. Remove lock file |
|
193
|
|
|
// 5. Remove stale cache dir |
|
194
|
|
|
// Note that there is no need to remove the meta-file |
|
195
|
|
|
$staleCacheDir = str_replace(static::LOCATION_CACHE_DIR, static::LOCATION_STALE_CACHE_DIR, $locationCacheDir); |
|
196
|
|
|
$fs->mkdir($staleCacheDir); |
|
197
|
|
|
$fs->mirror($locationCacheDir, $staleCacheDir); |
|
198
|
|
|
$lockFile = $this->getLocationCacheLockName($locationId); |
|
199
|
|
|
file_put_contents($lockFile, getmypid()); |
|
200
|
|
|
try { |
|
201
|
|
|
// array of removal is in reverse order on purpose since remove() starts from the end. |
|
202
|
|
|
$fs->remove([$staleCacheDir, $lockFile, $locationCacheDir]); |
|
203
|
|
|
|
|
204
|
|
|
return true; |
|
205
|
|
|
} catch (IOException $e) { |
|
206
|
|
|
// Log the error in the standard error log and at least try to remove the lock file |
|
207
|
|
|
error_log($e->getMessage()); |
|
208
|
|
|
@unlink($lockFile); |
|
|
|
|
|
|
209
|
|
|
|
|
210
|
|
|
return false; |
|
211
|
|
|
} |
|
212
|
|
|
} |
|
213
|
|
|
|
|
214
|
|
|
return false; |
|
215
|
|
|
} |
|
216
|
|
|
|
|
217
|
|
|
/** |
|
218
|
|
|
* Returns cache lock name for $locationId. |
|
219
|
|
|
* |
|
220
|
|
|
* This method is public only for unit tests. |
|
221
|
|
|
* Use it only if you know what you are doing. |
|
222
|
|
|
* |
|
223
|
|
|
* @internal |
|
224
|
|
|
* |
|
225
|
|
|
* @param int $locationId. If null, will return a global cache lock name (purging all content) |
|
|
|
|
|
|
226
|
|
|
* |
|
227
|
|
|
* @return string |
|
228
|
|
|
*/ |
|
229
|
|
|
public function getLocationCacheLockName($locationId = null) |
|
230
|
|
|
{ |
|
231
|
|
|
$locationId = $locationId ?: 'all'; |
|
232
|
|
|
|
|
233
|
|
|
return "$this->root/_ezloc_$locationId.purging"; |
|
234
|
|
|
} |
|
235
|
|
|
|
|
236
|
|
|
/** |
|
237
|
|
|
* Returns cache dir for $locationId. |
|
238
|
|
|
* |
|
239
|
|
|
* This method is public only for unit tests. |
|
240
|
|
|
* Use it only if you know what you are doing. |
|
241
|
|
|
* |
|
242
|
|
|
* @internal |
|
243
|
|
|
* |
|
244
|
|
|
* @param int $locationId |
|
245
|
|
|
* |
|
246
|
|
|
* @return string |
|
247
|
|
|
*/ |
|
248
|
|
|
public function getLocationCacheDir($locationId = null) |
|
249
|
|
|
{ |
|
250
|
|
|
$cacheDir = "$this->root/" . static::LOCATION_CACHE_DIR; |
|
251
|
|
|
if ($locationId) { |
|
|
|
|
|
|
252
|
|
|
$cacheDir .= "/$locationId"; |
|
253
|
|
|
} |
|
254
|
|
|
|
|
255
|
|
|
return $cacheDir; |
|
256
|
|
|
} |
|
257
|
|
|
} |
|
258
|
|
|
|
This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.