Let's ask the question clearly before entering into the details:
Is there a way of hard deleting an attribute value from a product?
By hard I mean, removing the row from the database and not only setting the value to null or empty.
Now, the details:
I'm currently confronted to a problem on Magento.
Many product in my store have user defined attributes that are not related to the product. For example, let's say we have a bag product, among other products like t-shirts,dresses,pants,etc.:
Our bag product has only configurable 1 attribute: color.
Our T-Shirt product has 2 configurable attributes: color and tshirt_size.
Our Dress product has 2 configurable attributes: color and dress_size
My current problem is that when I retrieve my Bag product from the database, the attributes stored in my product are: color, tshirt_size and dress_size.
I suspect that it is because on our store you can change the attribute set of a product (thanks to the FlagBit ChangeAttributeSet community extension).
I think that the bag product has been created with a specific attribute set, then someone changed the attribute set to another, and another again. That would have lead the product to get ALL the attributes from ALL the attribute sets it has been changed to. But that's just a though, and it could be another reason. I'm actually currently not looking for the specific reason that led my product to have unrelated attributes.
I today want to reset my products. By reset I mean removing the attributes unrelated to my product. In the case of my bag, that means removing the tshirt_size and dress_size attributes. And by removing I mean deleting forever these attributes from the object.
My problem is that, I can't find how to do it. I tried setting the attributes to NULL and saving the product, but when I query again the product, I still get the attribute in the datas, with a null value. I don't want the attribute value to be null, I want the attribute to not exist.
Here is a sample of code that explains a bit what I tried:
$product = Mage::getModel('catalog/product')->load(1234); //Let's assume that my bag product ID is 1234
Mage::log($product->getData());
/* This last line dump all my products datas and contains among other things:
[...]=>...
[color]=>3
[tshirt_size]=>34
[dress_size]=>45
[...]=>...
*/
If I do a:
$product->setData('tshirt_size',null);
$product->setData('dress_size',null);
$product->save();
then again:
$product = Mage::getModel('catalog/product')->load(1234);
Mage::log($product->getData());
/* I get:
[...]=>...
[color]=>3
[tshirt_size]=>null
[dress_size]=>null
[...]=>...
*/
I don't want that. I want:
/*
[...]=>...
[color]=>3
[...]=>...
*/
The fact that I still have the entries in my array mean that the actual rows in the database are not deleted. I want the rows to be deleted.
Even more frustrating, when I do a:
$product->getAttributes();
It returns me all the attributes that can be set to my product without the unrelated attributes -which makes sense because the unrelated attributes are not in the attribute set of my product.
So, again, the question is:
Is there a way of hard deleting an attribute value from a product?
By hard I mean, removing the row from the database and not only setting the value to null or empty.
Thanks for your help!
Hugues.
FYI: I'm using Magento 1.6.1.0 (but it does not really change anything)
I finally had to tackle this, and although #vBuck was close, I always was able to query the old data after the switch in the attribute set and if I switched back I would see the old data when it shouldn't have been there. Today, I have the same need to ensure the random data is removed. Here is how I approached this topic.
In order to programmatically delete attributes when you switch from one set to another you must first look at all the entries for the new set, then get a list of all attributes, check against that list and (this is the important part) remove it from the correct entity table. For example, I want to get rid of a value of 500 GB that is in the sample Magento data when I switch Western Digital 500GB HD - 7200RPM from Hard Drive to Shoes. In order to do that I must remove the entry in the catalog_product_entity_text table it's stored at. This means that I must find that entry where it's equal to the product id and then delete it from it's entity type table.
I was able to do this, and was not able to find the stray data after. This was confirmed by both a DB search and switching back to the old attribute set and looking for the data. you can find it all here
https://gist.github.com/jeremyBass/6581038#file-attribute-clean-switch-md
TESTED in CE 1.7.0.2, 1.8.0.0
So I too would love to have the programmatic answer, but I don't have the time to do it. But this is what I can provide to this chain. You see the desired resulting action happening when you try to remove an attribute by its self from the admin.
Steps:
make sure the attr set has the attr in it's set.
update all products with that attr set to have the attr needing deletion as null.
remove the the attr from the set
Seems when I don't clear all attrs before removal of attr from the set, the result is for the app to leave the rows there. Maybe for safety, I don't know as I have not dived in. What I can tell with as little as I have looked in this it’s that if you don't clear the values before attr removal from the set, when you set the attr to null, the attr remains.
So, then the programmatic solutions will need to follow that pattern. If I get time I'll write it, but I have like 15 other tasks in the queue right now.
Ok, I found it finally, 15 minutes after asking the question (and spending few hours trying to solve it)
Here is the answer:
$product = Mage::getModel('catalog/product')->load(1234); //Let's assume that my bag product ID is 1234
$product->setData('unwanted_attribute',null);
$resource = $product->getResource();
$resource->saveAttribute($product,'unwanted_attribute');
Edit: This doesn't work. If you re-save the product later by doing a $product->save(), all the attributes reappear.
Looks like this is an old post, but I too was looking for an answer here, and I think I'm onto a solution.
To solve this, I had to work directly with the product resource's write adapter:
$product->getResource()->getWriteConnection()
to delete the attribute values from the table.
I got clues on how to do this after digging through some code. See Mage_Catalog_Model_Resource_Eav_Mysql4_Abstract::_saveAttributeValue:
$write = $this->_getWriteAdapter();
$storeId = Mage::app()->getStore($object->getStoreId())->getId();
$table = $attribute->getBackend()->getTable();
/**
* If we work in single store mode all values should be saved just
* for default store id
* In this case we clear all not default values
*/
if (Mage::app()->isSingleStoreMode()) {
$storeId = $this->getDefaultStoreId();
$write->delete($table, join(' AND ', array(
$write->quoteInto('attribute_id=?', $attribute->getAttributeId()),
$write->quoteInto('entity_id=?', $object->getEntityId()),
$write->quoteInto('store_id<>?', $storeId)
)));
}
By using this code in a loop through $product->getAttributes() before changing the attribute set, you are able to "unlink" the values from the attributes of the product without destroying the attributes themselves. Then you can change the attribute set (as done in Flagbit's extension) safely, not leaving behind dead data.
Using the above code, here's a demonstration that I've written to prove my theory:
$product=Mage::getModel('catalog/product')->load(MY_TARGET_PRODUCT_ID);
$write=$product->getResource()->getWriteConnection();
foreach($product->getAttributes() as $attribute) {
if($attribute->getId()==MY_TARGET_ATTRIBUTE_ID) {
$write->delete(
$attribute->getBackend()->getTable(),
join(' AND ', array(
$write->quoteInto('attribute_id=?',$attribute->getAttributeId()),
$write->quoteInto('entity_id=?',$product->getId())
))
);
}
}
In this example, I'm looking for a specific attribute to clear from the product, but you can remove the IF conditional to delete every attached value.
I haven't fully tested this, but so far it seems alright. I'm running Magento 1.4, by the way. Hope it helps someone!
UPDATE:
Looks like the above is only half correct. You'll still have to be careful not to clear certain attributes that might render the product dead (such as the name, mode, status, and others). So in my case, it looks like I'll have to compare with the default attribute set before removing.
ANOTHER UPDATE:
With the help of this SO post: https://stackoverflow.com/a/8500506/1442685
I was able to compare all attributes linked to a product and delete only the non-default values. This way, when changing sets, I can ensure that basic information about the product is not lost. Check out my Gist for the two classes which make it all happen:
https://gist.github.com/vbuck/5911170
You should be able to unset the value like either of these:
$product->unsetData('unwanted_attribute')
->save();
$product->unsUnwantedAttribute()
->save();
you need to go to admin>>catalog>>attributes>>manage attribute sets
if your trying to manage your product catalog you should first think of your attributes (separate to your product), then your sets(again not actually part of your products).
after you have properly established your attributes and sets (of attributes) you can apply them to products with values for each attribute.
or maybe you prefer this kinda thing?
class Mage_catalog_Model_Product_Attribute_Set_Api extends Mage_Api_Model_Resource_Abstract
{
{
/**
* Retrieve attribute set list
*
* #return array
*/
public function items()
{
$entityType = Mage::getModel('catalog/product')->getResource()->getEntityType();
$collection = Mage::getResourceModel('eav/entity_attribute_set_collection')
->setEntityTypeFilter($entityType->getId());
$result = array();
foreach ($collection as $attributeSet) {
$result[] = array(
'set_id' => $attributeSet->getId(),
'name' => $attributeSet->getAttributeSetName()
);
}
return $result;
}
} // Class Mage_Catalog_Model_Product_Attribute_Set_Api End
Once you have associated an attribute set to a product there is no way to change attribute set or remove that attribute set.
The way out is to export product and then modify sheet and import.
This link also explains :
https://collaborate.magento.com/magento/topics/how_do_i_change_product_attribute_set
Related
Hello and good day to all the members of this great community. I'm still new in PHP and especially in Magento.
I'm not posting, waiting for answers, and leaving without replying back. This is a learning process. I hope to get a great support from all of you.
I have a product. I did create custom option for the product, that is an attribute named "a_size". The attribute has value of S, M and L. Each of the value has quantity.
In the single product view, I would like to call all the available size. That is the size (S, M, or L) that has quantity more than 0. I just want to show the available size, not how much the size left.
Can anybody guide me? I'm using Magento 1.7.x and as far for this 2 weeks, I did try pretty many of suggested answers from the community thru the search function.
The replies will be much appreciated. Thank you.
There are a few things to try.
Firstly check that when you set up your new attribute in the Magento Admin (Catalog->Attributes->Manage Attribute) that in the Frontend Properties box you have set Visible on Product View Page on Front-end to yes.
To get size values I use this code:
$cabac_sizeAttribute = $_product->getAttributeText("a_size");
but I have other code for getting attribute values that goes like this:
$_product_helper = Mage::helper('catalog/output');
$temp = $_product_helper->productAttribute($_product, $_product->getASize(), 'a_size');
I think it is related to the type of attribute: text, dropdown, multiselect etc so try both and see how you get on. But really the function productAttribute() is just applying formatting. You can read the function in the file app/core/Mage/Catalog/Helper/Output.php
Also, I wonder, if you have set up a configurable product and you are on the product view page then you will be viewing the configurable product. That product won't have an a_size value: you are trying to access the a_size attribute of the simple products that make up the configurable product, yes? Everything I wrote above is (I think) correct but to get the attribute of the simple products that are part of a configured product you should study the code in the function getJsonConfig() of the file app/core/Mage/Catalog/Block/Product/View/Type/Configurable.php
And in particular to these lines:
//file: file app/core/Mage/Catalog/Block/Product/View/Type/Configurable.php
//class: Mage_Catalog_Block_Product_View_Type_Configurable
//function: getJsonConfig()
foreach ($this->getAllowProducts() as $product) {
$productId = $product->getId();
foreach ($this->getAllowAttributes() as $attribute) {
$productAttribute = $attribute->getProductAttribute();
$productAttributeId = $productAttribute->getId();
$attributeValue = $product->getData($productAttribute->getAttributeCode());
Being careful about variable naming: $product is local here, I suggest changing it, and about $this - but if you are in a .phtml of the product view for configurables then I think your $this is already Mage_Catalog_Block_Product_View_Type_Configurable
Welcome to Magento coding. You are doing well; it is a long but rewarding path. (hints: local.xml is your vital friend and so is Alan Storm if you haven't come across his content yet.)
[Additionally, (welcome to Magento) I think you are trying to say eg S and L are out of stock and M is in stock but actually the function getAllowProducts() will disallow a product with zero stock and exclude it from the returned object. You will need to use
$allProducts = $this->getProduct()->getTypeInstance(true)
->getUsedProducts(null, $this->getProduct());
(taken from function getAllowProducts() in file app/core/Mage/Catalog/Block/Product/View/Type/Configurable.php)
and then, if needed, check that each product is allowed to be shown eg status=ENABLED, and then check its stock level...
]
Malachy.
If you want to get the values of your drop down attribute use the following code
$_product->getASize();
and initially load the product object
I need help with a certain custom extension I am building for Magento. I made an extension that would allow bundled product types to be associated with a "parent" bundle product.
So think of it as a I was selling a bundled product, "keyboard and mouse", that was bundled with a DESKTOP computer.
I have it working on the admin and I have it being displayed on the product view. However I am running into an issue when I am trying to add this "DESKTOP COMPUTER" to my shopping cart. I was able to trace it down to a function that called *_prepareProduct()* which is under /app/code/core/Mage/Bundle/Model/Product/Type.php.
Below is a snippet of the code that I am finding the problem. As you can see I have dumped out the selectionIds and that returns an array of what options I have selected from the product view page. The second dump is the selections->getItems (which I have no idea where this function is at, it wont let me focus it). However when I view this DUMP from a BUNDLE PRODUCT that only contains simple products (i.e., the bundle product that contains the keyboard and mouse) it will output data/object for selections->getItems(...). When I dump this from a bundled product that contains bundled products (i.e., the desktop computer that has the bundled product that contains the keyboard and mouse and other things ) it returns nothing for selections->getItems(...).
// If product has not been configured yet then $selections array should be empty
if (!empty($selectionIds)) {
$selections = $this->getSelectionsByIds($selectionIds, $product);
var_dump($selectionIds);
var_dump($selections->getItems());
// Check if added selections are still on sale
foreach ($selections->getItems() as $key => $selection) {
Can anyone help me understand "getSelectionsByIds" and how I can override it so it will not return an empty object for a bundled product when I add an item to my cart and/or help me understand getItems and how I can override that as well? I know how to override getSelectionsById but i don't understand what is causing the function to return nothing.
Thanks
getSelectionsById() is returning a Magento collection, which is a model used to contain other models (all of the same class). Think of it as a beefed-up array. You can read more about them here: Using Collections in Magento
Because of some PHP tricks, a Magento collection can, in many cases, be treated like an array. For example, the following code works:
$productCollection = Mage::getModel('catalog/product')->getCollection();
foreach ($productCollection as $product) {
// do stuff
}
Despite this, it's sometimes useful to get an actual PHP array from a collection. For this you use getItems(). It's a method available to any Magento collection (specifically, any object that inherits from Varien_Data_Collection). You shouldn't have any reason to override it. It simply returns the plain ol' PHP array that the collection object stores and uses internally. Nothing fancy, as you can see:
/**
* Retrieve collection items
*
* #return array
*/
public function getItems()
{
$this->load();
return $this->_items;
}
So if getItems() return null or an empty array for your collection, that means the collection contains no objects. This is often because a query applied to the collection returned an empty result set, although I'm not sure if that's applicable that is to your situation.
It's hard to say specifically why your code is failing to return a populated collection with the information you've given. It would be helpful if you could go into a bit more detail about how you've implemented your new product type--what classes you've created, how you're extending the core bundle type, etc.
today I'm fighting with Magento again :) and I found a difference between
$product = Mage::getModel('catalog/product')->loadByAttribute('sku', $product_sku);
and
$product = Mage::getModel('catalog/product')->load($product_id);
Can anyone exaplain me a difference between these two approaches? I found that when I'm loading a product by sku then when I 'try to re-save it with changed data then I get error exception 'Varien_Exception' with message 'Invalid method Varien_Object::save in app\code\core\Mage\CatalogInventory\Model\Observer.php(153): Varien_Object->__call('save', Array) that's true because once you try to load by sku then another observer sets product's stock item as Varien_Object, that looks like pitfall or I just dont understand it enough, but
I do daily Magento development from its beginnig so I know a lot about system and this is new for me. Thanks in advance, Jaro.
Interesting. While both methods will net you a single product model instance with fully loaded EAV data (provided the third parameter of loadByAttribute() is not passed or is *), the observers which add stock-related data are different for products vs. product collections, yet both stock information objects are added to the product data key "stock_item". It's debatable, but this feels like a bug. I would think that Mage_CatalogInventory_Model_Observer::saveInventoryData() or Mage_CatalogInventory_Model_Observer::_prepareItemForSave() would handle this.
You could resolve this issue by setting the product stock item fully on your product instance using the stock_item object.
loadByAttribute is a serious misnomer in my opinion because it doesn't actually trigger a load(); rather it uses getResourceCollection():
public function loadByAttribute($attribute, $value, $additionalAttributes = '*')
{
$collection = $this->getResourceCollection()
->addAttributeToSelect($additionalAttributes)
->addAttributeToFilter($attribute, $value)
->setPage(1,1);
Because it doesn't trigger the observer events associated with load() it means the resulting product object doesn't include the full set of product data you might want. In my case I needed the "description" attribute and it wasn't included.
There are several ways to resolve this:
Use a different method to load by SKU:
$product = Mage::getModel("catalog/product");
$product->load($product->getIdBySku("whatever"));
Force the desired attribute data to be included in the default product resource data by visiting Magento Admin > Catalog > Attributes > Edit attribute > "Used in Product Listing" = "Yes" and then reindexing. You should then be able to use the attribute data (in the frontend, at least) using loadByAttribute().
See also https://magento.stackexchange.com/a/197286/18855
My products all use numerical SKUs, but it seems to cause a problem when using the API to do product update.
According to the API doc, you can use either product ID or SKU.
Arguments:
mixed product - product ID or Sku
array productData - array of attributes values
mixed storeView - store view ID or code (optional)
But fully numerical SKUs don't seem to work.
I'm convinced there is some code somewhere which checks if the value is numerical and assumes I must be supplying the product ID.
I also read somewhere you can pass in a 4th parameter to specify you are using sku, but that didn't work either.
$proxy->call($sessionId, 'product.update', array('123456', array('name'=>'Updated name1'), null, 'sku') );
Does anyone know how to get this working?
Short answer is that there's a bug somewhere preventing the last param of product.update from being set properly (or maybe Varien haven't yet implemented it), which also presents a problem for the method product.info.
A quick workaround (if you don't mind losing the option to update by ID) is just to set the $identifierType in the Product API update() method ):
In app/code/core/Mage/Catalog/Model/Product/Api.php l.198
public function update($productId, $productData, $store = null, $identifierType = 'sku')
And finally load the product within the if ($idBySku) condition of the method getProduct() around l.427 of app/code/core/Mage/Catalog/Helper/Product.php
$productId = $idBySku;
$product->load($productId);
It's a bit of a fudge. I'll have a look for a better workaround as an override; otherwise, maybe someone else can post a better solution.
In a similar question TurmDrummer posted another workaround:
https://stackoverflow.com/a/10915276/1598270
There is a workaround for pure numeric or mixed SKU, that works quiet
well for me.
Just add a whitespace at the end of your SKU. Magento will interpret
the value as a SKU, because whitespace is non numeric. Internaly
Magento trims the whitespace later
This works perfectly from Magento 1.4.x - 1.7.
I liked this solution a bit better as a workaround because you aren't modifying any core code files, which I try to avoid.
I have written code that automatically imports products basically something like:
$product->setName('my name');
$product->save();
This is fine for free fill text boxes, but how would I go about setting say manufacturer, which is a drop down menu? Is there also a way that if the option doesnt exist, then it will automagically add it?
Thanks
This is tested in 1.5.0.1, you just need to target the correct attribute ID. As #B00MER stated, the attribute will not be created that you are targetting but if the attribute exist, this will create the options.
$eav_entity_setup = new Mage_Eav_Model_Entity_Setup('core_setup');
$new_option['attribute_id'] = $id;
$new_option['value']['_custom_'.$value][0] = $value;
$eav_entity_setup->addAttributeOption($new_option);
Documentation about addAttributeOption can be found here.
http://freegento.com/doc/d0/d7b/_eav_2_model_2_entity_2_setup_8php-source.html#l00603
You will need to create the functionality yourself unfortunately. And by default Magento will not 'automagically' create a option if one doesn't pre-exist.
To simply set the ID of the value you want (Say Sony was ID 12) you can do:
$product->setData('mfr', '12');
However you may find a lot more insight here on steps to do what your looking for:
http://www.arscommunity.com/wiki/magento/configurable-products-creation-code