Pages

Thursday, January 1, 2015

How to adding custom product attributes in Magento using setup script

Have you tried to add custom product attributes using the setup script and wondering why certain properties of the attribute are not set?
And in top of that, you're are running out time for this simple task, just because you don't find the right answer..
Here is an approach, how to solve this problem. Probably not the perfect solution, but it works ..!
To get started, you need to handle 3 parts in your module:

/yournamespace/modulename/sql/modulename_setup/install-0.1.0.php
/yournamespace/modulename/Resource/Eav/Mysql4/setup.php
/yournamespace/modulename/etc/config.xml

So let's start with the 1st part:
(Hint: if you update your module, name the script 'update-x.x.x.php')


<?php
/**
 * Add my_attribute values 
 * example by http://magento4newbies.blogspot.com.
 *
 */

$installer = $this;
$installer->startSetup();

$installer->addAttribute('catalog_product', 'my_attribute_date', array(
    'group'             => 'My Attribute Set',
    'label'             => 'My Attribute Date',
    'type'              => 'datetime',
    'input'             => 'date',
    'backend'           => 'eav/entity_attribute_backend_datetime',
    'frontend'          => '',
    'visible'           => true,
    'required'          => false,
    'user_defined'      => true,
    'searchable'        => false,
    'filterable'        => false,
    'comparable'        => false,
    'visible_on_front'  => true,
    'visible_in_advanced_search' => false,
    'unique'            => false,
    'global'            => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE,
));


$installer->endSetup();

You can modify 'input' and 'type' according to your needs:

//For textfield
'input' => 'text',
'type' => 'text',

//For textarea
'input' => 'textarea',
'type' => 'text',

//For date field
'input' => 'date',
'type' => 'datetime',

//For select list 
'input' => 'select',
'type' => 'text',

//For yes/no
'input' => 'boolean',
'type' => 'int',

2nd part is to define your resource class, so that your attribute's properties are written as expected:

<?php
/**
 * Yournamespace_Modulename_Model_Resource_Eav_Mysql4_Setup
 * example by http://magento4newbies.blogspot.com.
 *
 */

class Yournamespace_Modulename_Model_Resource_Eav_Mysql4_Setup extends Mage_Eav_Model_Entity_Setup
{
 
     /**
     * Prepare catalog attribute values to save
     * From: Mage_Catalog_Model_Resource_Setup
     *
     * @param array $attr
     * @return array
     */
    protected function _prepareValues($attr)
    {
        $data = parent::_prepareValues($attr);
        $data = array_merge($data, array(

            'apply_to'                      => $this->_getValue($attr, 'apply_to'),
            'frontend_input_renderer'       => $this->_getValue($attr, 'input_renderer'),
            'is_comparable'                 => $this->_getValue($attr, 'comparable', 0),
            'is_configurable'               => $this->_getValue($attr, 'is_configurable', 1),
            'is_filterable'                 => $this->_getValue($attr, 'filterable', 0),
            'is_filterable_in_search'       => $this->_getValue($attr, 'filterable_in_search', 0),
            'is_global'                     => $this->_getValue(
                $attr,
                'global',
                Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_WEBSITE
            ),
            'is_html_allowed_on_front'      => $this->_getValue($attr, 'is_html_allowed_on_front', 0),
            'is_searchable'                 => $this->_getValue($attr, 'searchable', 0),
            'is_used_for_promo_rules'       => $this->_getValue($attr, 'used_for_promo_rules', 0)
            'is_visible'                    => $this->_getValue($attr, 'visible', 1),
            'is_visible_on_front'           => $this->_getValue($attr, 'visible_on_front', 1),
            'is_wysiwyg_enabled'            => $this->_getValue($attr, 'wysiwyg_enabled', 0),
            'is_visible_in_advanced_search' => $this->_getValue($attr, 'visible_in_advanced_search', 0),
            'position'                      => $this->_getValue($attr, 'position', 0),
            'used_for_sort_by'              => $this->_getValue($attr, 'used_for_sort_by', 0),
            'used_in_product_listing'       => $this->_getValue($attr, 'used_in_product_listing', 0),
        ));
        return $data;
    }
}

And for the last part, you need to set the resource class and the right version number in config.xml.
<?xml version="1.0" encoding="UTF-8"?>
<config>
    <modules>
        <yournamespace_modulename>
            <version>x.x.x</version> 
        </yournamespace_modulename>
    </modules>
    <global>
        <resources>
            <yournamespace_modulename_setup>
                <setup>
                    <module>Yournamespace_Modulename</module>
                    <class>Yournamespace_Modulename_Model_Resource_Eav_Mysql4_Setup</class>
                </setup>
            </yournamespace_modulename_setup>
        </resources>
    </global>
</config>

Now when you check your attributes within the Magento Admin GUI (Catalog->Attributes->Manage Attributes), you see is_filterable, is_visible, is_visible_on_front, is_html_allowed_on_front and the other parameters set to the same value as in the setup script.

Sunday, November 30, 2014

Installing Magento 1.9.1 on Bitnami Nginx Stack

First download the Bitnami Nginx Stack Magento.
Install the Bitnami Stack where you like, called by me root_nginx_stack.

After installation copy the demo project to the apps folder and remove htdocs folder
  1. cd root_nginx_stack
    cp -r docs/demo/ apps/magento
    rm -rf apps/magento/htdocs
    unzip magento-1.9.1.0.zip
    mv magento root_nginx_stack/magento/apps/htdocs
  2. change in root_nginx_stack/magento/*.conf all reference 'demo' to 'magento'
  3. in root_nginx_stack/nginx/conf/bitnami/bitnami-apps-prefix.conf add the line
    include "root_nginx_stack/apps/magento/conf/nginx-prefix.conf";
  4. in root_nginx_stack/magento/nginx-vhosts.conf add the lines below before the include directive
    
        location / {
            index index.html index.php; ## Allow a static html file to be shown first
            try_files $uri $uri/ @handler; ## If missing pass the URI to Magento's front handler
            expires 30d; ## Assume all files are cachable
        }
        
        location @handler { ## Magento uses a common front handler
            rewrite / /index.php;
        }
    
  5. in root_nginx_stack/magento/nginx-app.conf replace the location part with the lines below and set your root_nginx_stack path.
    location ~ \.php$ {
        if (!-e $request_filename) { rewrite / /index.php last; } ## Catch 404s that try_files miss
    
        expires        off; ## Do not cache dynamic content
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_read_timeout 300;
        fastcgi_pass unix:/root_nginx_stack/php/var/run/www.sock;
        fastcgi_index index.php;
        fastcgi_param  SCRIPT_FILENAME $request_filename;
        include fastcgi_params;
    }
    
        ## These locations would be hidden by .htaccess normally
        location ^~ /magento/app/                { deny all; }
        location ^~ /magento/includes/           { deny all; }
        location ^~ /magento/lib/                { deny all; }
        location ^~ /magento/media/downloadable/ { deny all; }
        location ^~ /magento/pkginfo/            { deny all; }
        location ^~ /magento/report/config.xml   { deny all; }
        location ^~ /magento/var/                { deny all; }
    
        location /magento/var/export/ { ## Allow admins only to view export folder
            auth_basic           "Restricted"; ## Message shown in login window
            auth_basic_user_file htpasswd; ## See /etc/nginx/htpassword
            autoindex            on;
        }
    
        location  /magento. { ## Disable .htaccess and other hidden files
            return 404;
        }
    
    
        location ~ .php/ { ## Forward paths like /js/index.php/x.js to relevant handler
            rewrite ^(.*.php)/ $1 last;
        }
    
  6. create a database for your magento installation:
    /root_nginx_stack/mysql/bin/mysql -uroot -p
    ...
    create database bitnami_magento;
    grant all privileges on bitnami_magento.* to 'bn_magento'@'localhost' identified by 'MAGENTO_PASSWORD';
    
  7. restart nginx and type in your browser: http://localhost:8080/magento/index.php
    your magento installation starts now!
I got the references for this config from Bitnami Wiki and Magento Wiki.
It's not production ready but helps as a starting point.

Tuesday, November 25, 2014

How to delete orders

Usually you have after a certain time of developing some test orders that you want go get rid of. this is a small script to delete them


// if you run this code in a shell, see comment below, you have to set this flag
Mage::register('isSecureArea', true);

// select all orders
$orders = Mage::getModel('sales/order')->getCollection();

// select defined orders listed in $orderIds  
$orders = Mage::getModel('sales/order')->getCollection()
    ->addFieldToFilter('entity_id', (array) $orderIds);

foreach ($orders as $order) { 
    $order->delete(); 
}

you can run code snippet like above with a php shell as provided in Magento PHP Developer's Guide (p.58)

Sunday, November 23, 2014

Bulk update products to be in/out of stock programatically

This is an example to set all (filtered) products to be in stock with quantity of 1.

$products = Mage::getModel('catalog/product')->getCollection();
// filter your product list
$products = $products->addFieldToFilter($attribute_code, $attribute_value)->load();

foreach($products as $product){
 $stockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($product); 
 $stockItem->setData('is_in_stock', 1);
 $stockItem->setData('qty',1);
 $stockItem->save();
}


Friday, November 21, 2014

Converting wav files to flac with python and audiotools

This is not about Magento programming ;-)

I was looking for a (free) tools to convert all my wav files to flac. As such, not a special task, there are many tools around. But all the GUI tools I found, had big issues to run in batch mode with thousands of files to convert. 

Eventually I found track2track from audiotools. The only problem was here, I couldn't get it working in batch mode with setting the right params for the output (converted) file names. So I decided to wrap my own script around audiotools. 

Certainly not really performant, but it does the job.
Here my first try:


from Queue import Queue
import logging
import os
from threading import Thread
import audiotools
from audiotools.wav import InvalidWave

"""
Wave 2 Flac converter script
using audiotools
"""
class W2F:

    logger = ''

    def __init__(self):
        global logger
        # create logger
        logger = logging.getLogger(__name__)
        logger.setLevel(logging.DEBUG)

        # create a file handler
        handler = logging.FileHandler('converter.log')
        handler.setLevel(logging.INFO)

        # create a logging format
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        handler.setFormatter(formatter)

        # add the handlers to the logger
        logger.addHandler(handler)

    def convert(self):
        global logger
        file_queue = Queue()
        num_converter_threads = 5

        # collect files to be converted
        for root, dirs, files in os.walk("/Volumes/music"):

            for file in files:
                if file.endswith(".wav"):
                    file_wav = os.path.join(root, file)
                    file_flac = file_wav.replace(".wav", ".flac")

                    if (os.path.exists(file_flac)):
                        logger.debug(''.join(["File ",file_flac, " already exists."]))
                    else:
                        file_queue.put(file_wav)

        logger.info("Start converting:  %s files", str(file_queue.qsize()))

        # Set up some threads to convert files
        for i in range(num_converter_threads):
            worker = Thread(target=self.process, args=(file_queue,))
            worker.setDaemon(True)
            worker.start()

        file_queue.join()

    def process(self, q):
        """This is the worker thread function.
        It processes files in the queue one after
        another.  These daemon threads go into an
        infinite loop, and only exit when
        the main thread ends.
        """
        while True:
            global logger
            compression_quality = '0' #min compression
            file_wav = q.get()
            file_flac = file_wav.replace(".wav", ".flac")

            try:
                audiotools.open(file_wav).convert(file_flac,audiotools.FlacAudio, compression_quality)
                logger.info(''.join(["Converted ", file_wav, " to: ", file_flac]))
                q.task_done()
            except InvalidWave:
                logger.error(''.join(["Failed to open file ", file_wav, " to: ", file_flac," failed."]), exc_info=True)
            except Exception, e:
                logger.error('ExFailed to open file', exc_info=True)




As you can see, there is a bit of multithreading implemented. Looks like it works a bit faster than my first try. Anyway, it's only a dummy example, improve it as you like!

Wednesday, November 5, 2014

How to get Magento's attribute names and values

For my own reference, quick and dirty:
$product = Mage::getModel('catalog/product')->load($productId);
$attribute_value = $product->getData($attribute_code);

The attribute code used below is 'attribute_code'.

/**
 * get attribute collection
 */
