1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Created by PhpStorm. |
4
|
|
|
* User: mmikkel |
5
|
|
|
* Date: 14/07/2018 |
6
|
|
|
* Time: 11:55 |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace mmikkel\cacheflag\services; |
10
|
|
|
|
11
|
|
|
use craft\db\Table; |
12
|
|
|
use mmikkel\cacheflag\CacheFlag; |
13
|
|
|
use mmikkel\cacheflag\records\Flagged; |
14
|
|
|
|
15
|
|
|
use Craft; |
16
|
|
|
use craft\base\Element; |
17
|
|
|
use craft\base\ElementInterface; |
18
|
|
|
use craft\db\Query; |
19
|
|
|
use craft\elements\db\ElementQuery; |
20
|
|
|
use craft\events\DeleteTemplateCachesEvent; |
21
|
|
|
use craft\helpers\DateTimeHelper; |
22
|
|
|
use craft\helpers\Db; |
23
|
|
|
use craft\helpers\StringHelper; |
24
|
|
|
use craft\services\TemplateCaches; |
25
|
|
|
use craft\queue\jobs\DeleteStaleTemplateCaches; |
26
|
|
|
use DateTime; |
27
|
|
|
|
28
|
|
|
use yii\base\Component; |
29
|
|
|
use yii\base\Event; |
30
|
|
|
use yii\web\Response; |
31
|
|
|
|
32
|
|
|
class TemplateCachesService extends Component |
33
|
|
|
{ |
34
|
|
|
|
35
|
|
|
// Properties |
36
|
|
|
// ========================================================================= |
37
|
|
|
/** |
38
|
|
|
* The table that template caches are stored in. |
39
|
|
|
* |
40
|
|
|
* @var string |
41
|
|
|
*/ |
42
|
|
|
private static $_templateCachesTable = '{{%templatecaches}}'; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* The current request's path, as it will be stored in the templatecaches table. |
46
|
|
|
* |
47
|
|
|
* @var string|null |
48
|
|
|
*/ |
49
|
|
|
private $_path; |
50
|
|
|
|
51
|
|
|
// Public Methods |
52
|
|
|
// ========================================================================= |
53
|
|
|
/** |
54
|
|
|
* Returns a cached template by its key. |
55
|
|
|
* |
56
|
|
|
* @param string $key The template cache key |
57
|
|
|
* @param mixed|null $flags The Cache Flag flags this cache would've been flagged with |
58
|
|
|
* @param bool $global Whether the cache would have been stored globally. |
59
|
|
|
* @return string|null |
60
|
|
|
*/ |
61
|
|
|
public function getTemplateCache(string $key, $flags = null, bool $global) |
62
|
|
|
{ |
63
|
|
|
// Make sure template caching is enabled |
64
|
|
|
if ($this->_isTemplateCachingEnabled() === false) { |
65
|
|
|
return null; |
66
|
|
|
} |
67
|
|
|
// Don't return anything if it's not a global request and the path > 255 characters. |
68
|
|
|
if (!$global && strlen($this->_getPath()) > 255) { |
69
|
|
|
return null; |
70
|
|
|
} |
71
|
|
|
// Take the opportunity to delete any expired caches |
72
|
|
|
Craft::$app->getTemplateCaches()->deleteExpiredCachesIfOverdue(); |
|
|
|
|
73
|
|
|
/** @noinspection PhpUnhandledExceptionInspection */ |
74
|
|
|
$query = (new Query()) |
75
|
|
|
->select(['body']) |
76
|
|
|
->from([self::$_templateCachesTable . ' templatecaches']) |
77
|
|
|
->where([ |
78
|
|
|
'and', |
79
|
|
|
[ |
80
|
|
|
'cacheKey' => $key, |
81
|
|
|
'siteId' => Craft::$app->getSites()->getCurrentSite()->id |
82
|
|
|
], |
83
|
|
|
['>', 'expiryDate', Db::prepareDateForDb(new \DateTime())], |
84
|
|
|
]); |
85
|
|
|
if (!$global) { |
86
|
|
|
$query->andWhere([ |
87
|
|
|
'path' => $this->_getPath() |
88
|
|
|
]); |
89
|
|
|
} |
90
|
|
|
if ($flags) { |
91
|
|
|
|
92
|
|
|
// Sanitize flags |
93
|
|
|
if (\is_array($flags)) { |
94
|
|
|
$flags = \implode(',', \array_map(function ($flag) { |
95
|
|
|
return \preg_replace('/\s+/', '', $flag); |
96
|
|
|
}, $flags)); |
97
|
|
|
} else { |
98
|
|
|
$flags = \preg_replace('/\s+/', '', $flags); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
$flags = \implode(',', \explode('|', $flags)); |
102
|
|
|
|
103
|
|
|
$query |
104
|
|
|
->innerJoin( |
105
|
|
|
Flagged::tableName() . ' flagged', |
106
|
|
|
'[[flagged.cacheId]] = [[templatecaches.id]]' |
107
|
|
|
) |
108
|
|
|
->andWhere(['flagged.flags' => $flags]); |
109
|
|
|
} |
110
|
|
|
$cachedBody = $query->scalar(); |
111
|
|
|
if ($cachedBody === false) { |
112
|
|
|
return null; |
113
|
|
|
} |
114
|
|
|
return $cachedBody; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* Starts a new template cache. |
119
|
|
|
*/ |
120
|
|
|
public function startTemplateCache() |
121
|
|
|
{ |
122
|
|
|
return; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Ends a template cache. |
127
|
|
|
* |
128
|
|
|
* @param string $key The template cache key. |
129
|
|
|
* @param mixed|null $flags The Cache Flag flags this cache should be flagged with. |
130
|
|
|
* @param bool $global Whether the cache should be stored globally. |
131
|
|
|
* @param string|null $duration How long the cache should be stored for. Should be a [relative time format](http://php.net/manual/en/datetime.formats.relative.php). |
132
|
|
|
* @param mixed|null $expiration When the cache should expire. |
133
|
|
|
* @param string $body The contents of the cache. |
134
|
|
|
* @throws \Throwable |
135
|
|
|
*/ |
136
|
|
|
public function endTemplateCache(string $key, $flags = null, bool $global, string $duration = null, $expiration, string $body) |
137
|
|
|
{ |
138
|
|
|
// Make sure template caching is enabled |
139
|
|
|
if ($this->_isTemplateCachingEnabled() === false) { |
140
|
|
|
return; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
// If there are any transform generation URLs in the body, don't cache it. |
144
|
|
|
// stripslashes($body) in case the URL has been JS-encoded or something. |
145
|
|
|
if (StringHelper::contains(stripslashes($body), 'assets/generate-transform')) { |
146
|
|
|
return; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
if (!$global && (strlen($path = $this->_getPath()) > 255)) { |
150
|
|
|
Craft::warning('Skipped adding ' . $key . ' to template cache table because the path is > 255 characters: ' . $path, __METHOD__); |
151
|
|
|
|
152
|
|
|
return; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
if (Craft::$app->getDb()->getIsMysql()) { |
156
|
|
|
// Encode any 4-byte UTF-8 characters |
157
|
|
|
$body = StringHelper::encodeMb4($body); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
// Figure out the expiration date |
161
|
|
|
if ($duration !== null) { |
162
|
|
|
$expiration = new DateTime($duration); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
if (!$expiration) { |
166
|
|
|
$cacheDuration = Craft::$app->getConfig()->getGeneral()->cacheDuration; |
167
|
|
|
|
168
|
|
|
if ($cacheDuration <= 0) { |
169
|
|
|
$cacheDuration = 31536000; // 1 year |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
$cacheDuration += time(); |
173
|
|
|
|
174
|
|
|
$expiration = new DateTime('@' . $cacheDuration); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
// Save it |
178
|
|
|
$transaction = Craft::$app->getDb()->beginTransaction(); |
179
|
|
|
|
180
|
|
|
try { |
181
|
|
|
Craft::$app->getDb()->createCommand() |
182
|
|
|
->insert( |
183
|
|
|
self::$_templateCachesTable, |
184
|
|
|
[ |
185
|
|
|
'cacheKey' => $key, |
186
|
|
|
'siteId' => Craft::$app->getSites()->getCurrentSite()->id, |
187
|
|
|
'path' => $global ? null : $this->_getPath(), |
188
|
|
|
'expiryDate' => Db::prepareDateForDb($expiration), |
189
|
|
|
'body' => $body |
190
|
|
|
], |
191
|
|
|
false) |
192
|
|
|
->execute(); |
193
|
|
|
|
194
|
|
|
if ($flags) { |
195
|
|
|
|
196
|
|
|
$cacheId = Craft::$app->getDb()->getLastInsertID(self::$_templateCachesTable); |
197
|
|
|
|
198
|
|
|
// Sanitize flags |
199
|
|
|
if (\is_array($flags)) { |
200
|
|
|
$flags = \implode(',', \array_map(function ($flag) { |
201
|
|
|
return \preg_replace('/\s+/', '', $flag); |
202
|
|
|
}, $flags)); |
203
|
|
|
} else { |
204
|
|
|
$flags = \preg_replace('/\s+/', '', $flags); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
$flags = \implode(',', \explode('|', $flags)); |
208
|
|
|
|
209
|
|
|
Craft::$app->getDb()->createCommand() |
210
|
|
|
->insert( |
211
|
|
|
Flagged::tableName(), |
212
|
|
|
[ |
213
|
|
|
'cacheId' => $cacheId, |
214
|
|
|
'flags' => $flags |
215
|
|
|
], |
216
|
|
|
false |
217
|
|
|
) |
218
|
|
|
->execute(); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
$transaction->commit(); |
222
|
|
|
} catch (\Throwable $e) { |
223
|
|
|
$transaction->rollBack(); |
224
|
|
|
|
225
|
|
|
throw $e; |
226
|
|
|
} |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Deletes a cache by its ID(s). |
231
|
|
|
* |
232
|
|
|
* @param int|int[] $cacheId The cache ID(s) |
233
|
|
|
* @return bool |
234
|
|
|
*/ |
235
|
|
|
public function deleteCacheById($cacheId): bool |
236
|
|
|
{ |
237
|
|
|
if (is_array($cacheId) && empty($cacheId)) { |
238
|
|
|
return false; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
// Fire a 'beforeDeleteCaches' event |
242
|
|
|
if (Craft::$app->getTemplateCaches()->hasEventHandlers(TemplateCaches::EVENT_BEFORE_DELETE_CACHES)) { |
243
|
|
|
Craft::$app->getTemplateCaches()->trigger(TemplateCaches::EVENT_BEFORE_DELETE_CACHES, new DeleteTemplateCachesEvent([ |
244
|
|
|
'cacheIds' => (array)$cacheId |
245
|
|
|
])); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
$affectedRows = Craft::$app->getDb()->createCommand() |
249
|
|
|
->delete(Table::TEMPLATECACHES, ['id' => $cacheId]) |
250
|
|
|
->execute(); |
251
|
|
|
|
252
|
|
|
// Fire an 'afterDeleteCaches' event |
253
|
|
|
if ($affectedRows && Craft::$app->getTemplateCaches()->hasEventHandlers(TemplateCaches::EVENT_AFTER_DELETE_CACHES)) { |
254
|
|
|
Craft::$app->getTemplateCaches()->trigger(TemplateCaches::EVENT_AFTER_DELETE_CACHES, new DeleteTemplateCachesEvent([ |
255
|
|
|
'cacheIds' => (array)$cacheId |
256
|
|
|
])); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
return (bool)$affectedRows; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
// Private Methods |
263
|
|
|
// ========================================================================= |
264
|
|
|
/** |
265
|
|
|
* Returns whether template caching is enabled, based on the 'enableTemplateCaching' config setting. |
266
|
|
|
* |
267
|
|
|
* @return bool Whether template caching is enabled |
268
|
|
|
*/ |
269
|
|
|
private function _isTemplateCachingEnabled(): bool |
270
|
|
|
{ |
271
|
|
|
return !!Craft::$app->getConfig()->getGeneral()->enableTemplateCaching; |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* Returns the current request path, including a "site:" or "cp:" prefix. |
276
|
|
|
* |
277
|
|
|
* @return string |
278
|
|
|
*/ |
279
|
|
|
private function _getPath(): string |
280
|
|
|
{ |
281
|
|
|
if ($this->_path !== null) { |
282
|
|
|
return $this->_path; |
283
|
|
|
} |
284
|
|
|
if (Craft::$app->getRequest()->getIsCpRequest()) { |
285
|
|
|
$this->_path = 'cp:'; |
286
|
|
|
} else { |
287
|
|
|
$this->_path = 'site:'; |
288
|
|
|
} |
289
|
|
|
$this->_path .= Craft::$app->getRequest()->getPathInfo(); |
290
|
|
|
if (($pageNum = Craft::$app->getRequest()->getPageNum()) != 1) { |
291
|
|
|
$this->_path .= '/' . Craft::$app->getConfig()->getGeneral()->pageTrigger . $pageNum; |
292
|
|
|
} |
293
|
|
|
return $this->_path; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
} |
297
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.