Completed
Pull Request — master (#424)
by Jonas
03:35
created

CategoryResolver::resolve()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
nc 1
dl 0
loc 1
c 0
b 0
f 0
1
<?php
2
/**
3
 * (c) shopware AG <[email protected]>
4
 * For the full copyright and license information, please view the LICENSE
5
 * file that was distributed with this source code.
6
 */
7
8
namespace ShopwarePlugins\Connect\Components;
9
10
use Shopware\CustomModels\Connect\ProductToRemoteCategory;
11
use Shopware\CustomModels\Connect\RemoteCategory;
12
use Shopware\CustomModels\Connect\RemoteCategoryRepository;
13
use Shopware\Components\Model\ModelManager;
14
use Shopware\CustomModels\Connect\ProductToRemoteCategoryRepository;
15
use Shopware\Models\Category\Repository as CategoryRepository;
16
use Shopware\Models\Category\Category;
17
use Shopware\Components\Model\CategoryDenormalization;
18
19
abstract class CategoryResolver
20
{
21
    /**
22
     * @var ModelManager
23
     */
24
    protected $manager;
25
26
    /**
27
     * @var \Shopware\CustomModels\Connect\RemoteCategoryRepository
28
     */
29
    protected $remoteCategoryRepository;
30
31
    /**
32
     * @var \Shopware\CustomModels\Connect\ProductToRemoteCategoryRepository
33
     */
34
    protected $productToRemoteCategoryRepository;
35
36
    /**
37
     * @var \Shopware\Models\Category\Repository
38
     */
39
    protected $categoryRepository;
40
41
    /**
42
     * @var CategoryDenormalization
43
     */
44
    private $categoryDenormalization;
45
46
    public function __construct(
47
        ModelManager $manager,
48
        RemoteCategoryRepository $remoteCategoryRepository,
49
        ProductToRemoteCategoryRepository $productToRemoteCategoryRepository,
50
        CategoryRepository $categoryRepository,
51
        CategoryDenormalization $categoryDenormalization
52
    ) {
53
        $this->manager = $manager;
54
        $this->remoteCategoryRepository = $remoteCategoryRepository;
55
        $this->productToRemoteCategoryRepository = $productToRemoteCategoryRepository;
56
        $this->categoryRepository = $categoryRepository;
57
        $this->categoryDenormalization = $categoryDenormalization;
58
    }
59
60
    /**
61
     * Returns array with category entities
62
     * if they don't exist will be created
63
     *
64
     * @param array $categories
65
     * @param int $shopId
66
     * @param string $stream
67
     * @return \Shopware\Models\Category\Category[]
68
     */
69
    abstract public function resolve(array $categories, $shopId, $stream);
70
71
    /**
72
     * Generates categories tree by given array of categories
73
     *
74
     * @param array $categories
75
     * @param string $idPrefix
76
     * @return array
77
     */
78
    abstract public function generateTree(array $categories, $idPrefix = '');
79
80
    /**
81
     * Stores raw Shopware Connect categories
82
     *
83
     * @param array $categories
84
     * @param int $articleId
85
     * @param int $shopId
86
     * @return void
87
     */
88
    public function storeRemoteCategories(array $categories, $articleId, $shopId)
89
    {
90
        $remoteCategories = [];
91
        foreach ($categories as $categoryKey => $category) {
92
            $remoteCategory = $this->remoteCategoryRepository->findOneBy(['categoryKey' => $categoryKey, 'shopId' => $shopId]);
93
            if (!$remoteCategory) {
94
                $remoteCategory = new RemoteCategory();
95
                $remoteCategory->setCategoryKey($categoryKey);
96
                $remoteCategory->setShopId($shopId);
97
            }
98
            $remoteCategory->setLabel($category);
99
            $this->manager->persist($remoteCategory);
100
            $remoteCategories[] = $remoteCategory;
101
        }
102
103
        $this->manager->flush();
104
105
        $this->removeProductsFromNotAssignedRemoteCategories($remoteCategories, $articleId);
106
        $this->addProductToRemoteCategory($remoteCategories, $articleId);
107
108
        $this->manager->flush();
109
    }
110
111
    /**
112
     * @param RemoteCategory[] $remoteCategories
113
     * @param $articleId
114
     */
115
    private function addProductToRemoteCategory(array $remoteCategories, $articleId)
116
    {
117
        $productToCategories = $this->productToRemoteCategoryRepository->getRemoteCategoryIds($articleId);
118
        /** @var $remoteCategory \Shopware\CustomModels\Connect\RemoteCategory */
119
        foreach ($remoteCategories as $remoteCategory) {
120
            if (!in_array($remoteCategory->getId(), $productToCategories)) {
121
                $productToCategory = new ProductToRemoteCategory();
122
                $productToCategory->setArticleId($articleId);
123
                $productToCategory->setConnectCategory($remoteCategory);
124
                $this->manager->persist($productToCategory);
125
            }
126
        }
127
    }
128
129
    /**
130
     * @param \Shopware\CustomModels\Connect\RemoteCategory[] $assignedCategories
131
     * @param int $articleId
132
     */
133
    private function removeProductsFromNotAssignedRemoteCategories(array $assignedCategories, $articleId)
134
    {
135
        $currentProductCategoryIds = $this->productToRemoteCategoryRepository->getRemoteCategoryIds($articleId);
136
137
        $assignedCategoryIds = array_map(function (RemoteCategory $assignedCategory) {
138
            return $assignedCategory->getId();
139
        }, $assignedCategories);
140
141
        /** @var int $currentProductCategoryId */
142
        foreach ($currentProductCategoryIds as $currentProductCategoryId) {
143
            if (!in_array($currentProductCategoryId, $assignedCategoryIds)) {
144
                $this->deleteAssignmentOfLocalCategories($currentProductCategoryId, $articleId);
145
                $this->productToRemoteCategoryRepository->deleteByConnectCategoryId($currentProductCategoryId, $articleId);
146
            }
147
        }
148
    }
149
150
    /**
151
     * @param int $currentProductCategoryId
152
     * @param int $articleId
153
     */
154
    private function deleteAssignmentOfLocalCategories($currentProductCategoryId, $articleId)
155
    {
156
        $localCategoriesIds = $this->manager->getConnection()->executeQuery(
157
            'SELECT local_category_id FROM s_plugin_connect_categories_to_local_categories WHERE remote_category_id = ?',
158
            [$currentProductCategoryId]
159
        )->fetchAll(\PDO::FETCH_COLUMN);
160
        if ($localCategoriesIds) {
161
            $this->manager->getConnection()->executeQuery(
162
                'DELETE FROM `s_articles_categories` WHERE `articleID` = ? AND `categoryID` IN (?)',
163
                [$articleId, $localCategoriesIds],
164
                [\PDO::PARAM_INT, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY]
165
            );
166
            foreach ($localCategoriesIds as $categoryId) {
167
                $this->categoryDenormalization->removeAssignment($articleId, $categoryId);
168
            }
169
170
            $this->deleteEmptyConnectCategories($localCategoriesIds);
171
        }
172
    }
173
174
    /**
175
     * @param int[] $categoryIds
176
     */
177
    public function deleteEmptyConnectCategories(array $categoryIds)
178
    {
179
        foreach ($categoryIds as $categoryId) {
180
            $articleCount = (int) $this->manager->getConnection()->fetchColumn(
181
                'SELECT COUNT(id) FROM s_articles_categories WHERE categoryID = ?',
182
                [$categoryId]
183
            );
184
            if ($articleCount === 0) {
185
                $this->deleteEmptyCategory($categoryId);
186
            }
187
        }
188
    }
189
190
    /**
191
     * @param int $categoryId
192
     */
193
    private function deleteEmptyCategory($categoryId)
194
    {
195
        $connectImported = $this->manager->getConnection()->fetchColumn(
196
            'SELECT connect_imported_category FROM s_categories_attributes WHERE categoryID = ?',
197
            [$categoryId]
198
        );
199
        if ($connectImported == 1 && $this->countChildCategories($categoryId) === 0) {
200
            $parent = (int) $this->manager->getConnection()->fetchColumn(
201
                'SELECT parent FROM s_categories WHERE `id` = ?',
202
                [$categoryId]
203
            );
204
205
            $this->manager->getConnection()->executeQuery(
206
                'DELETE FROM `s_categories` WHERE `id` = ?',
207
                [$categoryId]
208
            );
209
210
            $this->deleteEmptyCategory($parent);
211
        }
212
    }
213
214
    /**
215
     * Loop through category tree and fetch ids
216
     *
217
     * @param array $node
218
     * @param int $parentId
219
     * @param int $shopId
220
     * @param string $stream
221
     * @param bool $returnOnlyLeafs
222
     * @param array $categories
223
     * @return array
224
     */
225
    public function convertTreeToKeys(array $node, $parentId, $shopId, $stream, $returnOnlyLeafs = true, $categories = [])
226
    {
227
        foreach ($node as $category) {
228
            $categoryId = $this->checkAndCreateLocalCategory($category['name'], $category['categoryId'], $parentId, $shopId, $stream);
229
230
            if ((!$returnOnlyLeafs) || (empty($category['children']))) {
231
                $categories[] = [
232
                    'categoryKey' => $categoryId,
233
                    'parentId' => $parentId,
234
                    'remoteCategory' => $category['categoryId']
235
                ];
236
            }
237
238
            if (!empty($category['children'])) {
239
                $categories = $this->convertTreeToKeys($category['children'], $categoryId, $shopId, $stream, $returnOnlyLeafs, $categories);
240
            }
241
        }
242
243
        return $categories;
244
    }
245
246
    /**
247
     * @param string $categoryName
248
     * @param string $categoryKey
249
     * @param int $parentId
250
     * @param int $shopId
251
     * @param string stream
252
     * @return int
253
     */
254
    private function checkAndCreateLocalCategory($categoryName, $categoryKey, $parentId, $shopId, $stream)
255
    {
256
        $id = $this->manager->getConnection()->fetchColumn('SELECT `s_categories`.`id` 
257
            FROM `s_categories`
258
            JOIN `s_categories_attributes` ON s_categories.id = s_categories_attributes.categoryID
259
            WHERE s_categories.`parent` = :parentId AND s_categories.`description` = :description AND s_categories_attributes.connect_imported_category = 1',
260
            [':parentId' => $parentId, ':description' => $categoryName]);
261
262
        if (!$id) {
263
            return $this->createLocalCategory($categoryName, $categoryKey, $parentId, $shopId, $stream);
264
        }
265
266
        $remoteCategoryId = $this->manager->getConnection()->fetchColumn('SELECT ctlc.remote_category_id
267
             FROM s_plugin_connect_categories_to_local_categories AS ctlc
268
             JOIN s_plugin_connect_categories AS cc ON cc.id = ctlc.remote_category_id
269
             WHERE cc.category_key = ? AND cc.shop_id = ? AND ctlc.stream = ?',
270
            [$categoryKey, $shopId, $stream]);
271
272
        //create entry in connect_categories_to_local_categories for the given stream -> for "merging" when assigning an other stream to the same category
273
        if (!$remoteCategoryId) {
274
            $this->manager->getConnection()->executeQuery('INSERT INTO s_plugin_connect_categories_to_local_categories (remote_category_id, local_category_id, stream)
275
                VALUES (
276
                    (SELECT id FROM s_plugin_connect_categories WHERE category_key = ? AND shop_id = ?),
277
                    ?,
278
                    ?
279
                )',
280
                [$categoryKey, $shopId, $id, $stream]);
281
        }
282
283
        return $id;
284
    }
285
286
    /**
287
     * @param string $categoryName
288
     * @param string $categoryKey
289
     * @param int $parentId
290
     * @param int $shopId
291
     * @param string $stream
292
     * @return int
293
     */
294
    public function createLocalCategory($categoryName, $categoryKey, $parentId, $shopId, $stream)
295
    {
296
        $path = $this->manager->getConnection()->fetchColumn('SELECT `path` 
297
            FROM `s_categories`
298
            WHERE `id` = ?',
299
            [$parentId]);
300
        $suffix = ($path) ? "$parentId|" : "|$parentId|";
301
        $path = $path . $suffix;
302
        $now = new \DateTime('now');
303
        $timestamp = $now->format('Y-m-d H:i:s');
304
        $this->manager->getConnection()->executeQuery('INSERT INTO `s_categories` (`description`, `parent`, `path`, `active`, `added`, `changed`) 
305
            VALUES (?, ?, ?, 1, ?, ?)',
306
            [$categoryName, $parentId, $path, $timestamp, $timestamp]);
307
        $localCategoryId = $this->manager->getConnection()->fetchColumn('SELECT LAST_INSERT_ID()');
308
309
        $this->manager->getConnection()->executeQuery('INSERT INTO `s_categories_attributes` (`categoryID`, `connect_imported_category`) 
310
            VALUES (?, 1)',
311
            [$localCategoryId]);
312
313
        $remoteCategoryId = $this->manager->getConnection()->fetchColumn('SELECT `id` 
314
            FROM `s_plugin_connect_categories`
315
            WHERE `category_key` = ? AND `shop_id` = ?',
316
            [$categoryKey, $shopId]);
317
        $this->manager->getConnection()->executeQuery('INSERT INTO `s_plugin_connect_categories_to_local_categories` (`remote_category_id`, `local_category_id`, `stream`) 
318
            VALUES (?, ?, ?)',
319
            [$remoteCategoryId, $localCategoryId, $stream]);
320
321
        return $localCategoryId;
322
    }
323
324
    /**
325
     * @param $categoryId
326
     * @return int
327
     */
328
    private function countChildCategories($categoryId)
329
    {
330
        return (int) $this->manager->getConnection()->fetchColumn(
331
            'SELECT COUNT(id) FROM s_categories WHERE parent = ?',
332
            [$categoryId]
333
        );
334
    }
335
}
336