Passed
Push — develop ( 542723...1a75a9 )
by M. Mikkel
04:36 queued 45s
created

CacheFlagService::invalidateAllFlaggedCaches()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
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\base\Element;
14
use craft\elements\Asset;
15
use craft\elements\Category;
16
use craft\elements\Entry;
17
use craft\elements\GlobalSet;
18
use craft\elements\Tag;
19
use mmikkel\cacheflag\CacheFlag;
20
use mmikkel\cacheflag\events\BeforeDeleteFlaggedTemplateCachesEvent;
21
use mmikkel\cacheflag\events\AfterDeleteFlaggedTemplateCachesEvent;
22
use mmikkel\cacheflag\events\FlaggedTemplateCachesEvent;
23
use mmikkel\cacheflag\records\Flagged;
24
use mmikkel\cacheflag\records\Flags;
25
26
use Craft;
27
use craft\base\Component;
28
use craft\db\Query;
29
use craft\helpers\UrlHelper;
30
use yii\caching\TagDependency;
31
32
33
/**
34
 * @author    Mats Mikkel Rummelhoff
35
 * @package   CacheFlag
36
 * @since     1.0.0
37
 */
38
class CacheFlagService extends Component
39
{
40
41
    // Constants
42
    // =========================================================================
43
    /**
44
     * @event Event The event that is triggered before flagged template caches are deleted.
45
     */
46
    const EVENT_BEFORE_INVALIDATE_FLAGGED_CACHES = 'beforeInvalidateFlaggedCaches';
47
48
    /**
49
     * @event Event The event that is triggered after flagged template caches are invalidated.
50
     */
51
    const EVENT_AFTER_INVALIDATE_FLAGGED_CACHES = 'afterInvalidateFlaggedCaches';
52
53
    /**
54
     * @event Event The event that is triggered before flagged template caches are deleted.
55
     * @deprecated since 1.1.0. Use [[\mmikkel\cacheflag\services\CacheFlagService::EVENT_BEFORE_INVALIDATE_FLAGGED_CACHES]] instead.
56
     */
57
    const EVENT_BEFORE_DELETE_FLAGGED_CACHES = 'beforeDeleteFlaggedCaches';
58
59
    /**
60
     * @event Event The event that is triggered after flagged template caches are deleted.
61
     * @deprecated since 1.1.0. Use [[\mmikkel\cacheflag\services\CacheFlagService::EVENT_AFTER_INVALIDATE_FLAGGED_CACHES]] instead.
62
     */
63
    const EVENT_AFTER_DELETE_FLAGGED_CACHES = 'afterDeleteFlaggedCaches';
64
65
    // Public Methods
66
    // =========================================================================
67
68
    /**
69
     * @return array
70
     */
71
    public function getCpTabs(): array
72
    {
73
        return [
74
            'cacheFlagIndex' => array(
75
                'label' => Craft::t('cache-flag', 'Flags'),
76
                'url' => UrlHelper::url('cache-flag'),
77
            ),
78
            'about' => array(
79
                'label' => Craft::t('cache-flag', 'About'),
80
                'url' => UrlHelper::url('cache-flag/about'),
81
            ),
82
        ];
83
    }
84
85
    /**
86
     * @return array
87
     */
88
    public function getSources(): array
89
    {
90
        $sources = [
91
            'sections' => [
92
                'column' => 'sectionId',
93
                'name' => Craft::t('app', 'Sections'),
94
                'sources' => Craft::$app->getSections()->getAllSections(),
95
            ],
96
            'categoryGroups' => [
97
                'column' => 'categoryGroupId',
98
                'name' => Craft::t('app', 'Category Groups'),
99
                'sources' => Craft::$app->getCategories()->getAllGroups(),
100
            ],
101
            'volumes' => [
102
                'column' => 'volumeId',
103
                'name' => Craft::t('app', 'Asset Volumes'),
104
                'sources' => Craft::$app->getVolumes()->getAllVolumes(),
105
            ],
106
            'globalSets' => [
107
                'column' => 'globalSetId',
108
                'name' => Craft::t('app', 'Global Sets'),
109
                'sources' => Craft::$app->getGlobals()->getAllSets(),
110
            ],
111
            'elementTypes' => [
112
                'column' => 'elementType',
113
                'name' => Craft::t('app', 'Element Types'),
114
                'sources' => \array_map(function (string $elementType) {
115
                    return [
116
                        'id' => $elementType,
117
                        'name' => $elementType,
118
                    ];
119
                }, Craft::$app->getElements()->getAllElementTypes()),
120
            ],
121
        ];
122
123
        if (Craft::$app->getEdition() === 1) {
124
            $sources['userGroups'] = [
125
                'column' => 'userGroupId',
126
                'name' => Craft::t('app', 'User Groups'),
127
                'sources' => Craft::$app->getUserGroups()->getAllGroups(),
128
            ];
129
        }
130
131
        return $sources;
132
    }
133
134
    /**
135
     * @return array
136
     */
137
    public function getAllFlags(): array
138
    {
139
        return (new Query())
140
            ->select('*')
141
            ->from([Flags::tableName()])
142
            ->all();
143
    }
144
145
    /**
146
     * @param string|array $flags
147
     * @param string $sourceColumn
148
     * @param string $sourceId
149
     * @throws \Throwable
150
     * @throws \yii\db\Exception
151
     */
152
    public function saveFlags($flags, string $sourceColumn, string $sourceId)
153
    {
154
155
        if (!$flags) {
156
            return;
157
        }
158
159
        if (\is_array($flags)) {
160
            $flags = \implode(',', $flags);
161
        }
162
163
        $transaction = Craft::$app->getDb()->beginTransaction();
164
165
        try {
166
167
            $flagsId = (int)(new Query())
168
                ->select(['id'])
169
                ->from([Flags::tableName()])
170
                ->where([$sourceColumn => $sourceId])
171
                ->scalar();
172
173
            if ($flagsId) {
174
175
                Craft::$app->getDb()->createCommand()
176
                    ->update(
177
                        Flags::tableName(),
178
                        ['flags' => $flags],
179
                        "id=:id",
180
                        [':id' => $flagsId],
181
                        false
182
                    )
183
                    ->execute();
184
185
            } else {
186
187
                Craft::$app->getDb()->createCommand()
188
                    ->insert(
189
                        Flags::tableName(),
190
                        [
191
                            'flags' => $flags,
192
                            $sourceColumn => $sourceId,
193
                        ],
194
                        false)
195
                    ->execute();
196
            }
197
198
            $transaction->commit();
199
200
        } catch (\Throwable $e) {
201
            $transaction->rollBack();
202
203
            throw $e;
204
        }
205
    }
206
207
    /**
208
     * @param string $sourceColumn
209
     * @param string $sourceValue
210
     * @throws \Throwable
211
     * @throws \yii\db\Exception
212
     */
213
    public function deleteFlagsBySource(string $sourceColumn, string $sourceValue)
214
    {
215
216
        $transaction = Craft::$app->getDb()->beginTransaction();
217
218
        try {
219
220
            Craft::$app->getDb()->createCommand()
221
                ->delete(
222
                    Flags::tableName(),
223
                    [$sourceColumn => $sourceValue]
224
                )
225
                ->execute();
226
227
            $transaction->commit();
228
229
        } catch (\Throwable $e) {
230
            $transaction->rollBack();
231
232
            throw $e;
233
        }
234
    }
235
236
    /**
237
     * @param string|string[] $flags
238
     * @return bool
239
     * @deprecated since 1.1.0
240
     */
241
    public function flagsHasCaches($flags): bool
0 ignored issues
show
Unused Code introduced by
The parameter $flags 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 ignore-unused  annotation

241
    public function flagsHasCaches(/** @scrutinizer ignore-unused */ $flags): bool

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
242
    {
243
        return true;
244
    }
245
246
    /**
247
     * Invalidate all flagged template caches
248
     */
249
    public function invalidateAllFlaggedCaches()
250
    {
251
        TagDependency::invalidate(Craft::$app->getCache(), 'cacheflag');
252
    }
253
254
    /**
255
     * @return bool
256
     * @deprecated since 1.1.0. Use [[\mmikkel\cacheflag\services\CacheFlagService::invalidateAllFlaggedCaches()]] instead.
257
     */
258
    public function deleteAllFlaggedCaches(): bool
259
    {
260
        Craft::$app->getDeprecator()->log('CacheFlagService::deleteAllFlaggedCaches()', 'deleteAllFlaggedCaches() has been deprecated. Use \mmikkel\cacheflag\services\CacheFlagService::invalidateAllFlaggedCaches() instead.');
261
        $this->invalidateAllFlaggedCaches();
262
        return true;
263
    }
264
265
    /**
266
     * @param Element $element
267
     * @return bool
268
     */
269
    public function invalidateFlaggedCachesByElement(Element $element): bool
270
    {
271
        // Collect all flags for this element
272
        $query = (new Query())
273
            ->select(['flags'])
274
            ->from(Flags::tableName());
275
276
        $elementType = \get_class($element);
277
        $dynamicFlags = ["element:$element->id", "element:$element->uid"];
278
279
        switch ($elementType) {
280
            case 'craft\elements\Asset':
281
                /** @var Asset $element */
282
                $query->orWhere([
283
                    'volumeId' => $element->volumeId,
284
                ]);
285
                $dynamicFlags[] = "asset:$element->id";
286
                $dynamicFlags[] = "asset:$element->uid";
287
                break;
288
            case 'craft\elements\Category':
289
                /** @var Category $element */
290
                $query->orWhere([
291
                    'categoryGroupId' => $element->groupId,
292
                ]);
293
                $dynamicFlags[] = "category:$element->id";
294
                $dynamicFlags[] = "category:$element->uid";
295
                break;
296
            case 'craft\elements\Entry':
297
                /** @var Entry $element */
298
                $query->orWhere([
299
                    'sectionId' => $element->sectionId,
300
                ]);
301
                $dynamicFlags[] = "entry:$element->id";
302
                $dynamicFlags[] = "entry:$element->uid";
303
                break;
304
            case 'craft\elements\GlobalSet':
305
                /** @var GlobalSet $element */
306
                $query->orWhere([
307
                    'globalSetId' => $element->id,
308
                ]);
309
                $dynamicFlags[] = "globalSet:$element->id";
310
                $dynamicFlags[] = "globalSet:$element->uid";
311
                break;
312
            case 'craft\elements\Tag':
313
                /** @var Tag $element */
314
                $query->orWhere([
315
                    'tagGroupId' => $element->groupId,
316
                ]);
317
                $dynamicFlags[] = "tag:$element->id";
318
                $dynamicFlags[] = "tag:$element->uid";
319
                break;
320
            case 'craft\elements\User':
321
                /** @var User $element */
322
                foreach ($element->getGroups() as $userGroup) {
323
                    $query->orWhere([
324
                        'userGroupId' => $userGroup->id,
325
                    ]);
326
                }
327
                $dynamicFlags[] = "user:$element->id";
328
                $dynamicFlags[] = "user:$element->uid";
329
                break;
330
        }
331
332
        $query->orWhere([
333
            'elementType' => $elementType,
334
        ]);
335
336
        $flags = \array_unique(\array_merge($query->column(), $dynamicFlags));
337
338
        return $this->invalidateFlaggedCachesByFlags($flags);
339
    }
340
341
    /**
342
     * @param Element $element
343
     * @return bool
344
     * @deprecated since 1.1.0. Use [[\mmikkel\cacheflag\services\CacheFlagService::invalidateFlaggedCachesByElement()]] instead.
345
     */
346
    public function deleteFlaggedCachesByElement(Element $element): bool
347
    {
348
        Craft::$app->getDeprecator()->log('CacheFlagService::deleteFlaggedCachesByElement()', 'deleteFlaggedCachesByElement() has been deprecated. Use \mmikkel\cacheflag\services\CacheFlagService::invalidateFlaggedCachesByElement() instead.');
349
        return $this->invalidateFlaggedCachesByElement($element);
350
    }
351
352
    /**
353
     * @param string|string[] $flags
354
     * @return bool
355
     */
356
    public function invalidateFlaggedCachesByFlags($flags): bool
357
    {
358
359
        if (\is_array($flags)) {
360
            $flags = $this->implodeFlagsArray($flags);
361
        } else if (\is_string($flags)) {
0 ignored issues
show
introduced by
The condition is_string($flags) is always true.
Loading history...
362
            $flags = \preg_replace('/\s+/', '', $flags);
363
        } else {
364
            return false;
365
        }
366
367
        $flags = \array_values(\array_unique(\explode(',', $flags)));
368
369
        if (empty($flags)) {
370
            return false;
371
        }
372
373
        // Fire a 'beforeDeleteFlaggedCaches' event
374
        if ($this->hasEventHandlers(self::EVENT_BEFORE_DELETE_FLAGGED_CACHES)) {
0 ignored issues
show
Deprecated Code introduced by
The constant mmikkel\cacheflag\servic...E_DELETE_FLAGGED_CACHES has been deprecated: since 1.1.0. Use [[\mmikkel\cacheflag\services\CacheFlagService::EVENT_BEFORE_INVALIDATE_FLAGGED_CACHES]] instead. ( Ignorable by Annotation )

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

374
        if ($this->hasEventHandlers(/** @scrutinizer ignore-deprecated */ self::EVENT_BEFORE_DELETE_FLAGGED_CACHES)) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
375
            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.');
376
            $this->trigger(self::EVENT_BEFORE_DELETE_FLAGGED_CACHES, new BeforeDeleteFlaggedTemplateCachesEvent([
0 ignored issues
show
Deprecated Code introduced by
The constant mmikkel\cacheflag\servic...E_DELETE_FLAGGED_CACHES has been deprecated: since 1.1.0. Use [[\mmikkel\cacheflag\services\CacheFlagService::EVENT_BEFORE_INVALIDATE_FLAGGED_CACHES]] instead. ( Ignorable by Annotation )

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

376
            $this->trigger(/** @scrutinizer ignore-deprecated */ self::EVENT_BEFORE_DELETE_FLAGGED_CACHES, new BeforeDeleteFlaggedTemplateCachesEvent([

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
Deprecated Code introduced by
The class mmikkel\cacheflag\events...ggedTemplateCachesEvent has been deprecated: since 1.1.0 ( Ignorable by Annotation )

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

376
            $this->trigger(self::EVENT_BEFORE_DELETE_FLAGGED_CACHES, /** @scrutinizer ignore-deprecated */ new BeforeDeleteFlaggedTemplateCachesEvent([
Loading history...
377
                'cacheIds' => [],
378
                'flags' => $flags,
379
            ]));
380
        }
381
382
        // Fire a `beforeInvalidateFlaggedCaches` event
383
        if ($this->hasEventHandlers(self::EVENT_BEFORE_INVALIDATE_FLAGGED_CACHES)) {
384
            $this->trigger(self::EVENT_BEFORE_INVALIDATE_FLAGGED_CACHES, new FlaggedTemplateCachesEvent([
385
                'flags' => $flags,
386
            ]));
387
        }
388
389
        $flagTags = \array_map(function (string $flag) {
390
            return "cacheflag::$flag";
391
        }, $flags);
392
393
        TagDependency::invalidate(Craft::$app->getCache(), $flagTags);
394
395
        // Fire a 'afterDeleteFlaggedCaches' event
396
        if ($this->hasEventHandlers(self::EVENT_AFTER_DELETE_FLAGGED_CACHES)) {
0 ignored issues
show
Deprecated Code introduced by
The constant mmikkel\cacheflag\servic...R_DELETE_FLAGGED_CACHES has been deprecated: since 1.1.0. Use [[\mmikkel\cacheflag\services\CacheFlagService::EVENT_AFTER_INVALIDATE_FLAGGED_CACHES]] instead. ( Ignorable by Annotation )

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

396
        if ($this->hasEventHandlers(/** @scrutinizer ignore-deprecated */ self::EVENT_AFTER_DELETE_FLAGGED_CACHES)) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
397
            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.');
398
            $this->trigger(self::EVENT_AFTER_DELETE_FLAGGED_CACHES, new AfterDeleteFlaggedTemplateCachesEvent([
0 ignored issues
show
Deprecated Code introduced by
The class mmikkel\cacheflag\events...ggedTemplateCachesEvent has been deprecated: since 1.1.0 ( Ignorable by Annotation )

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

398
            $this->trigger(self::EVENT_AFTER_DELETE_FLAGGED_CACHES, /** @scrutinizer ignore-deprecated */ new AfterDeleteFlaggedTemplateCachesEvent([
Loading history...
Deprecated Code introduced by
The constant mmikkel\cacheflag\servic...R_DELETE_FLAGGED_CACHES has been deprecated: since 1.1.0. Use [[\mmikkel\cacheflag\services\CacheFlagService::EVENT_AFTER_INVALIDATE_FLAGGED_CACHES]] instead. ( Ignorable by Annotation )

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

398
            $this->trigger(/** @scrutinizer ignore-deprecated */ self::EVENT_AFTER_DELETE_FLAGGED_CACHES, new AfterDeleteFlaggedTemplateCachesEvent([

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
399
                'cacheIds' => [],
400
                'flags' => $flags,
401
            ]));
402
        }
403
404
        // Fire a 'afterInvalidateFlaggedCaches' event
405
        if ($this->hasEventHandlers(self::EVENT_AFTER_INVALIDATE_FLAGGED_CACHES)) {
406
            $this->trigger(self::EVENT_AFTER_INVALIDATE_FLAGGED_CACHES, new FlaggedTemplateCachesEvent([
407
                'flags' => $flags,
408
            ]));
409
        }
410
411
        return true;
412
    }
413
414
    /**
415
     * @param string|string[] $flags
416
     * @return bool
417
     * @deprecated since 1.1.0. Use [[\mmikkel\cacheflag\services\CacheFlagService::invalidateFlaggedCachesByFlags()]] instead.
418
     */
419
    public function deleteFlaggedCachesByFlags($flags): bool
420
    {
421
        Craft::$app->getDeprecator()->log('CacheFlagService::deleteFlaggedCachesByFlags()', 'deleteFlaggedCachesByFlags() has been deprecated. Use \mmikkel\cacheflag\services\CacheFlagService::invalidateFlaggedCachesByFlags() instead.');
422
        return $this->invalidateFlaggedCachesByFlags($flags);
423
    }
424
425
    /*
426
     * Protected methods
427
     */
428
    /**
429
     * @param array $flagsArray
430
     * @return string
431
     */
432
    protected function implodeFlagsArray(array $flagsArray): string
433
    {
434
435
        $flags = '';
436
437
        foreach ($flagsArray as $item) {
438
            if (\is_array($item)) {
439
                $flags .= "{$this->implodeFlagsArray($item)},";
440
            } else {
441
                $flags .= \preg_replace('/\s+/', '', $item) . ',';
442
            }
443
        }
444
445
        $flags = \substr($flags, 0, 0 - strlen(','));
446
447
        return $flags;
448
    }
449
}
450