Categories
Magento 1

Retreiving Configurable Products Whose Children Have a Particular Attribute

A title as catchy as a loose tablecloth. This will give you a list of all configurable products whose children have a particular attribute.

SELECT config_product.entity_id, prod_name.value AS product_name, config_product.sku
     FROM  catalog_product_entity config_product
JOIN eav_attribute
    ON eav_attribute.entity_type_id = 4 
    AND eav_attribute.attribute_code = "name"
JOIN catalog_product_entity_varchar prod_name
    ON prod_name.`attribute_id` = eav_attribute.`attribute_id`
    AND prod_name.`entity_id` = config_product.`entity_id`
WHERE config_product.entity_id IN (
    SELECT parent_id 
        FROM catalog_product_entity_int att_value 
    JOIN `catalog_product_super_link` link
        ON link.product_id = att_value.entity_id
    AND att_value.`value` = attribute_value
);
  • As Magento only allows the creation of product links on dropdowns, catalog_product_entity_int should be fine to remain hardcoded.
  • Change attribute_value to the option value id. An easy way to retrieve this from Magento’s admin is to look at the name of the input box when looking at the attribute’s Manage Label/Options tab; it will be something like “option[value][959][0]” where 959 is the option ID.

The SQL firstly uses a subquery to get all products which are being used as simples (if their product_id appears in the super_link table), and gets their configurable counterparts from this. We need to do this as a subquery their may be multiple configurables using the same simple product. It then uses the parent IDs in looking up more details from the catalog_product_entity and catalog_product_entity_varchar tables.

Categories
Magento 1

Setting Category & Product Attribute Defaults in Magento 1

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.

Categories
Magento 1

Displaying Magento’s Configurable Options as Radio Buttons (Quick and Dirty)

By default Magento displays a product’s configurable options as select drop downs. This isn’t always appropriate for the project however; some clients prefer radio buttons, and longer product names will cause some of the text in the select box to be cut off on mobile devices.

Here’s a quick and dirty way to display those selects as radio buttons (this assumes a descendent of the RWD theme is being used and hasn’t been tested with a base theme). We don’t mess about with changing templates with this method – it’s all done in JavaScript.

XML

    <PRODUCT_TYPE_configurable>
        <reference name="head">
            <action method="addItem">
                <type>skin_js</type>
                <name>js/configurable.js</name>
            </action>
        </reference>
    </PRODUCT_TYPE_configurable>

JavaScript – configurable.js

;(function($j){
    var loadOptions = Product.Config.prototype.loadOptions;
    var containerClass = "options-list"

    // Create an option array for each of the configurable options
    Product.Config.prototype.loadOptions = function() {
        loadOptions.apply(this);
        var self = this;

        this.settings.each(function(element){
            var attributeId = element.id.replace(/[a-z]*/, '');
            var options = self.getAttributeOptions(attributeId);
            var radioId = attributeId + "-radio";

            var $optionContainer = $j('<ul />').addClass(containerClass);

            if(options){
                for(var i=0;i<options.length;i++){
                    var $radio = $j("<input type='radio' />" ).attr("name", radioId).addClass('radio');
                    var $li = $j("<li />");
                    var foundValue = $j(element).find('option[value="' + options[i].id + '"]').html();
                    var $span = $j("<span class='label' / >").append($j('<label />').html(foundValue));

                    $radio.attr('value', options[i].id);
                    $li.append($radio)
                        .append($span);

                    $optionContainer.append($li);

                    $radio.on('click', function(ev){
                        var val = $j(this).val();
                        $j(element).val(val);
                        self.reloadPrice();
                    });
                }
            }

            $j(element).parent().append($optionContainer);
            
            $j(element).css({
                'visibility':'hidden',
                'height': '0px',
                'position': 'absolute'
            });
        });
    }
}(jQuery));
  • We monkey patch the Product.Config object and add our own loadOptions method. This runs the original method, then recreates the select as a list of radio options in the same container as where the select box resides.
  • The select box is then hidden, however instead of applying a display: none on it, we change its visibility, height and position. This because Magento will not validate hidden options by default so appling this method allows us to keep Magento’s built in validation.
  • When the user changes the selected radio button, all the script does is changes the value of the hidden select box and then calls the reloadPrice() method.
