Passed
Push — master ( f96468...e8dc9f )
by M. Mikkel
03:43
created

CacheFlagService::deleteFlaggedCachesByFlags()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 2
b 0
f 0
nc 1
nop 1
dl 0
loc 4
rs 10
1
<?php
2
/**
3
 * Cache Flag plugin for Craft CMS 3.x
4
 *
5
 * Flag and clear template caches.
6
 *
7
 * @link      https://vaersaagod.no
8
 * @copyright Copyright (c) 2018 Mats Mikkel Rummelhoff
9
 */
10
11
namespace mmikkel\cacheflag\services;
12
13
use Craft;
14
use craft\base\Component;
15
use craft\base\Element;
16
use craft\db\Query;
17
use craft\db\Table;
18
use craft\elements\Asset;
19
use craft\elements\Category;
20
use craft\elements\Entry;
21
use craft\elements\GlobalSet;
22
use craft\elements\Tag;
23
use craft\helpers\Db;
24
use craft\helpers\StringHelper;
25
use craft\helpers\UrlHelper;
26
27
use yii\caching\TagDependency;
28
29
use mmikkel\cacheflag\CacheFlag;
30
use mmikkel\cacheflag\events\BeforeDeleteFlaggedTemplateCachesEvent;
31
use mmikkel\cacheflag\events\AfterDeleteFlaggedTemplateCachesEvent;
32
use mmikkel\cacheflag\events\FlaggedTemplateCachesEvent;
33
use mmikkel\cacheflag\records\Flagged;
34
use mmikkel\cacheflag\records\Flags;
35
36
37
/**
38
 * @author    Mats Mikkel Rummelhoff
39
 * @package   CacheFlag
40
 * @since     1.0.0
41
 */
