├── .github
├── CODE_OF_CONDUCT.md
└── FUNDING.yml
├── Controller
└── Adminhtml
│ ├── Export
│ ├── Category.php
│ ├── CategoryDownload.php
│ └── CategoryPost.php
│ └── Import
│ ├── Category.php
│ └── CategoryPost.php
├── LICENSE
├── Model
├── Config
│ ├── ExcludedFields.php
│ └── Source
│ │ └── Category
│ │ ├── Attributes.php
│ │ └── VisibleAttributes.php
├── Csv
│ └── Options.php
├── DataProvider.php
├── Export
│ ├── Categories.php
│ └── ToCsv.php
├── Import
│ ├── Categories.php
│ └── FromCsv.php
├── Session
│ └── DownloadContext.php
└── Utils.php
├── README.md
├── Setup
└── Patch
│ └── Data
│ ├── AddCategoryCodeAttributeV1.php
│ └── PopulateCategoryCodeV1.php
├── Ui
└── Component
│ └── Form
│ └── Button
│ └── Download.php
├── composer.json
├── etc
├── acl.xml
├── adminhtml
│ ├── menu.xml
│ ├── routes.xml
│ └── system.xml
├── config.xml
└── module.xml
├── registration.php
└── view
└── adminhtml
├── layout
├── adminhtml_export_category.xml
└── adminhtml_import_category.xml
└── ui_component
├── category_form.xml
├── export_category_form.xml
└── import_category_form.xml
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at opengento@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | #github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | #patreon: # Replace with a single Patreon username
5 | #open_collective: # Replace with a single Open Collective username
6 | #ko_fi: # Replace with a single Ko-fi username
7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | #liberapay: # Replace with a single Liberapay username
10 | #issuehunt: # Replace with a single IssueHunt username
11 | #otechie: # Replace with a single Otechie username
12 | custom: ['https://www.helloasso.com/associations/opengento/formulaires/1']
13 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/Export/Category.php:
--------------------------------------------------------------------------------
1 | resultFactory->create(ResultFactory::TYPE_PAGE);
23 | $page->getConfig()->getTitle()->set(new Phrase('Export Categories'));
24 |
25 | return $page;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/Export/CategoryDownload.php:
--------------------------------------------------------------------------------
1 | downloadContext->getFile();
35 | if ($file) {
36 | $this->downloadContext->clearFile();
37 | try {
38 | return $this->fileFactory->create(
39 | basename($file),
40 | ['type' => 'filename', 'value' => $file],
41 | DirectoryList::VAR_IMPORT_EXPORT
42 | );
43 | } catch (Exception $e) {
44 | $this->messageManager->addExceptionMessage($e, new Phrase('Something went wrong while downloading the file.'));
45 | }
46 | }
47 |
48 | return $this->resultRedirectFactory->create()->setPath('*/*/category');
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/Export/CategoryPost.php:
--------------------------------------------------------------------------------
1 | downloadContext->setFile(
38 | $this->toCsv->execute(
39 | $this->resolveStoreIds(),
40 | $this->resolveAttributes(),
41 | Options::createFromRequest($this->getRequest())
42 | )
43 | );
44 |
45 | $this->messageManager->addSuccessMessage(new Phrase('The export file is now ready for download.'));
46 | } catch (LocalizedException $e) {
47 | $this->messageManager->addErrorMessage($e, $e->getMessage());
48 | } catch (Exception $e) {
49 | $this->messageManager->addExceptionMessage($e, new Phrase('Something went wrong while exporting the data.'));
50 | }
51 |
52 | return $this->resultRedirectFactory->create()->setPath('*/*/category');
53 | }
54 |
55 | private function resolveStoreIds(): array
56 | {
57 | return (array)$this->getRequest()->getParam('store_ids', [Store::DEFAULT_STORE_ID]);
58 | }
59 |
60 | /**
61 | * @throws InputException
62 | */
63 | private function resolveAttributes(): array
64 | {
65 | return (array)$this->getRequest()->getParam('attributes') ?: throw InputException::requiredField('attributes');
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/Import/Category.php:
--------------------------------------------------------------------------------
1 | resultFactory->create(ResultFactory::TYPE_PAGE);
23 | $page->getConfig()->getTitle()->set(new Phrase('Import Categories'));
24 |
25 | return $page;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/Import/CategoryPost.php:
--------------------------------------------------------------------------------
1 | fromCsv->execute(
39 | $this->uploadFile('import-' . time() . '-categories.csv'),
40 | Options::createFromRequest($this->getRequest())
41 | );
42 | $this->messageManager->addSuccessMessage(new Phrase('Import successful!'));
43 | } catch (LocalizedException $e) {
44 | $this->messageManager->addErrorMessage($e->getMessage());
45 | } catch (Exception $e) {
46 | $this->messageManager->addExceptionMessage($e, new Phrase('Something went wrong while uploading the file.'));
47 | }
48 |
49 | return $this->resultRedirectFactory->create()->setPath('*/*/category');
50 | }
51 |
52 | /**
53 | * @throws Exception
54 | */
55 | private function uploadFile(string $fileName): string
56 | {
57 | $directoryRead = $this->filesystem->getDirectoryRead(DirectoryList::VAR_IMPORT_EXPORT);
58 | $uploader = $this->uploaderFactory->create(['fileId' => 'file']);
59 | $uploader->setAllowCreateFolders(true);
60 | $uploader->setAllowRenameFiles(true);
61 | $uploader->setFilesDispersion(false);
62 | $uploader->setFilenamesCaseSensitivity(true);
63 | $uploader->setAllowedExtensions(['csv']);
64 | $uploader->setAllowRenameFiles(true);
65 | $result = $uploader->save($directoryRead->getAbsolutePath('import'), $fileName);
66 | if ($result === false) {
67 | throw new LocalizedException(new Phrase('The uploaded file could not be saved.'));
68 | }
69 |
70 | return $directoryRead->getAbsolutePath($result['path'] . '/' . $result['file']);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) OpenGento
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Model/Config/ExcludedFields.php:
--------------------------------------------------------------------------------
1 | excludedAttributes ??= array_unique(array_merge(
35 | ['category_code'],
36 | $this->resolveInvisibleAttributes(),
37 | explode(',', $this->scopeConfig->getValue(self::CONFIG_PATH_EXCLUDE_FIELDS) ?? ''),
38 | explode(',', $this->scopeConfig->getValue(self::CONFIG_PATH_EXCLUDE_ATTRIBUTES) ?? '')
39 | ));
40 | }
41 |
42 | private function resolveInvisibleAttributes(): array
43 | {
44 | $collection = $this->collectionFactory->create();
45 | $collection->setEntityTypeFilter($this->config->getEntityType(Category::ENTITY));
46 | $collection->addFieldToSelect('attribute_code');
47 | $collection->joinLeft(
48 | ['cea' => 'catalog_eav_attribute'],
49 | 'main_table.attribute_id = cea.attribute_id',
50 | ['']
51 | );
52 | $collection->addFieldToFilter('cea.is_visible', 0);
53 |
54 | return $collection->getColumnValues('attribute_code');
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Model/Config/Source/Category/Attributes.php:
--------------------------------------------------------------------------------
1 | options ??= $this->resolveAttributes();
37 | }
38 |
39 | /**
40 | * @throws LocalizedException
41 | */
42 | private function resolveAttributes(): array
43 | {
44 | $options = [];
45 | /** @var Attribute $attribute */
46 | foreach ($this->createAttributeCollection()->getItems() as $attribute) {
47 | $options[] = [
48 | 'label' => sprintf('%s (%s)', $attribute->getDefaultFrontendLabel(), $attribute->getAttributeCode()),
49 | 'value' => $attribute->getAttributeCode()
50 | ];
51 | }
52 |
53 | return $options;
54 | }
55 |
56 | /**
57 | * @throws LocalizedException
58 | */
59 | private function createAttributeCollection(): Collection
60 | {
61 | $collection = $this->collectionFactory->create();
62 | $collection->setEntityTypeFilter($this->config->getEntityType(Category::ENTITY));
63 | $collection->addFieldToSelect(['attribute_code', 'frontend_label']);
64 | $collection->addFieldToFilter('attribute_code', ['nin' => $this->excludedFields->get()]);
65 | $collection->setOrder('frontend_label', 'ASC');
66 | $collection->setOrder('attribute_code', 'ASC');
67 |
68 | return $collection;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Model/Config/Source/Category/VisibleAttributes.php:
--------------------------------------------------------------------------------
1 | options ??= $this->resolveAttributes();
35 | }
36 |
37 | /**
38 | * @throws LocalizedException
39 | */
40 | private function resolveAttributes(): array
41 | {
42 | $options = [];
43 | /** @var Attribute $attribute */
44 | foreach ($this->createVisibleAttributeCollection()->getItems() as $attribute) {
45 | $options[] = [
46 | 'label' => sprintf('%s (%s)', $attribute->getDefaultFrontendLabel(), $attribute->getAttributeCode()),
47 | 'value' => $attribute->getAttributeCode()
48 | ];
49 | }
50 |
51 | return $options;
52 | }
53 |
54 | /**
55 | * @throws LocalizedException
56 | */
57 | private function createVisibleAttributeCollection(): Collection
58 | {
59 | $collection = $this->collectionFactory->create();
60 | $collection->setEntityTypeFilter($this->config->getEntityType(Category::ENTITY));
61 | $collection->addFieldToSelect(['attribute_code', 'frontend_label']);
62 | $collection->joinLeft(
63 | ['cea' => 'catalog_eav_attribute'],
64 | 'main_table.attribute_id = cea.attribute_id',
65 | ['']
66 | );
67 | $collection->addFieldToFilter('cea.is_visible', 1);
68 | $collection->addFieldToFilter('attribute_code', 'category_code');
69 | $collection->setOrder('frontend_label', 'ASC');
70 | $collection->setOrder('attribute_code', 'ASC');
71 |
72 | return $collection;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Model/Csv/Options.php:
--------------------------------------------------------------------------------
1 | delimiter) !== 1) {
26 | throw new InputException(
27 | new Phrase(
28 | 'Invalid value of "%1" provided for the %2 field. Expected a single valid character.',
29 | ['delimiter', $this->delimiter]
30 | )
31 | );
32 | }
33 | if (strlen($this->enclosure) !== 1) {
34 | throw new InputException(
35 | new Phrase(
36 | 'Invalid value of "%1" provided for the %2 field. Expected a single valid character.',
37 | ['enclosure', $this->enclosure]
38 | )
39 | );
40 | }
41 | }
42 |
43 | /**
44 | * @throws InputException
45 | */
46 | public static function createFromRequest(RequestInterface $request): Options
47 | {
48 | return new Options((string)$request->getParam('delimiter'), (string)$request->getParam('enclosure'));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Model/DataProvider.php:
--------------------------------------------------------------------------------
1 | collectionFactory->create();
37 | $collection->setStoreId($storeId);
38 | $collection->setProductStoreId($storeId);
39 | $collection->setLoadProductCount(false);
40 | $collection->addAttributeToSelect($attributes);
41 | $collection->addAttributeToFilter('parent_id', ['neq' => 0]);
42 |
43 | $export = [];
44 | /** @var Category $category */
45 | foreach ($collection->getItems() as $category) {
46 | $row = $this->utils->sanitizeData($category->toArray($attributes));
47 | $row['store'] = $this->storeManager->getStore($storeId)->getCode();
48 | $row['parent_code'] = $category->getParentCategory()->getData('category_code');
49 | $export[] = $row;
50 | }
51 |
52 | return $export;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Model/Export/ToCsv.php:
--------------------------------------------------------------------------------
1 | filesystem->getDirectoryWrite(DirectoryList::VAR_IMPORT_EXPORT);
41 | $directoryWrite->create('export');
42 | $fileName = 'export/' . time() . '-categories.csv';
43 |
44 | $batch = [];
45 | foreach ($storeIds as $storeId) {
46 | $batch[] = $this->categories->execute((int)$storeId, $attributes);
47 | }
48 | $data = array_merge([], ...$batch);
49 | array_unshift($data, array_keys($data[0] ?? []));
50 |
51 | $this->csv->setDelimiter($options->delimiter);
52 | if ($options->enclosure !== null) {
53 | $this->csv->setEnclosure($options->enclosure);
54 | }
55 | $this->csv->appendData($directoryWrite->getAbsolutePath($fileName), $data);
56 |
57 | return $directoryWrite->getRelativePath($fileName);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Model/Import/Categories.php:
--------------------------------------------------------------------------------
1 | batchByStore($data) as $storeId => $rows) {
42 | $this->processStore($storeId, $rows);
43 | }
44 | }
45 |
46 | /**
47 | * @throws NoSuchEntityException
48 | * @throws CouldNotSaveException
49 | * @throws LocalizedException
50 | */
51 | private function processStore(int $storeId, array $rows): void
52 | {
53 | $currentStore = $this->storeManager->getStore();
54 | $this->storeManager->setCurrentStore($storeId);
55 | $collection = $this->createCollection($storeId, array_column($rows, 'category_code'));
56 |
57 | try {
58 | foreach ($rows as $row) {
59 | $category = $collection->getItemByColumnValue('category_code', $row['category_code'])
60 | ?? $this->categoryFactory->create();
61 | $category->addData($row);
62 | $category->setStoreId($storeId);
63 | if (isset($row['parent_code'])) {
64 | $category = $this->updateParent($category, $row['parent_code']);
65 | }
66 | $this->categoryRepository->save($category);
67 | }
68 | } catch (LocalizedException $e) {
69 | throw new LocalizedException(
70 | new Phrase(
71 | 'An error occurred while processing the category with code "%1" with the store "%2". The error is: %3',
72 | [
73 | $row['category_code'],
74 | $this->storeManager->getStore($storeId)->getName(),
75 | $e->getMessage()]
76 | )
77 | );
78 | } finally {
79 | $this->storeManager->setCurrentStore($currentStore);
80 | }
81 | }
82 |
83 | /**
84 | * @throws NoSuchEntityException
85 | * @throws InputException
86 | */
87 | private function batchByStore(array $data): array
88 | {
89 | $batch = [];
90 | foreach ($data as $row) {
91 | $batch[$this->storeManager->getStore($row['store'] ?? 'admin')->getId()][] = $this->utils->sanitizeData($row);
92 | }
93 |
94 | return $batch;
95 | }
96 |
97 | private function createCollection(int $storeId, array $codes): Collection
98 | {
99 | $collection = $this->collectionFactory->create();
100 | $collection->setStoreId($storeId);
101 | $collection->setProductStoreId($storeId);
102 | $collection->setLoadProductCount(false);
103 | $collection->addFieldToFilter('category_code', ['in' => $codes]);
104 |
105 | return $collection;
106 | }
107 |
108 | /**
109 | * @throws LocalizedException
110 | */
111 | private function updateParent(Category $category, string $parentCode): Category
112 | {
113 | $collection = $this->collectionFactory->create();
114 | $collection->addAttributeToSelect('path');
115 | $collection->setLoadProductCount(false);
116 | $collection->addAttributeToFilter('category_code', $parentCode);
117 |
118 | /** @var Category $parentCategory */
119 | $parentCategory = $collection->getFirstItem();
120 | $parentId = (int)$parentCategory->getId();
121 | if ($parentId) {
122 | if ($category->getId()) {
123 | if ($parentId !== (int)$category->getParentId()) {
124 | $category->move($parentId, null);
125 | }
126 | } else {
127 | $category->setParentId($parentId);
128 | }
129 | }
130 |
131 | return $category;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/Model/Import/FromCsv.php:
--------------------------------------------------------------------------------
1 | csv->setDelimiter($options->delimiter);
39 | if ($options->enclosure !== null) {
40 | $this->csv->setEnclosure($options->enclosure);
41 | }
42 |
43 | $rows = $this->csv->getData($filePath);
44 | $keys = array_map(static fn (string $key): string => preg_replace('/[^\w]/', '', $key), array_shift($rows));
45 | $keysCount = count($keys);
46 |
47 | $data = [];
48 | foreach ($rows as $row) {
49 | if ($keysCount !== count($row)) {
50 | throw new InputException(
51 | new Phrase('The number of column does not match the keys. Please verify the field separator.')
52 | );
53 | }
54 | $data[] = array_combine($keys, $row);
55 | }
56 |
57 | $this->categories->execute($data);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Model/Session/DownloadContext.php:
--------------------------------------------------------------------------------
1 | dataPersistor->get(self::FILE_KEY);
21 | }
22 |
23 | public function setFile(string $file): void
24 | {
25 | $this->dataPersistor->set(self::FILE_KEY, $file);
26 | }
27 |
28 | public function clearFile(): void
29 | {
30 | $this->dataPersistor->clear(self::FILE_KEY);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Model/Utils.php:
--------------------------------------------------------------------------------
1 | excludedFields->get()));
27 |
28 | return ['category_code' => $categoryCode] + $data;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Category Import Export Module for Magento 2
2 |
3 | [](https://packagist.org/packages/opengento/module-category-import-export)
4 | [](./LICENSE)
5 | [](https://packagist.org/packages/opengento/module-category-import-export/stats)
6 | [](https://packagist.org/packages/opengento/module-category-import-export/stats)
7 |
8 | This module add the many countries to many stores relation and make it available to the storefront.
9 |
10 | - [Setup](#setup)
11 | - [Composer installation](#composer-installation)
12 | - [Setup the module](#setup-the-module)
13 | - [Features](#features)
14 | - [Settings](#settings)
15 | - [Documentation](#documentation)
16 | - [Support](#support)
17 | - [Authors](#authors)
18 | - [License](#license)
19 |
20 | ## Setup
21 |
22 | Magento 2 Open Source or Commerce edition is required.
23 |
24 | ### Composer installation
25 |
26 | Run the following composer command:
27 |
28 | ```
29 | composer require opengento/module-category-import-export
30 | ```
31 |
32 | ### Setup the module
33 |
34 | Run the following magento command:
35 |
36 | ```
37 | bin/magento setup:upgrade
38 | ```
39 |
40 | **If you are in production mode, do not forget to recompile and redeploy the static resources.**
41 |
42 | ## Features
43 |
44 | - You can import categories from System > Data Transfer > Import Categories
45 | - You can export categories from System > Data Transfer > Export Categories
46 |
47 | ## Documentation
48 |
49 | - The available format for import/export is CSV
50 | - A new attribute category attribute is added with the module: `category_code` which allows to identify the categories.
51 |
52 | ## Support
53 |
54 | Raise a new [request](https://github.com/opengento/magento2-category-import-export/issues) to the issue tracker.
55 |
56 | ## Authors
57 |
58 | - **Opengento Community** - *Lead* - [](https://twitter.com/opengento)
59 | - **Thomas Klein** - *Maintainer* - [](https://github.com/thomas-kl1)
60 | - **Contributors** - *Contributor* - [](https://github.com/opengento/magento2-category-import-export/graphs/contributors)
61 |
62 | ## License
63 |
64 | This project is licensed under the MIT License - see the [LICENSE](./LICENSE) details.
65 |
66 | ***That's all folks!***
67 |
--------------------------------------------------------------------------------
/Setup/Patch/Data/AddCategoryCodeAttributeV1.php:
--------------------------------------------------------------------------------
1 | eavSetupFactory->create(['setup' => $this->setup]);
38 |
39 | $eavSetup->addAttribute(
40 | Category::ENTITY,
41 | 'category_code',
42 | [
43 | 'type' => 'varchar',
44 | 'label' => 'Category Code',
45 | 'input' => 'text',
46 | 'source' => '',
47 | 'visible' => true,
48 | 'default' => null,
49 | 'required' => true,
50 | 'global' => ScopedAttributeInterface::SCOPE_GLOBAL,
51 | 'user_defined' => false,
52 | 'group' => '',
53 | 'backend' => ''
54 | ]
55 | );
56 |
57 | return $this;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Setup/Patch/Data/PopulateCategoryCodeV1.php:
--------------------------------------------------------------------------------
1 | collecionFactory->create();
45 | $categories = $collection->addAttributeToSelect(['entity_id', 'name', 'category_code', 'path'])->getItems();
46 |
47 | $idsToName = [];
48 | /** @var Category $category */
49 | foreach ($categories as $category) {
50 | $idsToName[$category->getId()] = $category->getName();
51 | }
52 |
53 | /** @var Category $category */
54 | foreach ($categories as $category) {
55 | if (!$category->getData('category_code')) {
56 | $code = [];
57 | foreach ($category->getPathIds() as $pathId) {
58 | if ($pathId === '') {
59 | throw new LocalizedException(
60 | new Phrase(
61 | 'Category "%1" has an invalid path: %2.',
62 | [$category->getName(), $category->getPath()]
63 | )
64 | );
65 | }
66 | $code[] = $idsToName[$pathId];
67 | }
68 |
69 | $category->setCustomAttribute('category_code', strtolower(str_replace(' ', '_', implode('_', $code))));
70 | $this->resourceCategory->saveAttribute($category, 'category_code');
71 | }
72 | }
73 |
74 | return $this;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Ui/Component/Form/Button/Download.php:
--------------------------------------------------------------------------------
1 | downloadContext->getFile()
27 | ? [
28 | 'label' => new Phrase('Download'),
29 | 'on_click' => sprintf("location.href = '%s';", $this->urlBuilder->getUrl('*/*/categoryDownload')),
30 | 'class' => 'download primary'
31 | ]
32 | : [];
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "opengento/module-category-import-export",
3 | "description": "This module add the capability to import and export the categories from the back-office.",
4 | "keywords": [
5 | "php",
6 | "magento",
7 | "magento2",
8 | "import",
9 | "export",
10 | "category"
11 | ],
12 | "require": {
13 | "php": "^8.1",
14 | "magento/framework": "*",
15 | "magento/module-backend": "*",
16 | "magento/module-ui": "*",
17 | "magento/module-eav": "*",
18 | "magento/module-store": "*",
19 | "magento/module-catalog": "*"
20 | },
21 | "require-dev": {
22 | "magento/magento-coding-standard": "^33",
23 | "roave/security-advisories": "dev-latest"
24 | },
25 | "suggest": {},
26 | "type": "magento2-module",
27 | "license": [
28 | "MIT"
29 | ],
30 | "homepage": "https://github.com/opengento/magento2-category-import-export",
31 | "authors": [
32 | {
33 | "name": "Opengento Team",
34 | "email": "opengento@gmail.com",
35 | "homepage": "https://opengento.fr/",
36 | "role": "lead"
37 | },
38 | {
39 | "name": "Thomas Klein",
40 | "email": "thomaskein876@gmail.com",
41 | "homepage": "https://www.linkedin.com/in/thomas-klein/",
42 | "role": "maintainer"
43 | }
44 | ],
45 | "support": {
46 | "source": "https://github.com/opengento/magento2-category-import-export",
47 | "issues": "https://github.com/opengento/magento2-category-import-export/issues"
48 | },
49 | "autoload": {
50 | "files": [
51 | "registration.php"
52 | ],
53 | "psr-4": {
54 | "Opengento\\CategoryImportExport\\": ""
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/etc/acl.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |