Completed
Push — master ( 512122...851336 )
by Tobias
18s
created

CategoryResolver::createLocalCategory()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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