Skip to content

Magento 2 Custom Module – Adding Category IDs programatically

system.xml file

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="specials_subcategory" translate="label" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1">
            <class>separator-top</class>
            <label>Category Specials</label>
            <tab>ThreeYearOrders</tab>
            <resource>Gelmar_SpecialsSubCategory::config</resource>
            <group id="general" translate="label" type="text" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Settings</label>
                <field id="run_specials_logic" translate="label" type="button" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0">
                    <label>Update Category Specials</label>
                    <button class="action specials" type="button">
                        <span>Update</span>
                    </button>
                    <action>
                        <url path="gelmar/specials/run" />
                    </action>
                </field>
            </group>
        </section>
    </system>
</config>

This is my system.xml file. The settings label and button both display but the button has no text. I simply just want the button to say “Update”. Please advise or give suggestions.
Next I need help with my functionality. Below I have my Run.php file which is supposed to add the corresponding category ID to the product if it is in the specials category. So for example if I have a product that is in my Bathroom category and is on special it should add to the Bathroom specials category. I have double checked the IDs and they are all correct however it does not seem to be working.

Run.php file

<?php

namespace GelmarSpecialsSubCategoryControllerAdminhtmlSpecials;

use MagentoBackendAppAction;
use MagentoCatalogModelResourceModelProductCollectionFactory as ProductCollectionFactory;
use MagentoCatalogApiCategoryLinkManagementInterface;
use MagentoFrameworkControllerResultFactory;

class Run extends Action
{
    protected $productCollectionFactory;
    protected $categoryLinkManagement;

    public function __construct(
        ActionContext $context,
        ProductCollectionFactory $productCollectionFactory,
        CategoryLinkManagementInterface $categoryLinkManagement
    ) {
        parent::__construct($context);
        $this->productCollectionFactory = $productCollectionFactory;
        $this->categoryLinkManagement = $categoryLinkManagement;
    }

    public function execute()
    {
        // Category IDs
        $specialsCategoryId = 206;
        $mapping = [
            249 => 259,
            5   => 260,
            21  => 261,
            19  => 262,
            24  => 263,
            28  => 264,
            53  => 265
        ];

        // Get all products in the Specials category (206)
        $productCollection = $this->productCollectionFactory->create()
            ->addCategoriesFilter(['in' => $specialsCategoryId]);

        // Consider adding pagination or limiting the collection size
        // $productCollection->setPageSize(100)->setCurPage(1);

        // Loop through the products
        foreach ($productCollection as $product) {
            $categoryIds = $product->getCategoryIds();

            // Use array_intersect() for better performance
            $categoriesToAdd = array_intersect(array_keys($mapping), $categoryIds);
            $newCategoryIds = array_merge($categoryIds, array_map(function($id) use ($mapping) {
                return $mapping[$id];
            }, $categoriesToAdd));

            // Ensure no duplicates
            $newCategoryIds = array_unique($newCategoryIds);

            // Only update if categories have changed
            if ($newCategoryIds !== $categoryIds) {
                try {
                    $this->categoryLinkManagement->assignProductToCategories($product->getSku(), $newCategoryIds);
                } catch (Exception $e) {
                    // Log the error or handle it as needed
                    $this->messageManager->addErrorMessage(__('Error assigning categories to product %1: %2', $product->getSku(), $e->getMessage()));
                }
            }
        }

        // Display success message in the admin
        $this->messageManager->addSuccessMessage(__('Specials category update completed successfully.'));

        // Redirect back to the config page
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
        $resultRedirect->setPath('adminhtml/system_config/edit/section/specials_subcategory');
        return $resultRedirect;
    }
}

I am not sure if my Run.php file is incorrect or if my system.xml file is incorrect. Basically what I want to happen is to be able to click a button on the admin side and then it updates the products that are on special to add the necessary category IDs such as Bathroom specials and then has a message to say all products have been updated. Please advise and give suggestions