Categories
Magento 1

Magento Cart Items – Getting Child Product Items

This is how it’s done in the Mage_Checkout_Block_Cart_Item_Renderer_Configurable class. It will return the simple product if available otherwise it will return the product of the cart item.

<?php
public function getChildProduct()
{
    if ($option = $this->getItem()->getOptionByCode('simple_product')) {
        return $option->getProduct();
    }
    return $this->getProduct();
}
Categories
Magento 1

Twitter Cards & Magento

Twitter cards offer rich ways to share content from websites. Here’s how to implement Twitter’s card data and add a share link to product pages!

Create A Head Block

This will allow us to implement Twitter’s meta tags into Magento’s head block. Creating this as a text list will also allow us to reuse this for pages other than product detail.

<default>
    <reference name="head">
        <block type="core/text_list" name="twitter.head.extra" />
    </reference>
</default>

Add The Meta Card Data

<catalog_product_view>
    <reference name="twitter.head.extra">
        <block type="modulename/catalog_product_view_meta" name="twitter.product.meta" template="catalog/product/view/meta.phtml" />
    </reference>
</catalog_product_view>

Create The Meta Template

Create this in the location catalog/product/view/meta.phtml

<?php
$_imageUrl = $this->getImageUrl();
$_product = $this->getProduct();

?>
<?php if($_imageUrl): ?>
    <meta name="twitter:card" content="summary" />
    <meta name="twitter:image" content="<?php echo $_imageUrl; ?>">
    <meta name="twitter:site" content="@twitter_handle" />
    <meta name="twitter:title" content="<?php echo $this->htmlEscape($_product->getName()); ?>" />
    <meta name="twitter:description" content="<?php echo $this->htmlEscape($_product->getShortDescription()); ?>" />
<?php endif; ?>

Create The Block Class

This is the class we reference in our XML and should correspond to modulename/catalog_product_view_meta

<?php
class Namespace_Module_Block_Catalog_Product_View_Meta extends Mage_Core_Block_Template
{
    protected function getProduct()
    {
        return Mage::registry('product');
    }

    public function getImageUrl()
    {
        if(!($product = $this->getProduct())){
            return;
        }

        return Mage::helper('catalog/image')->init($product, 'small_image');
    }
}

Add A Sharing Link

If the above steps are working correctly, all of the product meta tags included in the head of the product page’s head. Now, we just nee to create a sharing link from the page and Twitter should do the rest for us!

<?php 
$_product = $this->getProduct();
$_helper = $this->helper('catalog/output');
$_productName = urlencode(trim($_helper->productAttribute($_product, $_product->getName(), 'name')));
$_productUrl = urlencode(trim($_product->getProductUrl()));
?>

<a class="icon icon-twitter" href="<?php echo 'http://twitter.com/home?status=' . $_productName . "  " . $_productUrl; ?>" target="_blank" title="<?php echo Mage::helper('core')->quoteEscape($this->__('Share on Twitter')) ?>">
    <span><?php echo $this->__('Share on Twitter') ?></span>
</a>
Categories
Magento 1

Adding Extra Attributes to All Product Collections

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.

Config

<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.

Observer

<?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.

Helper

<?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.

System Configuration

<?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.

Backend Model

<?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

Categories
Magento 1

Display Shipping Methods For A Product

This will display appropriate shipping methods on the product detail page

The Block Class

class Namespace_Module_Block_Catalog_Product_Delivery extends Mage_Core_Block_Template{

    protected $_product;

    public function _construct(){
       parent::_construct();
       $this->setTemplate('catalog/product/delivery.phtml');
    }