42
class CacheFlagService extends Component
43
{
44
45
    // Constants
46
    // =========================================================================
47
    /**
48
     * @event Event The event that is triggered before flagged template caches are deleted.
49
     */
50
    const EVENT_BEFORE_INVALIDATE_FLAGGED_CACHES = 'beforeInvalidateFlaggedCaches';
51
52
    /**
53
     * @event Event The event that is triggered after flagged template caches are invalidated.
54
     */
55
    const EVENT_AFTER_INVALIDATE_FLAGGED_CACHES = 'afterInvalidateFlaggedCaches';
56
57
    /**
58
     * @event Event The event that is triggered before flagged template caches are deleted.
59
     * @deprecated since 1.1.0. Use [[\mmikkel\cacheflag\services\CacheFlagService::EVENT_BEFORE_INVALIDATE_FLAGGED_CACHES]] instead.
60
     */
61
    const EVENT_BEFORE_DELETE_FLAGGED_CACHES = 'beforeDeleteFlaggedCaches';
62
63
    /**
64
     * @event Event The event that is triggered after flagged template caches are deleted.
65
     * @deprecated since 1.1.0. Use [[\mmikkel\cacheflag\services\CacheFlagService::EVENT_AFTER_INVALIDATE_FLAGGED_CACHES]] instead.
66
     */
67
    const EVENT_AFTER_DELETE_FLAGGED_CACHES = 'afterDeleteFlaggedCaches';
68
69
    // Public Methods
70
    // =========================================================================
71
72
    /**
73
     * @return array
74
     */
75
    public function getAllFlags(): array
76
    {
77
        return (new Query())
78
            ->select('*')
79
            ->from([Flags::tableName()])
80
            ->all();
81
    }
82
83
    /**
84
     * @param string|string[] $flags
85
     * @param string $sourceColumn
86
     * @param string $sourceValue
87
     * @throws \Throwable
88
     * @throws \yii\db\Exception
89
     */
90
    public function saveFlags($flags, string $sourceColumn, string $sourceValue)
91
    {
92
93
        if (!$flags) {
94
            return;
95
        }
96
97
        if (\is_array($flags)) {
98
            $flags = \implode(',', $flags);
99
        }
100
101
        $uid = (new Query())
102
            ->select(['uid'])
103
            ->from(Flags::tableName())
104
            ->where([$sourceColumn => $sourceValue])
105
            ->scalar();
106
107
        $isNew = !$uid;
108
        if ($isNew) {
109
            $uid = StringHelper::UUID();
110
        }
111
112
        $sourceKey = null;
113
114
        switch ($sourceColumn) {
115
            case 'sectionId':
116
                $sourceKey = 'section';
117
                $sourceValue = Db::uidById(Table::SECTIONS, (int)$sourceValue);
118
                break;
119
            case 'categoryGroupId':
120
                $sourceKey = 'categoryGroup';
121
                $sourceValue = Db::uidById(Table::CATEGORYGROUPS, (int)$sourceValue);
122
                break;
123
            case 'tagGroupId':
124
                $sourceKey = 'tagGroup';
125
                $sourceValue = Db::uidById(Table::TAGGROUPS, (int)$sourceValue);
126
                break;
127
            case 'userGroupId':
128
                $sourceKey = 'userGroup';
129
                $sourceValue = Db::uidById(Table::USERGROUPS, (int)$sourceValue);
130
                break;
131
            case 'volumeId':
132
                $sourceKey = 'volume';
133
                $sourceValue = Db::uidById(Table::VOLUMES, (int)$sourceValue);
134
                break;
135
            case 'globalSetId':
136
                $sourceKey = 'globalSet';
137
                $sourceValue = Db::uidById(Table::GLOBALSETS, (int)$sourceValue);
138
                break;
139
            case 'elementType':
140
                $sourceKey = 'elementType';
141
                break;
142
            default:
143
                return;
144
        }
145
146
        if (!$sourceValue) {
147
            return;
148
        }
149
150
        // Save it to the project config
151
        $path = "cacheFlags.{$uid}";
152
        Craft::$app->projectConfig->set($path, [
153
            'source' => "$sourceKey:$sourceValue",
154
            'flags' => $flags,
155
        ]);
156
    }
157
158
    /**
159
     * @param string $sourceColumn
160
     * @param string $sourceValue
161
     * @throws \Throwable
162
     * @throws \yii\db\Exception
163
     */
164
    public function deleteFlagsBySource(string $sourceColumn, string $sourceValue)
165
    {
166
167
        $uid = (new Query())
168
            ->select(['uid'])
169
            ->from(Flags::tableName())
170
            ->where([$sourceColumn => $sourceValue])
171
            ->scalar();
172
173
        if (!$uid) {
174
            return false;
175
        }
176
177
        // Remove it from the project config
178
        $path = "cacheFlags.{$uid}";
179
        Craft::$app->projectConfig->remove($path);
180
    }
181
182
    /**
183
     * @return bool
184
     * @deprecated since 1.1.0
185
     */
186
    public function flagsHasCaches(): bool
187
    {
188
        return true;
189
    }
190
191
    /**
192
     * Invalidate all flagged template caches
193
     */
194
    public function invalidateAllFlaggedCaches()
195
    {
196
        TagDependency::invalidate(Craft::$app->getCache(), 'cacheflag');
197
    }
198
199
    /**
200
     * @return bool
201
     * @deprecated since 1.1.0. Use [[\mmikkel\cacheflag\services\CacheFlagService::invalidateAllFlaggedCaches()]] instead.
202
     */
203
    public function deleteAllFlaggedCaches(): bool
204
    {
205
        Craft::$app->getDeprecator()->log('CacheFlagService::deleteAllFlaggedCaches()', 'deleteAllFlaggedCaches() has been deprecated. Use \mmikkel\cacheflag\services\CacheFlagService::invalidateAllFlaggedCaches() instead.');
206
        $this->invalidateAllFlaggedCaches();
207
        return true;
208
    }
209
210
    /**
211
     * @param Element $element
212
     * @return bool
213
     */
214
    public function invalidateFlaggedCachesByElement(Element $element): bool
215
    {
216
        // Collect all flags for this element
217
        $query = (new Query())
218
            ->select(['flags'])
219
            ->from(Flags::tableName());
220
221
        $elementType = \get_class($element);
222
        $dynamicFlags = ["element:$element->id", "element:$element->uid"];
223
224
        switch ($elementType) {
225
            case 'craft\elements\Asset':
226
                /** @var Asset $element */
227
                $query->orWhere([
228
                    'volumeId' => $element->volumeId,
229
                ]);
230
                $dynamicFlags[] = "asset:$element->id";
231
                $dynamicFlags[] = "asset:$element->uid";
232
                break;
233
            case 'craft\elements\Category':
234
                /** @var Category $element */
235
                $query->orWhere([
236
                    'categoryGroupId' => $element->groupId,
237
                ]);
238
                $dynamicFlags[] = "category:$element->id";
239
                $dynamicFlags[] = "category:$element->uid";
240
                break;
241
            case 'craft\elements\Entry':
242
                /** @var Entry $element */
243
                $query->orWhere([
244
                    'sectionId' => $element->sectionId,
245
                ]);
246
                $dynamicFlags[] = "entry:$element->id";
247
                $dynamicFlags[] = "entry:$element->uid";
248
                break;
249
            case 'craft\elements\GlobalSet':
250
                /** @var GlobalSet $element */
251
                $query->orWhere([
252
                    'globalSetId' => $element->id,
253
                ]);
254
                $dynamicFlags[] = "globalSet:$element->id";
255
                $dynamicFlags[] = "globalSet:$element->uid";
256
                break;
257
            case 'craft\elements\Tag':
258
                /** @var Tag $element */
259
                $query->orWhere([
260
                    'tagGroupId' => $element->groupId,
261
                ]);
262
                $dynamicFlags[] = "tag:$element->id";
263
                $dynamicFlags[] = "tag:$element->uid";
264
                break;
265
            case 'craft\elements\User':
266
                /** @var User $element */
267
                foreach ($element->getGroups() as $userGroup) {
268
                    $query->orWhere([
269
                        'userGroupId' => $userGroup->id,
270
                    ]);
271
                }
272
                $dynamicFlags[] = "user:$element->id";
273
                $dynamicFlags[] = "user:$element->uid";
274
                break;
275
        }
276
277
        $query->orWhere([
278
            'elementType' => $elementType,
279
        ]);
280
281
        $flags = \array_unique(\array_merge($query->column(), $dynamicFlags));
282
283
        return $this->invalidateFlaggedCachesByFlags($flags);
284
    }
285
286
    /**
287
     * @param Element $element
288
     * @return bool
289
     * @deprecated since 1.1.0. Use [[\mmikkel\cacheflag\services\CacheFlagService::invalidateFlaggedCachesByElement()]] instead.
290
     */
291
    public function deleteFlaggedCachesByElement(Element $element): bool
292
    {
293
        Craft::$app->getDeprecator()->log('CacheFlagService::deleteFlaggedCachesByElement()', 'deleteFlaggedCachesByElement() has been deprecated. Use \mmikkel\cacheflag\services\CacheFlagService::invalidateFlaggedCachesByElement() instead.');
294
        return $this->invalidateFlaggedCachesByElement($element);
295
    }
296
297
    /**
298
     * @param string|string[]|null $flags
299
     * @return bool
300
     */
301
    public function invalidateFlaggedCachesByFlags($flags): bool
302
    {
303
304
        if (!$flags) {
305
            return false;
306
        }
307
308
        if (\is_array($flags)) {
309
            $flags = $this->implodeFlagsArray($flags);
310
        } else {
311
            $flags = \preg_replace('/\s+/', '', $flags);
312
        }
313
314
        $flags = \array_values(\array_unique(\explode(',', $flags)));
315
316
        if (empty($flags)) {
317
            return false;
318
        }
319
320
        // Fire a 'beforeDeleteFlaggedCaches' event
321
        if ($this->hasEventHandlers(/** @scrutinizer ignore-deprecated */ self::EVENT_BEFORE_DELETE_FLAGGED_CACHES)) {
322
            Craft::$app->getDeprecator()->log('CacheFlagService::EVENT_BEFORE_DELETE_FLAGGED_CACHES', 'The `EVENT_BEFORE_DELETE_FLAGGED_CACHES` event has been deprecated. Use \mmikkel\cacheflag\services\CacheFlagService::EVENT_BEFORE_INVALIDATE_FLAGGED_CACHES instead.');
323
            $this->trigger(/** @scrutinizer ignore-deprecated */ self::EVENT_BEFORE_DELETE_FLAGGED_CACHES, /** @scrutinizer ignore-deprecated */ new BeforeDeleteFlaggedTemplateCachesEvent([
324
                'cacheIds' => [],
325
                'flags' => $flags,
326
            ]));
327
        }
328
329
        // Fire a `beforeInvalidateFlaggedCaches` event
330
        if ($this->hasEventHandlers(self::EVENT_BEFORE_INVALIDATE_FLAGGED_CACHES)) {
331
            $this->trigger(self::EVENT_BEFORE_INVALIDATE_FLAGGED_CACHES, new FlaggedTemplateCachesEvent([
332
                'flags' => $flags,
333
            ]));
334
        }
335
336
        $flagTags = \array_map(function (string $flag) {
337
            return "cacheflag::$flag";
338
        }, $flags);
339
340
        TagDependency::invalidate(Craft::$app->getCache(), $flagTags);
341
342
        // Fire a 'afterDeleteFlaggedCaches' event
343
        if ($this->hasEventHandlers(/** @scrutinizer ignore-deprecated */ self::EVENT_AFTER_DELETE_FLAGGED_CACHES)) {
344
            Craft::$app->getDeprecator()->log('CacheFlagService::EVENT_AFTER_DELETE_FLAGGED_CACHES', 'The `EVENT_AFTER_DELETE_FLAGGED_CACHES` event has been deprecated. Use \mmikkel\cacheflag\services\CacheFlagService::EVENT_AFTER_INVALIDATE_FLAGGED_CACHES instead.');
345
            $this->trigger(/** @scrutinizer ignore-deprecated */ self::EVENT_AFTER_DELETE_FLAGGED_CACHES, /** @scrutinizer ignore-deprecated */ new AfterDeleteFlaggedTemplateCachesEvent([
346
                'cacheIds' => [],
347
                'flags' => $flags,
348
            ]));
349
        }
350
351
        // Fire a 'afterInvalidateFlaggedCaches' event
352
        if ($this->hasEventHandlers(self::EVENT_AFTER_INVALIDATE_FLAGGED_CACHES)) {
353
            $this->trigger(self::EVENT_AFTER_INVALIDATE_FLAGGED_CACHES, new FlaggedTemplateCachesEvent([
354
                'flags' => $flags,
355
            ]));
356
        }
357
358
        return true;
359
    }
360
361
    /**
362
     * @param string|string[] $flags
363
     * @return bool
364
     * @deprecated since 1.1.0. Use [[\mmikkel\cacheflag\services\CacheFlagService::invalidateFlaggedCachesByFlags()]] instead.
365
     */
366
    public function deleteFlaggedCachesByFlags($flags): bool
367
    {
368
        Craft::$app->getDeprecator()->log('CacheFlagService::deleteFlaggedCachesByFlags()', 'deleteFlaggedCachesByFlags() has been deprecated. Use \mmikkel\cacheflag\services\CacheFlagService::invalidateFlaggedCachesByFlags() instead.');
369
        return $this->invalidateFlaggedCachesByFlags($flags);
370
    }
371
372
    /*
373
     * Protected methods
374
     */
375
    /**
376
     * @param array $flagsArray
377
     * @return string
378
     */
379
    protected function implodeFlagsArray(array $flagsArray): string
380
    {
381
382
        $flags = '';
383
384
        foreach ($flagsArray as $item) {
385
            if (\is_array($item)) {
386
                $flags .= "{$this->implodeFlagsArray($item)},";
387
            } else {
388
                $flags .= \preg_replace('/\s+/', '', $item) . ',';
389
            }
390
        }
391
392
        $flags = \substr($flags, 0, 0 - strlen(','));
393
394
        return $flags;
395
    }
396
}
397