$attribute = $_product->getResource()->getAttribute('attribute_code');

/**
 * get attribute type
 */
$attribute->getAttributeType();

/**
 * get attribute label
 */
$attribute->getFrontendLabel();

/**
 * get attribute default value
 */
$attribute->getDefaultValue();

/**
 * check if the attribute is visible
 */
$attribute->getIsVisible();

/**
 * check if the attribute is required
 */
$attribute->getIsRequired();

/**
 * get attribute value
 */
$attributeValue = Mage::getModel('catalog/product')->load($_product->getId())->getAttributeCode();

//or without loading the product
$attributeValue = Mage::getResourceModel('catalog/product')->getAttributeRawValue($productId, 'attribute_code', $storeId);

/**
* get attribute option code/value list 
*/
$attribute = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'attribute_code');
$options = $attribute->getSource()->getAllOptions(false);


in a view.php or similar, you can access the attribute text related to a product like:
<?php echo $_product->getAttributeText('attribute_code'); ?>

Thursday, October 9, 2014

"Undefined Index" error importing products over cvs file

If you happened to get one of the following error during import of products over a cvs file,
  • Notice: Undefined index: _media_attribute_id
  • Notice: Undefined index: _media_lablel
  • Notice: Undefined index: _media_position
  • Notice: Undefined index: _media_image
  • Notice: Undefined index: _media_is_disabled
  • Notice: Undefined index: _attribute_set

make sure, to fill each field with a meaningful value. These errors occur if you import several images / categories per product.