Passed
Push — master ( b1065f...542723 )
by M. Mikkel
06:27
created

TemplateCachesService::deleteCacheById()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 12
c 1
b 0
f 0
nc 5
nop 1
dl 0
loc 25
rs 9.2222
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();
0 ignored issues
show
Deprecated Code introduced by
The function craft\services\TemplateC...xpiredCachesIfOverdue() has been deprecated: in 3.2.0. Use [[deleteExpiredCaches()]] instead. ( Ignorable by Annotation )

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

72
        /** @scrutinizer ignore-deprecated */ Craft::$app->getTemplateCaches()->deleteExpiredCachesIfOverdue();

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.

Loading history...
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