    public function setProductId($productId){
        $this->_product = Mage::getModel('catalog/product')->load($productId);
    }

    public function getDeliveryMethods(){
        $quote = Mage::getModel('sales/quote');
        $existingQuote = Mage::getSingleton('checkout/session')->getQuote();

        if($countryId = $existingQuote->getShippingAddress()->getCountryId()){
            $quote->getShippingAddress()->setCountryId($countryId);
        }else{
            $quote->getShippingAddress()->setCountryId(Mage::getStoreconfig('general/country/default'));
        }

        $quote->addProduct($this->_product);
        $quote->getShippingAddress()->collectTotals();
        $quote->getShippingAddress()->setCollectShippingRates(true);
        $quote->getShippingAddress()->collectShippingRates();
        $rates = $quote->getShippingAddress()->getShippingRatesCollection();

       return $rates;
    }

}
  • Load a new quote object and populate its address with the country of the existing quote or the default config country.
  • Add the product to the quote
  • return the rates

The Template

<?php
$_rates = $this->getDeliveryMethods();
?>

<p><?php echo $this->__("DELIVERY OPTIONS");?></p>
<?php if(count($_rates)):?>
    <ul class="product-rates">
    <?php foreach($_rates as $_rate):?>
        <li><?php echo $this->escapeHtml($_rate->getMethodTitle());?></li>
    <?php endforeach; ?>
    </ul>
<?php endif;?>
Categories
Magento 1

Product Attributes

List of Parameters

Standard EAV Parameters

  • backend The backend model to use for saving values. These should extend Mage_Eav_Model_Entity_Attribute_Backend_Abstract
  • type default: varchar
  • frontend The frontend model to use for rendering values
  • input default: text
  • label The label to display in the admin
  • frontend_class
  • source The source model
  • required
  • user_defined
  • default The default value
  • unique
  • note Will be displayed under the input in the admin area

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.

  • global The scope of the attributes. Default: Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL
  • visible default: 1
  • searchable default: 0
  • filterable default 0
  • comparable default 0
  • visible_on_front default 0
  • wysiwyg_enabled default 0
  • is_html_allowed_on_front default 0
  • visible_in_advanced_search default 0
  • filterable_in_search default 0
  • used_in_product_listing default 0
  • used_for_sort_by default 0
  • apply_to default 0
  • is_configurable default 1
  • used_for_promo_rules default 0

Attribute Sets and Groups

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.

Getting All Attributes For An Entity (Raw SQL)

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'

Running Setup Scripts

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');

Adding A Product Attribute with Options

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();

Adding Options with Translations

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();

Updating Attributes

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);

Updating a Set of Product Attributes

$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
);

Mage_Eav_Model_Setup – Useful Methods

  • getEntityTypeId($entity_code); Gets the Database ID of the entity type with the given code (Eg. Customer)
  • getDefaultAttributeSetId($entityTypeId); Gets the default attribute set ID for the entity
  • getDefaultAttributeGroupId($entityTypeId, $attributeSetId = null); * Gets the default group for the entity‚Äôs attribute set. If no attribute set Id is passed in then the default set is used.
  • addAttribute($entity_code, $attribute_code, array)

Adding options to an existing attribute

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
	)
));

Adding Attribures to a Configurable Product’s Collection










Categories
Magento 1

Adding an Image Attribute To A Product

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.

The Installer

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();
  • Because we’re saving an image, we need to create our own backend class to deal with the saving of the image and setting the filename of the image on the product object.
  • We also need to create a custom renderer for the input. This is a block class which we’ll create in our module. By default, Magento will use its own image renderer which is Mage_Adminhtml_Block_Catalog_Category_Helper_Image. This extends Varien_Data_Form_Element_Image and hardcodes the getUrl() method to look in media/catalog/product.

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.

The 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.

The Helper Class

<?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 . '/';
    }
}

The Attribute’s Backend Model

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.