AttachmentRepository::buildAttachment()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types = 1);
4
5
/**
6
 * File: AttachmentRepository.php
7
 *
8
 * @author Bartosz Kubicki [email protected]>
9
 * @copyright Copyright (C) 2018 Lizard Media (http://lizardmedia.pl)
10
 */
11
12
namespace LizardMedia\ProductAttachment\Model;
13
14
use Exception;
15
use LizardMedia\ProductAttachment\Api\AttachmentRepositoryInterface;
16
use LizardMedia\ProductAttachment\Api\Data\AttachmentFactoryInterface;
17
use LizardMedia\ProductAttachment\Api\Data\AttachmentInterface;
18
use LizardMedia\ProductAttachment\Api\Data\File\ContentUploaderInterface;
19
use LizardMedia\ProductAttachment\Helper\Version;
20
use LizardMedia\ProductAttachment\Model\Attachment\ContentValidator;
21
use LizardMedia\ProductAttachment\Model\AttachmentRepository\SearchResultProcessor;
22
use LizardMedia\ProductAttachment\Model\Product\TypeHandler\Attachment as AttachmentHandler;
23
use LizardMedia\ProductAttachment\Model\ResourceModel\Attachment\Collection;
24
use LizardMedia\ProductAttachment\Model\ResourceModel\Attachment\Collection as AttachmentCollection;
25
use LizardMedia\ProductAttachment\Model\ResourceModel\Attachment\CollectionFactory as AttachmentCollectionFactory;
26
use Magento\Catalog\Api\Data\ProductInterface;
27
use Magento\Catalog\Api\ProductRepositoryInterface;
28
use Magento\Downloadable\Helper\Download;
29
use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
30
use Magento\Framework\Api\SearchCriteriaInterface;
31
use Magento\Framework\Api\SearchResultsInterface;
32
use Magento\Framework\Api\SearchResultsInterfaceFactory;
33
use Magento\Framework\Exception\InputException;
34
use Magento\Framework\Exception\NoSuchEntityException;
35
use Magento\Framework\Exception\StateException;
36
use Magento\Framework\Serialize\Serializer\Json as JsonSeliarizer;
37
38
/**
39
 * Class AttachmentRepository
40
 * @package LizardMedia\ProductAttachment\Model
41
 */
