This is useful for loading a product’s gallery on the something like the listing page.
$product->getResource()->getAttribute('media_gallery')->getBackend()->afterLoad($product);
This is useful for loading a product’s gallery on the something like the listing page.
$product->getResource()->getAttribute('media_gallery')->getBackend()->afterLoad($product);
Somewhere along the way in a 1.9.x update, configurable attribute sorting stopped working on the fronted. Magento should use an attribute’s position parameter (in the options tab when editing a product attribute), to order the dropdown on a product detail page.
The issue appears to be where Magento assigns prices to an attribute collection in Mage_Catalog_Model_Resource_Product_Type_Configurable_Attribute_Collection
Magento first gets the options for the attribute here:
$options = $productAttribute->getFrontend()->getSelectOptions();
At this point, all is good. In Magento’s SQL to get the options, sort_order is used correctly and the array of options is in the correct order.
The problem seems to occur in the loop on line 257:
foreach ($this->getProduct()->getTypeInstance(true) ->getUsedProducts(array($productAttribute->getAttributeCode()), $this->getProduct()) as $associatedProduct) { ... }
The order of the attributes will now be determined by the order of items in this loop, which is the order of associated products.
What we need to do is re-order the $values array which is populated in the above loop, we can do this using the order of the $options array:
$order = 0; $optionsByValue = array(); foreach ($options as $option) { $optionsByValue[$option['value']] = $option['label']; $orders[$option['value']] = $order; $order++; }
Here we utilise the existing loop which populates the $optionsByValue array to log the ordering of each option. We can then use those values by passing them into our custom sort method just before Magento adds the prices to each item:
/* Resort the values array using the order we've logged against options */ usort($values, array($this, 'sortValues')); foreach ($values as $data) { $this->getItemById($data['product_super_attribute_id'])->addPrice($data); }
Here’s the sort method which puts the array back to the correct sort order:
public function sortValues($value1, $value2){ if(isset($value1['sort_order']) && isset($value2['sort_order'])){ if($value1['sort_order'] > $value2['sort_order']){ return 1; } if($value1['sort_order'] < $value2['sort_order']) { return -1; } return 0; } }
Go to Catalog > Attributes and create the attribute to be used to create the configurable product. This would be typically something like “Colour”. Make sure “Use to create configurable product” is set to “Yes”.
In “Manage Label / Options” add the options for the swatch, E.g. “Red”, “Yellow”, “Blue”. Magento will use these values when looking for the image files for the swatches.
Go to System > Configuration > Configurable Swatches and set “Enabled” to “Yes”, and select the newly created attribute to be used as a swatch. Set any of the swatch dimensions required if required.
Create the configurable product, and add associated products to it using the attribute we’ve created above. You should now find that when you view your product you get text links output as the colour swatches. This is because Magento currently can’t find any images to use for the colour swatch itself.
There are two ways to get Magento to show images as swatches. The first is to add global swatches which are simply images which are placed in Magento’s **Media/wysiwyg/swatches/” directory using the option label as the filename. So, for example, you may have “blue.png”, “red.png” etc. The extension for image files is PNG.
The second, and this will supersede the first, will allow the setting of a configurable swatch on the product level. To do this, upload images to the configurable product, and then set image’s label field to the attribute’s option value with the suffix “-swatch”, E.g. “blue-swatch”. This image will now be resized and used as the image for that option.
Magento decides whether to use a renderer instead of simply outputting configurable options as a select element in catalog/product/view/type/configurable.phtml:
Using this method, we can add other renderers to the product.info.options.configurable.renderers block, implement the shouldRender method, and decide whether the use that renderer based on the attribute we’re passed in.
When creating a new attribute, we can specify a default value to be saved alongside that attribute. However, these values do not propagate to categories or products which already exist. We can, however use load_after events to remedy this.
Confix XML
<global> <events> <catalog_category_load_after> <observers> <observer_name> <type>singleton</type> <class>module/observer</class> <method>setDefaults</method> </observer_name> </observers> </catalog_category_load_after> </events> </global>
Note: To set the default of a category use catalog_product_load_after instead
<?php class Namespace_Module_Model_Observer { public function setDefaults($observer) { $category = $observer->getCategory(); if($category->getShowSizeRollover() === NULL){ $category->setShowSizeRollover(true); } } }
In this example we set the attribute ‘show_size_rollover’ to true if it’s not already been given a value. Because we’ve added this event in the global space, this value will propagate for both frontend and adminhtml.
There are a few ways to get get attribute values in Magento:
<?php $product->getAttributeCode(); $product->getData(attribute_code);
These two methods do the same thing, however if the attribute is a type of select or multiselect then the ID of the value will be returned rather than the text of the attribute.
For these methods, the frontend attribute can be obtained from the product and used to get the attribute value:
<?php // Get the type of the attribute as dictated by the attribute's *frontend_model* field. // This will be a Mage_Catalog_Model_Resource_Eav_Attribute model: $attr = $product->getResource()->getAttribute($attributeName); // Get the value from the frontend type of the attribute. The frontend type is dictated by the frontend_model column in the eav_attribute table. By default, this will be Mage_Eav_Model_Entity_Frontend_Default if the frontend_model field is empty. $val = $attr->getFrontend()->getValue($product);
Note If an attribute value is not set, (I.e. NULL) the frontend attribute will return “No”. This happens in the Mage_Eav_Model_Entity_Attribute_Frontend_Abstract class’ getValue method:
if (!$valueOption) { $opt = Mage::getModel('eav/entity_attribute_source_boolean'); $options = $opt->getAllOptions(); if ($options) { foreach ($options as $option) { if ($option['value'] == $value) { $valueOption = $option['label']; } } } }
– It uses the “eav/entity_attribute_source_boolean” and defaults to one of the values set within in.
Another way to get the value of a select attribute is to use
<?php $product->getAttributeText(attribute_name);
This will use the attribute’s source_model (typically used in getting all possible values in adminhtml), and looks up the text of the attribute based on the value’s ID (this is set on the product’s data array):
<?php public function getAttributeText($attributeCode) { return $this->getResource() ->getAttribute($attributeCode) ->getSource() ->getOptionText($this->getData($attributeCode)); }
Product attributes have various non-standard EAV options to control how they’re output, such as escaping HTML characters. To honour these settings, product attributes should be passed through the catalog/output helper:
<?php $helper = Mage::helper('catalog/output'); echo $helper->productAttribute($product, $value, 'attribute_name');
Magento provides a block to output product attributes on the product detail page, provided they’re set to be visible on front. Use the Mage_Catalog_Block_Product_View_Attributes. If an attribute isn’t set, it will return “N/A”, if this is not required then this could be overridden and this line replaced with a continue;.
This can be very useful for attributes which need to be available on all product collections. This is to be used sparingly, however – adding a lot of attributes to a product collection can drastically impact performance; all attributes should also be indexed into the flat tables so that they’re available when flat catalog is on.
<frontend> <events> <catalog_product_collection_load_before> <observers> <llapgoch_pcattributes_add> <model>llapgoch_addpcattributes/observer</model> <method>addAttributes</method> <type>model</type> </llapgoch_pcattributes_add> </observers> </catalog_product_collection_load_before> </events> </frontend> <default> <llapgoch_addpcattributes> <general> <attributes></attributes> </general> </llapgoch_addpcattributes> </default>
– We add an observer to the catalog_product_collection_load_before event. This will make sure our attributes are added to the collection wherever it’s instantiated from.
– We also add a default node as a placeholder – we’ll allow loaded attributes to be set via the admin’s system configuration.
<?php class Llapgoch_AddProductCollectionAttributes_Model_Observer{ public function addAttributes($observer){ $attrs = Mage::helper('llapgoch_addpcattributes')->getAttributesToAdd(); if(is_array($attrs) && count($attrs)){ $observer->getCollection()->addAttributeToSelect($attrs); } } }
– All we need to do is get the attributes we’d like to add to the collection and add them to the select object.
<?php class Llapgoch_AddProductCollectionAttributes_Helper_Data extends Mage_Core_Helper_Abstract{ const XML_PATH_PRODUCT_ATTRIBUTES = "llapgoch_addpcattributes/general/attributes"; public function getAttributesToAdd(){ $attrs = explode(" ", Mage::getStoreConfig(self::XML_PATH_PRODUCT_ATTRIBUTES)); if(count($attrs)){ return $attrs; } return false; } }
– We load the attributes which can either be set in the admin area (see System Configuration) or overridden by another module’s config.xml.
– We split the attributes into an array using spaces, but this could be any character.
<?xml version="1.0"?> <config> <tabs> <llapgoch translate="label"> <label>LLAP-Goch</label> <sort_order>100</sort_order> </llapgoch> </tabs> <sections> <llapgoch_addpcattributes translate="label" module="llapgoch_addpcattributes"> <label>Product Attributes</label> <tab>llapgoch</tab> <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> <groups> <general translate="label"> <label>General</label> <sort_order>10</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> <fields> <attributes translate="label"> <label>Collection Attributes To Add</label> <comment>Space separate attributes to be added to every product collection</comment> <frontend_type>text</frontend_type> <backend_model>llapgoch_addpcattributes/system_config_backend_attributestring</backend_model> <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> </attributes> </fields> </general> </groups> </llapgoch_addpcattributes> </sections> </config>
– We just add our field as a text area to the system config.
– We use a backend model which will be used to warn the user if commas are detected in the string because we’ve chosen to split attributes on spaces.
<?php class Llapgoch_AddProductCollectionAttributes_Model_System_Config_Backend_Attributestring extends Mage_Core_Model_Config_Data{ public function _afterSave(){ $helper = Mage::helper('llapgoch_addpcattributes'); if(strpos($this->getValue(), ",") !== false){ Mage::getSingleton('core/session')->addNotice($helper->__('Please use spaces to separate your attribute names instead of commas')); } parent::_afterSave(); } }
– Checks the string for the existence of spaces and adds a notice for the user if so.
This code is available as a complete module here: https://github.com/llapgoch/magento-add-attributes-to-product-collection
Standard EAV Parameters
Catalog Product Specific
These are stored in the catalog_eav_attribute table, and the saving of them is catered for by the Mage_Catalog_Model_Resource_Setup class.
Attribute groups live in the eav_attribute_group table. They are assigned to attribute sets via their ID and have a default_id column to denote a default attribute group. Attribute sets can have multiple groups, and to change the order of the groups, use the sort_order column.
SELECT attribute_code, frontend_label FROM eav_attribute ea JOIN eav_entity_type eet USING(entity_type_id) WHERE eet.entity_type_code = 'customer_address'
Adding product attributes need to be run through the Mage_Catalog_Model_Resource_Setup script. This extends Mage_Eav_Model_Entity_Setup and includes the code to deal with the extra options available in the catalog_eav_attribute table. If your setup scripts aren’t running as this and you can’t change it, if for example other setup scripts rely on a different class, you can directly instantiate it. This instantiates it with the core_setup resource which uses the default_setup connection.
$setup = new Mage_Catalog_Model_Resource_Setup('core_setup');
The following will add a dropdown field with the name ‘Product Overlay Logo’. It will have two options – Playline and Horse. Options added in this manner will have a blank option so that saving a product with one of these options is not required.
<?php $installer = $this; $installer->startSetup(); $setup = new Mage_Catalog_Model_Resource_Setup('core_setup'); $setup->addAttribute('catalog_product', 'product_overlay_logo', array( 'attribute_set' => 'Default', 'group' => 'Hand Made Places', 'input' => 'select', 'type' => 'int', 'label' => 'Product Overlay Logo', 'visible' => true, 'required' => false, 'visible_on_front' => true, 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL, 'sort_order' => 10, 'option' => array( 'values' => array( 0 => 'Playline', 1 => 'Horse' ) ) )); $installer->endSetup();
Use the Store IDs as the array keys for each language translation.
<?php $installer = $this; $installer->startSetup(); $setup = new Mage_Eav_Model_Entity_Setup('core_setup'); $setup->addAttribute('catalog_product', 'product_overlay_logo', array( 'attribute_set' => 'Default', 'group' => 'Hand Made Places', 'input' => 'select', 'type' => 'int', 'label' => 'Product Overlay Logo', 'visible' => true, 'required' => false, 'visible_on_front' => true, 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL, 'sort_order' => 10, 'option' => array( 'values' => array( 0 => array( 0 => 'Playline', 1 => 'Playline French', 2 => 'Playline German' ), 1 => array( 0 => 'Horse', 1 => 'Horse French', 2 => 'Horse German' ) ) ) )); $installer->endSetup();
It’s important to note that when using the EAV’s update attribute method that the attribute code is not converted to another code — the column should be referenced as it is in the database. E.g. visible_on_front should be is_visible_on_front:
<?php $entityTypeId = $this->getEntityTypeId('catalog_product'); $this->updateAttribute($entityTypeId, 'is_visible_on_front', 'is_visible_on_front', 0);
$attrData = array( 'attribute_code_here'=> 'Value Here', ); $storeId = 0; $productIds = Mage::getModel('catalog/product')->getCollection()->getAllIds(); Mage::getModel("catalog/product_action")->updateAttributes( $productIds, $attrData, $storeId );
Sometimes, options need to be added to existing attributes:
$eav = new Mage_Eav_Model_Entity_Setup('core_setup'); $attr = Mage::getSingleton('eav/config')->getAttribute($entityTypeId, $attributeCode); $eav->addAttributeOption(array( 'value' => array( 'small' => array( 0 => 'Small' ), 'large' => array( 0 => 'Large' ) ), 'attribute_id' => $attr->getId(), 'order' => array( 'small' => 100, 'large' => 200 ) ));
This post deals with adding an image (or any other type of media) to a product. This is useful where an image is required to be separate from Magento’s built in product media system.
First, we want to create our attribute.
<?php $installer = $this; $installer->startSetup(); $this->addAttribute('catalog_product', 'poster_frame', array( 'attribute_set' => 'Default', 'group' => 'iWeb Media', 'input' => 'image', 'type' => 'varchar', 'label' => 'Poster Frame', 'visible' => true, 'required' => false, 'visible_on_front' => true, 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL, 'backend' => 'iwebmedia/product_attribute_backend_image', 'input_renderer'=> 'iwebmedia/adminhtml_product_image', 'sort_order' => 10 )); $this->endSetup();
Note: The input_renderer is a special field for the catalog_product EAV type and is stored in the catalog_eav_attribute table as frontend_input_renderer.
<?php class Iweb_Media_Block_Adminhtml_Product_Image extends Varien_Data_Form_Element_Image{ protected function _getUrl() { $url = false; if ($this->getValue()) { $url = Mage::helper('iwebmedia')->getPlaceholderUrl() . $this->getValue(); } return $url; } }
– We just needed to change the getUrl() method to return the path to our image location, which we’ve got a helper to do so that’s only in one location.
<?php class Iweb_Media_Helper_Data extends Mage_Core_Helper_Abstract { const PLACEHOLDER_UPLOAD_DIR = "iwebmedia"; public function getPlaceholderDir(){ return Mage::getBaseDir('media') . DS . self::PLACEHOLDER_UPLOAD_DIR . DS; } public function getPlaceholderUrl(){ return Mage::getBaseUrl('media') . '/' . self::PLACEHOLDER_UPLOAD_DIR . '/'; } }
This is the class which deals with the saving of our data and image
<?php class Iweb_Media_Model_Product_Attribute_Backend_Image extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract{ public function beforeSave($object){ parent::beforeSave($object); $name = $this->_getName(); $imageData = $object->getData($name); if(isset($imageData['delete']) && (bool) $imageData['delete']){ return $this->_removeImage($object, $imageData['value']); }else{ return $this->_uploadImage($object); } } protected function _getHelper(){ return Mage::helper('iwebmedia'); } protected function _getName(){ return $this->getAttribute()->getName(); } protected function _removeImage($object, $fileName){ $file = $this->_getHelper()->getPlaceholderDir() . $fileName; $name = $this->_getName(); if(file_exists($file)){ unlink($file); } $object->setData($name, ''); } protected function _uploadImage($object){ $name = $this->_getName(); if(!isset($_FILES[$name]) || (int) $_FILES[$name]['size'] <= 0){ return; } $path = $this->_getHelper()->getPlaceholderDir(); $uploader = new Varien_File_Uploader($_FILES[$name]); $uploader->setAllowedExtensions(array('jpg','jpeg','gif','png')); // Allow Magento to create a name for this upload! $uploader->setAllowRenameFiles(true); $result = $uploader->save($path); $object->setData($name, $result['file']); } }
– We use the beforeSave() method which then uses the delete input (provided by Magento’s Varien_Data_Form_Element_Image class) to check whether we should remove the existing image or upload a new one.
– The Varien_File_Uploader class deals with the validation of the image and will throw an error in case of any failure.
– setAllowRenameFiles(true) allows the file uploader to create a new file name if the current one already exists.
– $object->setData($name, $result[‘file’]); sets the new filename on the product for saving.
<?php public function getValue($product, $attributeCode){ if(($val = $product->getData($attributeCode)) === null){ return $product->getResource()->getAttribute($attributeCode)->getDefaultValue(); } return $val; }
<global> <sales> <quote> <item> <product_attributes> <location/> </product_attributes> </item> </quote> </sales> </global>
This is called by the _assignProducts method of the Mage_Sales_Model_Resource_Quote_Item_Collection‘s _assignProducts() method, where it adds all of the attributes to the product collection of the quote item collection.
Create an installer with the following:
$installer = new Mage_Sales_Model_Resource_Setup('core_setup'); $entities = array( 'quote_item', 'order_item' ); $options = array( // For some reason, VARCHAR works here, whereas elsewhere it must be TYPE_TEXT with a length of 255. 'type' => Varien_Db_Ddl_Table::TYPE_VARCHAR, 'visible' => true, 'required' => false ); foreach ($entities as $entity) { $installer->addAttribute($entity, 'location', $options); }
The addAttribute() method of the Mage_Sales_Model_Resource_Setup creates the columns on the quote_item and order_item tables.
Unlike copying from quote items to order items, there isn’t an XML method to do this – it has to be accomplished through an observer. Add the following to the config (with the observer / method substituted):
<frontend> <events> <sales_quote_item_set_product> <observers> <observer_name> <class>model/observer</class> <method>addAttributesToQuoteItem</method> </observer_name> </observers> </sales_quote_item_set_product> </events> </frontend>
Then create the following method for the observer above:
public function addAttributesToQuoteItem($observer){ $quoteItem = $observer->getQuoteItem(); $product = $observer->getProduct(); $quoteItem->setLocation($product->getLocation()); }
Any arbitrary information could be set on the quote or quote item; it does not have to come from the product itself. E.g. A different type of ship note could be set by a different observer. The associated columns just need to exist on the quote or quote item tables.
<global> <fieldsets> <sales_convert_quote_item> <location> <to_order_item>*</to_order_item> </location> </sales_convert_quote_item> </fieldsets> </global>
Note: This will not copy the data to the sales_flat_shipment_item table.
The following will copy the object’s attributes when converting the quote item back to a quote item, an invoice item, and a credit memo item
<global> <fieldsets> <sales_convert_order_item> <location> <to_quote_item>*</to_quote_item> <to_invoice_item>*</to_invoice_item> <to_cm_item>*</to_cm_item> </location> </sales_convert_order_item> </fieldsets> </global>
These are used in the Mage_Sales_Model_Convert_Order class’ itemToQuoteItem, itemToInvoiceItem, and itemToCreditmemoItem methods. Note: In default Magento, it does not appear that the itemToQuoteItem is used, so this should be included for third party extensions which may rely on this method.