Here's how I did it using some quick array management functions from newer versions of PHP:
<?php require_once '../../app/Mage.php'; Mage::app(); Mage::app()->setCurrentStore(Mage::getModel('core/store')->load(Mage_Core_Model_App::ADMIN_STORE_ID)); $storeCode = Mage::app()->getStore()->getStoreId(); function addProductsToCategoryId($mergeProductIds, $categoryId, $storeCode) { // load the $category by $categoryId $category = Mage::getModel('catalog/category')->setStoreId($storeCode)->load($categoryId); // build a flipped array of two merged arrays (1) array keys from flipped $mergeProductIds, (2) array keys from product_id keyed array in $category $categoryProductIds = array_flip(array_merge(array_keys(array_flip($mergeProductIds)),array_keys($category->getProductsPosition()))); // combine array_keys from resulting merge with a matched index array filled with '0' // THIS resets position of product within category, change this logic if desired $categoryProductIds = array_combine(array_keys($categoryProductIds), array_fill(0, count($categoryProductIds), '0')); $category->setPostedProducts($categoryProductIds); $category->save(); // optional // return $categoryProductIds; } // optional array of category IDs to test against for nin (not in) or in a find_in_set array test // in the optional example line below, nin (not in) is used $categoryIds = array(5,8,9,10,11,12,45,46); $collectionIds = Mage::getModel('catalog/product')->getCollection() ->setStoreId($storeCode) // optional inclusion of join for category_id ->joinField('category_id', 'catalog/category_product', 'category_id', 'product_id = entity_id', null, 'left') // optional logic to only gather ids that are, or are not in a given categoryIds array, nin (not in) is shown in example // ->addAttributeToFilter('category_id', array('nin' => array('finset' => $categoryIds))) // optional line to test whether product is associated to ANY category ->addAttributeToFilter('category_id', array('null' => true)) // example qualifiers to affect gathered IDs ->addAttributeToFilter('sku', array('like' => 'MH%')) ->addAttributeToFilter('sku', array('nlike' => '%E')) ->addAttributeToFilter('sku', array('nlike' => '%E#')) ->addAttributeToFilter('sku', array('nlike' => '%Euro')) ->addAttributeToFilter('sku', array('nlike' => '%Euro#')) ->getAllIds() ; // if using a return value, you can set the results of this to a variable // to perform further operations against the resulting data addProductsToCategoryId($collectionIds, 8, $storeCode);
Please note: by default, my method does NOT save the position for products within the categories that you installed. IT WILL will return all positions by default to "0".
It works great. After this, re-scanning is required, adding fast mass. The code is a bit complicated, so explaining the code in direct contextual comments was more reasonable to me in this case.
I included a lot of additional options in it, but all of them are marked as such and fully explained.