42
class AttachmentRepository implements AttachmentRepositoryInterface
43
{
44
    /**
45
     * @var AttachmentFactoryInterface
46
     */
47
    private $attachmentFactory;
48
49
    /**
50
     * @var ContentUploaderInterface
51
     */
52
    private $fileContentUploader;
53
54
    /**
55
     * @var ContentValidator
56
     */
57
    private $contentValidator;
58
59
    /**
60
     * @var SearchResultProcessor
61
     */
62
    private $searchResultProcessor;
63
64
    /**
65
     * @var AttachmentHandler
66
     */
67
    private $attachmentTypeHandler;
68
69
    /**
70
     * @var AttachmentCollectionFactory
71
     */
72
    private $attachmentCollectionFactory;
73
74
    /**
75
     * @var Version
76
     */
77
    private $version;
78
79
    /**
80
     * @var ProductRepositoryInterface
81
     */
82
    private $productRepository;
83
84
    /**
85
     * @var JoinProcessorInterface
86
     */
87
    private $extensionAttributesJoinProcessor;
88
89
    /**
90
     * @var SearchResultsInterfaceFactory
91
     */
92
    private $searchResultFactory;
93
94
    /**
95
     * @var JsonSeliarizer
96
     */
97
    private $jsonSeliarizer;
98
99
    /**
100
     * @param AttachmentFactoryInterface $attachmentFactory
101
     * @param ContentUploaderInterface $fileContentUploader
102
     * @param ContentValidator $contentValidator
103
     * @param SearchResultProcessor $searchResultProcessor
104
     * @param AttachmentHandler $attachmentTypeHandler
105
     * @param AttachmentCollectionFactory $attachmentCollectionFactory
106
     * @param Version $version
107
     * @param ProductRepositoryInterface $productRepository
108
     * @param JoinProcessorInterface $extensionAttributesJoinProcessor
109
     * @param SearchResultsInterfaceFactory $searchResultsFactory
110
     * @param JsonSeliarizer $jsonSeliarizer
111
     */
112
    public function __construct(
113
        AttachmentFactoryInterface $attachmentFactory,
114
        ContentUploaderInterface $fileContentUploader,
115
        ContentValidator $contentValidator,
116
        SearchResultProcessor $searchResultProcessor,
117
        AttachmentHandler $attachmentTypeHandler,
118
        AttachmentCollectionFactory $attachmentCollectionFactory,
119
        Version $version,
120
        ProductRepositoryInterface $productRepository,
121
        JoinProcessorInterface $extensionAttributesJoinProcessor,
122
        SearchResultsInterfaceFactory $searchResultsFactory,
123
        JsonSeliarizer $jsonSeliarizer
124
    ) {
125
        $this->attachmentFactory = $attachmentFactory;
126
        $this->fileContentUploader = $fileContentUploader;
127
        $this->contentValidator = $contentValidator;
128
        $this->searchResultProcessor = $searchResultProcessor;
129
        $this->attachmentTypeHandler = $attachmentTypeHandler;
130
        $this->attachmentCollectionFactory = $attachmentCollectionFactory;
131
        $this->version = $version;
132
        $this->productRepository = $productRepository;
133
        $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor;
134
        $this->searchResultFactory = $searchResultsFactory;
135
        $this->jsonSeliarizer = $jsonSeliarizer;
136
    }
137
138
139
    /**
140
     * @param int $id
141
     * @return AttachmentInterface
142
     * @throws NoSuchEntityException
143
     */
144
    public function getById($id) : AttachmentInterface
145
    {
146
        $attachment = $this->attachmentFactory->create();
147
        $attachment->getResource()->load($attachment, $id);
148
        if (!$attachment->getId()) {
149
            throw new NoSuchEntityException(__('Unable to find attachment with id "%1"', $id));
150
        }
151
        return $attachment;
152
    }
153
154
155
    /**
156
     * @param SearchCriteriaInterface $searchCriteria
157
     * @param int $storeId
158
     *
159
     * @return SearchResultsInterface
160
     */
161
    public function getList(SearchCriteriaInterface $searchCriteria, int $storeId = 0) : SearchResultsInterface
162
    {
163
        $collection = $this->attachmentCollectionFactory->create();
164
        /** @var $collection AttachmentCollection */
165
166
        $this->searchResultProcessor->addFiltersToCollection($searchCriteria, $collection);
167
        $this->searchResultProcessor->addSortOrdersToCollection($searchCriteria, $collection);
168
        $this->searchResultProcessor->addPagingToCollection($searchCriteria, $collection);
169
170
        $collection->load();
171
172
        $searchResult = $this->buildSearchResult($searchCriteria, $collection);
173
        $searchResultRebuilt = [];
174
175
        foreach ($searchResult->getItems() as $attachment) {
176
            $attachment->setStoreId($storeId);
177
            $attachment->getResource()->loadItemTitle($attachment);
178
            $searchResultRebuilt[] = $this->buildAttachment($attachment);
179
        }
180
181
        return $searchResult->setItems($searchResultRebuilt);
182
    }
183
184
185
    /**
186
     * @param SearchCriteriaInterface $searchCriteria
187
     * @param AttachmentCollection $collection
188
     *
189
     * @return SearchResultsInterface $searchResults
190
     */
191
    private function buildSearchResult(SearchCriteriaInterface $searchCriteria, AttachmentCollection $collection)
192
    {
193
        $searchResults = $this->searchResultFactory->create();
194
195
        /** @var $searchResults SearchResultsInterface */
196
197
        $searchResults->setSearchCriteria($searchCriteria);
198
        $searchResults->setItems($collection->getItems());
199
        $searchResults->setTotalCount($collection->getSize());
200
201
        return $searchResults;
202
    }
203
204
205
    /**
206
     * @param ProductInterface $product
207
     * @return AttachmentInterface[]
208
     * @throws Exception
209
     *
210
     */
211
    public function getAttachmentsByProduct(ProductInterface $product) : array
212
    {
213
        $attachmentList = [];
214
        $attachments = $this->getAttachments($product);
215
216
        foreach ($attachments as $attachment) {
217
            $attachmentList[] = $this->buildAttachment($attachment);
218
        }
219
220
        return $attachmentList;
221
    }
222
223
    /**
224
     * @param ProductInterface $product
225
     * @return AttachmentCollection
226
     * @throws Exception
227
     */
228
    private function getAttachments(ProductInterface $product) : ?Collection
229
    {
230
        if ($product->getProductAttachments() === null) {
231
            $attachmentCollection = $this->attachmentCollectionFactory->create()
232
                ->addProductToFilter([$product->getEntityId()])
233
                ->addTitleToResult((int) $product->getStoreId());
234
235
            $this->extensionAttributesJoinProcessor->process($attachmentCollection);
236
            $product->setProductAttachments($attachmentCollection);
237
        }
238
239
        return $product->getProductAttachments();
240
    }
241
242
243
    /**
244
     * Attachment is build using data from different tables.
245
     *
246
     * @param AttachmentInterface $resourceData
247
     * @return AttachmentInterface
248
     */
249
    protected function buildAttachment($resourceData) : AttachmentInterface
250
    {
251
        $attachment = $this->attachmentFactory->create();
252
        $this->setBasicFields($resourceData, $attachment);
253
        return $attachment;
254
    }
255
256
257
    /**
258
     * @param AttachmentInterface $resourceData
259
     * @param AttachmentInterface $dataObject
260
     * @return void
261
     */
262
    protected function setBasicFields(AttachmentInterface $resourceData, AttachmentInterface $dataObject) : void
263
    {
264
        $dataObject->setId($resourceData->getId());
265
266
        $storeTitle = $resourceData->getStoreTitle();
267
        $title = $resourceData->getTitle();
268
269
        if (!empty($storeTitle)) {
270
            $dataObject->setTitle($storeTitle);
271
        } else {
272
            $dataObject->setTitle($title);
273
        }
274
275
        $dataObject->setSortOrder($resourceData->getSortOrder());
276
        $dataObject->setProductId($resourceData->getProductId());
277
        $dataObject->setAttachmentType($resourceData->getAttachmentType());
278
279
        if ($resourceData->getAttachmentFile()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $resourceData->getAttachmentFile() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
280
            $dataObject->setAttachmentFile($resourceData->getAttachmentFile());
281
        }
282
283
        if ($resourceData->getAttachmentUrl()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $resourceData->getAttachmentUrl() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
284
            $dataObject->setAttachmentUrl($resourceData->getAttachmentUrl());
285
        }
286
    }
287
288
289
    /**
290
     * @param string $sku
291
     * @param AttachmentInterface $attachment
292
     * @param bool $isGlobalScopeContent
293
     * @return int
294
     * @throws InputException
295
     * @throws Exception
296
     *
297
     * @throws NoSuchEntityException
298
     */
299
    public function save(string $sku, AttachmentInterface $attachment, bool $isGlobalScopeContent = true) : int
300
    {
301
        $product = $this->productRepository->get($sku, true);
302
303
        $id = (int) $attachment->getId();
304
305
        if ($id) {
306
            return $this->updateAttachment($product, $attachment, $isGlobalScopeContent);
307
        } else {
308
            $validateAttachmentContent = !($attachment->getAttachmentType() === Download::LINK_TYPE_FILE
309
                && $attachment->getAttachmentFile());
0 ignored issues
show
Bug Best Practice introduced by
The expression $attachment->getAttachmentFile() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
310
311
            if (!$this->contentValidator->isValid($attachment, $validateAttachmentContent)) {
312
                throw new InputException(__('Provided attachment information is invalid.'));
313
            }
314
315
            if (!in_array(
316
                $attachment->getAttachmentType(),
317
                [Download::LINK_TYPE_URL, Download::LINK_TYPE_FILE],
318
                true
319
            )) {
320
                throw new InputException(__('Invalid attachment type.'));
321
            }
322
323
            $title = $attachment->getTitle();
324
325
            if (empty($title)) {
326
                throw new InputException(__('Attachment title cannot be empty.'));
327
            }
328
329
            return $this->saveAttachment($product, $attachment, $isGlobalScopeContent);
330
        }
331
    }
332
333
334
    /**
335
     * @param ProductInterface $product
336
     * @param AttachmentInterface $attachment
337
     * @param bool $isGlobalScopeContent
338
     * @return int
339
     */
340
    protected function saveAttachment(
341
        ProductInterface $product,
342
        AttachmentInterface $attachment,
343
        bool $isGlobalScopeContent
344
    ) : int {
345
        $attachmentData = [
346
            Attachment::ID => (int) $attachment->getId(),
347
            'is_delete' => 0,
348
            Attachment::ATTACHMENT_TYPE => $attachment->getAttachmentType(),
349
            Attachment::SORT_ORDER => $attachment->getSortOrder(),
350
            Attachment::TITLE => $attachment->getTitle(),
351
        ];
352
353
        if ($attachment->getAttachmentType() === Download::LINK_TYPE_FILE
354
            && $attachment->getAttachmentFile() === null) {
355
            $attachmentData[Download::LINK_TYPE_FILE] = $this->jsonSeliarizer->serialize(
356
                [
357
                    $this->fileContentUploader->upload($attachment->getAttachmentFileContent(), 'attachment'),
0 ignored issues
show
Bug introduced by
It seems like $attachment->getAttachmentFileContent() can be null; however, upload() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
358
                ]
359
            );
360
        } elseif ($attachment->getAttachmentType() === Download::LINK_TYPE_URL) {
361
            $attachmentData[Attachment::ATTACHMENT_URL] = $attachment->getAttachmentUrl();
362
        } else {
363
            $attachmentData[Download::LINK_TYPE_FILE] = $this->jsonSeliarizer->serialize(
364
                [
365
                    [
366
                        Download::LINK_TYPE_FILE => $attachment->getAttachmentFile(),
367
                        'status' => 'old',
368
                    ],
369
                ]
370
            );
371
        }
372
373
        $downloadableData = ['attachment' => [$attachmentData]];
374
375
        if ($isGlobalScopeContent) {
376
            $product->setStoreId(0);
377
        }
378
379
        $this->attachmentTypeHandler->save($product, $downloadableData);
380
        return $product->getLastAddedAttachmentId();
381
    }
382
383
384
    /**
385
     * @param ProductInterface $product
386
     * @param AttachmentInterface $attachment
387
     * @param bool $isGlobalScopeContent
388
     * @return int
389
     * @throws InputException
390
     * @throws Exception
391
     * @throws NoSuchEntityException
392
     */
393
    protected function updateAttachment(
394
        ProductInterface $product,
395
        AttachmentInterface $attachment,
396
        bool $isGlobalScopeContent
397
    ): int {
398
        $id = (int) $attachment->getId();
399
400
        $existingAttachment = $this->attachmentFactory->create()->load($id);
401
        /** @var $existingAttachment AttachmentInterface */
402
403
        if (!$existingAttachment->getId()) {
404
            throw new NoSuchEntityException(__('There is no attachment with provided ID.'));
405
        }
406
407
        $linkFieldValue = (int) $product->getData($this->version->getLinkFieldValue());
408
409
        if ($existingAttachment->getProductId() !== $linkFieldValue) {
410
            throw new InputException(__('Provided attachment is not related to given product.'));
411
        }
412
413
        $validateFileContent = $attachment->getAttachmentFileContent() !== null;
414
415
        if (!$this->contentValidator->isValid($attachment, $validateFileContent)) {
416
            throw new InputException(__('Provided attachment information is invalid.'));
417
        }
418
419
        if ($isGlobalScopeContent) {
420
            $product->setStoreId(0);
421
        }
422
423
        $title = $attachment->getTitle();
424
425
        if (empty($title)) {
426
            if ($isGlobalScopeContent) {
427
                throw new InputException(__('Attachment title cannot be empty.'));
428
            }
429
430
            $existingAttachment->setTitle('');
431
        } else {
432
            $existingAttachment->setTitle($attachment->getTitle());
433
        }
434
435
        if ($attachment->getAttachmentType() === Download::LINK_TYPE_FILE
436
            && $attachment->getAttachmentFileContent() === null) {
437
            $attachment->setAttachmentFile($existingAttachment->getAttachmentFile());
438
        }
439
440
        $this->saveAttachment($product, $attachment, $isGlobalScopeContent);
441
442
        return (int) $existingAttachment->getId();
443
    }
444
445
    /**
446
     * @param int $id
447
     * @return bool
448
     * @throws StateException
449
     * @throws NoSuchEntityException
450
     */
451
    public function delete(int $id) : bool
452
    {
453
        /** @var $attachment AttachmentInterface */
454
        $attachment = $this->attachmentFactory->create()->load($id);
455
456
        if (!(int) $attachment->getId()) {
457
            throw new NoSuchEntityException(__('There is no attachment with provided ID.'));
458
        }
459
460
        try {
461
            $attachment->delete();
462
        } catch (Exception $exception) {
463
            throw new StateException(__('Cannot delete attachment with id "%1"', $attachment->getId()), $exception);
464
        }
465
466
        return true;
467
    }
468
}
469