I can't seem to figure out how to best update hundreds of category attributes with a script. Any suggestions as the best way to approach this? I won't be using the installer or anything like that.
Magento 1.9 PHP Script – Update Category Attributes Programmatically
categorymagento-1.9PHPscript
Related Solutions
Your initial code should be working; see:
<?php
include 'app/Mage.php';
Mage::app();
$obj = new Varien_Object(array('entity_id' => 10,'description'=>'test','store_id'=>0));
$resource = Mage::getModel('catalog/category')->getResource();
/* @var $resource Mage_Catalog_Model_Resource_Category */
$resource->saveAttribute($obj,'description');
So, that's the basic save approach for one attribute at a time.
You might want to create a category action resource, e.g. Mage_Catalog_Model_Resource_Category_Action
(drop it in local codepool to keep the kids from weeping), however, you'll need to adjust the updateAttributes
method to save different store scopes and attribute values per entity - this is different from the catalog action resource behavior.
<?php
/**
* Catalog Category Mass processing resource model
*
* @category Mage
* @package Mage_Catalog
* @author Ben Marks <ben@blueacorn.com>
*/
class Mage_Catalog_Model_Resource_Category_Action extends Mage_Catalog_Model_Resource_Abstract
{
/**
* Initialize connection
*
*/
protected function _construct()
{
$resource = Mage::getSingleton('core/resource');
$this->setType(Mage_Catalog_Model_Category::ENTITY) //note the change from the product action resource
->setConnection(
$resource->getConnection('catalog_read'),
$resource->getConnection('catalog_write')
);
}
/**
* Update entity attribute values individually by scope
*
* $dataArray = array(
* {attribute code} => array (
* array (
* 'entity_id' => {category id},
* 'value => {value},
* 'store_id' => {store id}
* ),
* array (
* //...
* )
* )
* );
*
* @param array
* @return Mage_Catalog_Model_Resource_Category_Action
* @throws Exception
*/
public function updateAttributes($dataArray)
{
$this->_getWriteAdapter()->beginTransaction();
try {
$i = 0;
foreach ($dataArray as $code => $entities) {
$attribute = $this->getAttribute($code);
if (!$attribute->getAttributeId()) {
continue;
}
foreach ($entities as $entity) {
$object = new Varien_Object();
$object->setIdFieldName('entity_id')
->setStoreId($entity['store_id']);
$object->setId($entity['entity_id']);
// collect data for save
$this->_saveAttributeValue($object, $attribute, $entity['value']);
}
}
$this->_processAttributeValues();
$this->_getWriteAdapter()->commit();
} catch (Exception $e) {
$this->_getWriteAdapter()->rollBack();
throw $e;
}
return $this;
}
}
Obviously you should change the array structure to whatever works for you. Test data & script for the above class & method are below, and will work for sample data.
<?php
include 'app/Mage.php';
Mage::app();
$action = Mage::getResourceModel('catalog/category_action');
$test = array(
'description' => array(
array (
'entity_id' => 10, //Furniture category
'value' => 'ONE',
'store_id' => 1 //English store scope
),
array (
'entity_id' => 10, //Furniture category
'value' => 'TWO',
'store_id' => 2 //German store scope
),
array (
'entity_id' => 22, //Living Room category
'value' => 'THREE',
'store_id' => 3 //French store scope
)
),
'meta_keywords' => array(
array (
'entity_id' => 22, //Living Room category
'value' => 'FOUR',
'store_id' => 1 //English store scope
)
)
);
$action->updateAttributes($test);
I was digging through this today and it's not easy to untangle. The way smart categories have been designed did not take into account any easy way for their creation programmatically via a migration script.
The merchandiser data is completely compartmentalized from the category model and there are observers which listen for category saves in the admin and kick off merchandiser handlers which ingest the POST data from the visual merchandiser tab.
The added complexity lies in the data being store as serialized arrays in the database with the keys having been generated by the Magento backend.
For instance:
Array
(
[0] => Array
(
[category_id] => 18
[heroproducts] =>
[attribute_codes] => product_type
[smart_attributes] => a:1:{s:18:"_1452698469143_143";a:3:{s:9:"attribute";s:3:"180";s:5:"value";s:4:"1190";s:4:"link";s:2:"OR";}}
[ruled_only] => 0
[automatic_sort] => none
)
)
... where smart_attributes
unserializes to...
Array
(
[_1452698469143_143] => Array
(
[attribute] => 180
[value] => 1190
[link] => OR
)
)
One (admitadly terrible) option might be to generate the smart category, and then use the data in a migration script that instantiates the merchandiser/merchandiser
resource model and inserts it via the insertCategoryValues
method. That's how the core handle it, at any rate (you can see it in action in:
// app/code/community/OnTap/Merchandiser/etc/config.xml
<catalog_category_save_after>
<observers>
<merchandiser_catalog_category_prepare_save>
<class>merchandiser/adminhtml_observer</class>
<method>categorySaveAfter</method>
</merchandiser_catalog_category_prepare_save>
</observers>
</catalog_category_save_after>
Which kicks off the categorySaveAfter
method where the configured VM settings are stored.
app/code/community/OnTap/Merchandiser/Model/Adminhtml/Observer::categorySaveAfter()
I have an email out to the OnTap team to see if they have any best-practice suggestions for handling the creation of VM rules for use in migration scripts. If they reply with anything useful, I'll post it here.
Best Answer
If you have many categories and want to update few attributes than you can use code similar to this below. It's fast and saves only values for these attributes which you want to update. You can run this script from browser or from terminal (terminal is better for cases like you described).