Completed
Push — EZP-30427 ( f2742c...7c8d0e )
by
unknown
19:22
created

ContentHandler::loadDraftListForUser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
cc 1
nc 1
nop 3
rs 10
1
<?php
2
3
/**
4
 * File containing the ContentHandler implementation.
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\Persistence\Cache;
10
11
use eZ\Publish\API\Repository\Values\Content\Relation as APIRelation;
12
use eZ\Publish\SPI\Persistence\Content\Handler as ContentHandlerInterface;
13
use eZ\Publish\SPI\Persistence\Content;
14
use eZ\Publish\SPI\Persistence\Content\VersionInfo;
15
use eZ\Publish\SPI\Persistence\Content\ContentInfo;
16
use eZ\Publish\SPI\Persistence\Content\CreateStruct;
17
use eZ\Publish\SPI\Persistence\Content\UpdateStruct;
18
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct;
19
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as RelationCreateStruct;
20
21
/**
22
 * @see \eZ\Publish\SPI\Persistence\Content\Handler
23
 */
24
class ContentHandler extends AbstractInMemoryPersistenceHandler implements ContentHandlerInterface
25
{
26
    const ALL_TRANSLATIONS_KEY = '0';
27
28
    /** @var callable */
29
    private $getContentInfoTags;
30
31
    /** @var callable */
32
    private $getContentInfoKeys;
33
34
    /** @var callable */
35
    private $getContentTags;
36
37
    protected function init(): void
38
    {
39
        $this->getContentInfoTags = function (ContentInfo $info, array $tags = []) {
40
            $tags[] = 'content-' . $info->id;
41
42
            if ($info->mainLocationId) {
43
                $locations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($info->id);
44
                foreach ($locations as $location) {
45
                    $tags[] = 'location-' . $location->id;
46
                    foreach (explode('/', trim($location->pathString, '/')) as $pathId) {
47
                        $tags[] = 'location-path-' . $pathId;
48
                    }
49
                }
50
            }
51
52
            return $tags;
53
        };
54
        $this->getContentInfoKeys = function (ContentInfo $info) {
55
            return [
56
                'ez-content-info-' . $info->id,
57
                'ez-content-info-byRemoteId-' . $this->escapeForCacheKey($info->remoteId),
58
            ];
59
        };
60
61 View Code Duplication
        $this->getContentTags = function (Content $content) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
62
            $versionInfo = $content->versionInfo;
63
            $tags = [
64
                'content-fields-' . $versionInfo->contentInfo->id,
65
                'content-fields-type-' . $versionInfo->contentInfo->contentTypeId,
66
            ];
67
68
            return $this->getCacheTagsForVersion($versionInfo, $tags);
69
        };
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75
    public function create(CreateStruct $struct)
76
    {
77
        // Cached on demand when published or loaded
78
        $this->logger->logCall(__METHOD__, ['struct' => $struct]);
79
80
        return $this->persistenceHandler->contentHandler()->create($struct);
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86
    public function createDraftFromVersion($contentId, $srcVersion, $userId)
87
    {
88
        $this->logger->logCall(__METHOD__, ['content' => $contentId, 'version' => $srcVersion, 'user' => $userId]);
89
        $draft = $this->persistenceHandler->contentHandler()->createDraftFromVersion($contentId, $srcVersion, $userId);
90
        $this->cache->invalidateTags(["content-{$contentId}-version-list"]);
91
92
        return $draft;
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98 View Code Duplication
    public function copy($contentId, $versionNo = null, $newOwnerId = null)
99
    {
100
        $this->logger->logCall(__METHOD__, [
101
            'content' => $contentId,
102
            'version' => $versionNo,
103
            'newOwner' => $newOwnerId,
104
        ]);
105
106
        return $this->persistenceHandler->contentHandler()->copy($contentId, $versionNo, $newOwnerId);
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112
    public function load($contentId, $versionNo = null, array $translations = null)
113
    {
114
        $keySuffix = $versionNo ? "-${versionNo}-" : '-';
115
        $keySuffix .= empty($translations) ? self::ALL_TRANSLATIONS_KEY : implode('|', $translations);
116
117
        return $this->getCacheValue(
118
            (int) $contentId,
119
            'ez-content-',
120
            function ($id) use ($versionNo, $translations) {
121
                return $this->persistenceHandler->contentHandler()->load($id, $versionNo, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 112 can also be of type array; however, eZ\Publish\SPI\Persistence\Content\Handler::load() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
122
            },
123
            $this->getContentTags,
124
            static function (Content $content) use ($keySuffix) {
125
                // Version number & translations is part of keySuffix here and depends on what user asked for
126
                return ['ez-content-' . $content->versionInfo->contentInfo->id . $keySuffix];
127
            },
128
            $keySuffix,
129
            ['content' => $contentId, 'version' => $versionNo, 'translations' => $translations]
130
        );
131
    }
132
133
    public function loadContentList(array $contentIds, array $translations = null): array
134
    {
135
        $keySuffix = '-' . (empty($translations) ? self::ALL_TRANSLATIONS_KEY : implode('|', $translations));
136
137
        return $this->getMultipleCacheValues(
138
            $contentIds,
139
            'ez-content-',
140
            function (array $cacheMissIds) use ($translations) {
141
                return $this->persistenceHandler->contentHandler()->loadContentList($cacheMissIds, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 133 can also be of type array; however, eZ\Publish\SPI\Persisten...dler::loadContentList() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
142
            },
143
            $this->getContentTags,
144
            static function (Content $content) use ($keySuffix) {
145
                // Version number & translations is part of keySuffix here and depends on what user asked for
146
                return ['ez-content-' . $content->versionInfo->contentInfo->id . $keySuffix];
147
            },
148
            $keySuffix,
149
            ['content' => $contentIds, 'translations' => $translations]
150
        );
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156 View Code Duplication
    public function loadContentInfo($contentId)
157
    {
158
        return $this->getCacheValue(
159
            $contentId,
160
            'ez-content-info-',
161
            function ($contentId) {
162
                return $this->persistenceHandler->contentHandler()->loadContentInfo($contentId);
163
            },
164
            $this->getContentInfoTags,
165
            $this->getContentInfoKeys,
166
            '',
167
            ['content' => $contentId]
168
        );
169
    }
170
171 View Code Duplication
    public function loadContentInfoList(array $contentIds)
172
    {
173
        return $this->getMultipleCacheValues(
174
            $contentIds,
175
            'ez-content-info-',
176
            function (array $cacheMissIds) {
177
                return $this->persistenceHandler->contentHandler()->loadContentInfoList($cacheMissIds);
178
            },
179
            $this->getContentInfoTags,
180
            $this->getContentInfoKeys,
181
            '',
182
            ['content' => $contentIds]
183
        );
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189 View Code Duplication
    public function loadContentInfoByRemoteId($remoteId)
190
    {
191
        return $this->getCacheValue(
192
            $this->escapeForCacheKey($remoteId),
193
            'ez-content-info-byRemoteId-',
194
            function () use ($remoteId) {
195
                return $this->persistenceHandler->contentHandler()->loadContentInfoByRemoteId($remoteId);
196
            },
197
            $this->getContentInfoTags,
198
            $this->getContentInfoKeys,
199
            '',
200
            ['content' => $remoteId]
201
        );
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    public function loadVersionInfo($contentId, $versionNo)
208
    {
209
        $cacheItem = $this->cache->getItem("ez-content-version-info-${contentId}-${versionNo}");
210 View Code Duplication
        if ($cacheItem->isHit()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
211
            $this->logger->logCacheHit(['content' => $contentId, 'version' => $versionNo]);
212
213
            return $cacheItem->get();
214
        }
215
216
        $this->logger->logCacheMiss(['content' => $contentId, 'version' => $versionNo]);
217
        $versionInfo = $this->persistenceHandler->contentHandler()->loadVersionInfo($contentId, $versionNo);
218
        $cacheItem->set($versionInfo);
219
        $cacheItem->tag($this->getCacheTagsForVersion($versionInfo));
220
        $this->cache->save($cacheItem);
221
222
        return $versionInfo;
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     */
228
    public function countDraftsForUser(int $userId): int
229
    {
230
        $this->logger->logCall(__METHOD__, ['user' => $userId]);
231
232
        return $this->persistenceHandler->contentHandler()->countDraftsForUser($userId);
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    public function loadDraftsForUser($userId)
239
    {
240
        $this->logger->logCall(__METHOD__, ['user' => $userId]);
241
242
        return $this->persistenceHandler->contentHandler()->loadDraftsForUser($userId);
243
    }
244
245
    /**
246
     * {@inheritdoc}
247
     */
248
    public function loadDraftListForUser(int $userId, int $offset = 0, int $limit = -1): array
249
    {
250
        $this->logger->logCall(__METHOD__, ['user' => $userId, 'offset' => $offset, 'limit' => $limit]);
251
252
        return $this->persistenceHandler->contentHandler()->loadDraftListForUser($userId, $offset, $limit);
253
    }
254
255
    /**
256
     * {@inheritdoc}
257
     */
258
    public function setStatus($contentId, $status, $versionNo)
259
    {
260
        $this->logger->logCall(__METHOD__, ['content' => $contentId, 'status' => $status, 'version' => $versionNo]);
261
        $return = $this->persistenceHandler->contentHandler()->setStatus($contentId, $status, $versionNo);
262
263
        if ($status === VersionInfo::STATUS_PUBLISHED) {
264
            $this->cache->invalidateTags(['content-' . $contentId]);
265
        } else {
266
            $this->cache->invalidateTags(["content-{$contentId}-version-{$versionNo}"]);
267
        }
268
269
        return $return;
270
    }
271
272
    /**
273
     * {@inheritdoc}
274
     */
275
    public function updateMetadata($contentId, MetadataUpdateStruct $struct)
276
    {
277
        $this->logger->logCall(__METHOD__, ['content' => $contentId, 'struct' => $struct]);
278
        $contentInfo = $this->persistenceHandler->contentHandler()->updateMetadata($contentId, $struct);
279
        $this->cache->invalidateTags(['content-' . $contentId]);
280
281
        return $contentInfo;
282
    }
283
284
    /**
285
     * {@inheritdoc}
286
     */
287
    public function updateContent($contentId, $versionNo, UpdateStruct $struct)
288
    {
289
        $this->logger->logCall(__METHOD__, ['content' => $contentId, 'version' => $versionNo, 'struct' => $struct]);
290
        $content = $this->persistenceHandler->contentHandler()->updateContent($contentId, $versionNo, $struct);
291
        $this->cache->invalidateTags(["content-{$contentId}-version-{$versionNo}"]);
292
293
        return $content;
294
    }
295
296
    /**
297
     * {@inheritdoc}
298
     */
299
    public function deleteContent($contentId)
300
    {
301
        $this->logger->logCall(__METHOD__, ['content' => $contentId]);
302
303
        // Load reverse field relations first
304
        $reverseRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
305
            $contentId,
306
            APIRelation::FIELD | APIRelation::ASSET
307
        );
308
309
        $return = $this->persistenceHandler->contentHandler()->deleteContent($contentId);
310
311
        if (!empty($reverseRelations)) {
312
            $tags = \array_map(
313
                static function ($relation) {
314
                    // only the full content object *with* fields is affected by this
315
                    return 'content-fields-' . $relation->sourceContentId;
316
                },
317
                $reverseRelations
318
            );
319
        } else {
320
            $tags = [];
321
        }
322
        $tags[] = 'content-' . $contentId;
323
        $this->cache->invalidateTags($tags);
324
325
        return $return;
326
    }
327
328
    /**
329
     * {@inheritdoc}
330
     */
331
    public function deleteVersion($contentId, $versionNo)
332
    {
333
        $this->logger->logCall(__METHOD__, ['content' => $contentId, 'version' => $versionNo]);
334
        $return = $this->persistenceHandler->contentHandler()->deleteVersion($contentId, $versionNo);
335
        $this->cache->invalidateTags(["content-{$contentId}-version-{$versionNo}"]);
336
337
        return $return;
338
    }
339
340
    /**
341
     * {@inheritdoc}
342
     */
343
    public function listVersions($contentId, $status = null, $limit = -1)
344
    {
345
        $cacheItem = $this->cache->getItem("ez-content-${contentId}-version-list" . ($status ? "-byStatus-${status}" : '') . "-limit-{$limit}");
346 View Code Duplication
        if ($cacheItem->isHit()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
347
            $this->logger->logCacheHit(['content' => $contentId, 'status' => $status]);
348
349
            return $cacheItem->get();
350
        }
351
352
        $this->logger->logCacheMiss(['content' => $contentId, 'status' => $status]);
353
        $versions = $this->persistenceHandler->contentHandler()->listVersions($contentId, $status, $limit);
354
        $cacheItem->set($versions);
355
        $tags = ["content-{$contentId}", "content-{$contentId}-version-list"];
356
        foreach ($versions as $version) {
357
            $tags = $this->getCacheTagsForVersion($version, $tags);
358
        }
359
        $cacheItem->tag($tags);
360
        $this->cache->save($cacheItem);
361
362
        return $versions;
363
    }
364
365
    /**
366
     * {@inheritdoc}
367
     */
368
    public function addRelation(RelationCreateStruct $relation)
369
    {
370
        $this->logger->logCall(__METHOD__, ['struct' => $relation]);
371
372
        return $this->persistenceHandler->contentHandler()->addRelation($relation);
373
    }
374
375
    /**
376
     * {@inheritdoc}
377
     */
378
    public function removeRelation($relationId, $type)
379
    {
380
        $this->logger->logCall(__METHOD__, ['relation' => $relationId, 'type' => $type]);
381
        $this->persistenceHandler->contentHandler()->removeRelation($relationId, $type);
382
    }
383
384
    /**
385
     * {@inheritdoc}
386
     */
387 View Code Duplication
    public function loadRelations($sourceContentId, $sourceContentVersionNo = null, $type = null)
388
    {
389
        $this->logger->logCall(
390
            __METHOD__,
391
            [
392
                'content' => $sourceContentId,
393
                'version' => $sourceContentVersionNo,
394
                'type' => $type,
395
            ]
396
        );
397
398
        return $this->persistenceHandler->contentHandler()->loadRelations($sourceContentId, $sourceContentVersionNo, $type);
399
    }
400
401
    /**
402
     * {@inheritdoc}
403
     */
404
    public function loadReverseRelations($destinationContentId, $type = null)
405
    {
406
        $this->logger->logCall(__METHOD__, ['content' => $destinationContentId, 'type' => $type]);
407
408
        return $this->persistenceHandler->contentHandler()->loadReverseRelations($destinationContentId, $type);
409
    }
410
411
    /**
412
     * {@inheritdoc}
413
     */
414
    public function publish($contentId, $versionNo, MetadataUpdateStruct $struct)
415
    {
416
        $this->logger->logCall(__METHOD__, ['content' => $contentId, 'version' => $versionNo, 'struct' => $struct]);
417
        $content = $this->persistenceHandler->contentHandler()->publish($contentId, $versionNo, $struct);
418
        $this->cache->invalidateTags(['content-' . $contentId]);
419
420
        return $content;
421
    }
422
423
    /**
424
     * {@inheritdoc}
425
     */
426
    public function removeTranslationFromContent($contentId, $languageCode)
427
    {
428
        $this->deleteTranslationFromContent($contentId, $languageCode);
429
    }
430
431
    /**
432
     * {@inheritdoc}
433
     */
434
    public function deleteTranslationFromContent($contentId, $languageCode)
435
    {
436
        $this->logger->logCall(
437
            __METHOD__,
438
            [
439
                'contentId' => $contentId,
440
                'languageCode' => $languageCode,
441
            ]
442
        );
443
444
        $this->persistenceHandler->contentHandler()->deleteTranslationFromContent($contentId, $languageCode);
445
        $this->cache->invalidateTags(['content-' . $contentId]);
446
    }
447
448
    /**
449
     * {@inheritdoc}
450
     */
451
    public function deleteTranslationFromDraft($contentId, $versionNo, $languageCode)
452
    {
453
        $this->logger->logCall(
454
            __METHOD__,
455
            ['content' => $contentId, 'version' => $versionNo, 'languageCode' => $languageCode]
456
        );
457
        $content = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
458
            $contentId,
459
            $versionNo,
460
            $languageCode
461
        );
462
        $this->cache->invalidateTags(["content-{$contentId}-version-{$versionNo}"]);
463
464
        return $content;
465
    }
466
467
    /**
468
     * Return relevant content and location tags so cache can be purged reliably.
469
     *
470
     * For use when generating cache, not on invalidation.
471
     *
472
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $versionInfo
473
     * @param array $tags Optional, can be used to specify other tags.
474
     *
475
     * @return array
476
     */
477
    private function getCacheTagsForVersion(VersionInfo $versionInfo, array $tags = []): array
478
    {
479
        $contentInfo = $versionInfo->contentInfo;
480
        $tags[] = 'content-' . $contentInfo->id . '-version-' . $versionInfo->versionNo;
481
        $getContentInfoTagsFn = $this->getContentInfoTags;
482
483
        return $getContentInfoTagsFn($contentInfo, $tags);
484
    }
485
486 View Code Duplication
    private function getCacheTagsForContent(Content $content): array
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
487
    {
488
        $versionInfo = $content->versionInfo;
489
        $tags = [
490
            'content-fields-' . $versionInfo->contentInfo->id,
491
            'content-fields-type-' . $versionInfo->contentInfo->contentTypeId,
492
        ];
493
494
        return $this->getCacheTagsForVersion($versionInfo, $tags);
495
    }
496
}
497