Pages

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.

Sunday, September 28, 2014

Magento REST API

Not really easy to extend the REST API,  here some info which might help.

Mapping REST Operation to Magento:


// Map HTTP methods to classic CRUD verbs
$operationByMethod = array(
    'GET'    => Mage_Api2_Model_Resource::OPERATION_RETRIEVE,
    'POST'   => Mage_Api2_Model_Resource::OPERATION_CREATE,
    'PUT'    => Mage_Api2_Model_Resource::OPERATION_UPDATE,
    'DELETE' => Mage_Api2_Model_Resource::OPERATION_DELETE
);

Important insight to understand how magento decides where to route your request,
see core/Mage/Api2/Model/Mage_Api2_Model_Resource. If you don't get the desired results, check if ACTION_TYPE_xxx is set right, i.e only ACTION_TYPE_COLLECTION is allowed for OPERATION_CREATE.


public function dispatch()
{
    switch ($this->getActionType() . $this->getOperation()) {
        /* Create */
        case self::ACTION_TYPE_ENTITY . self::OPERATION_CREATE:
            // Creation of objects is possible only when working with collection
            $this->_critical(self::RESOURCE_METHOD_NOT_IMPLEMENTED);
            break;
        case self::ACTION_TYPE_COLLECTION . self::OPERATION_CREATE:
            // If no of the methods(multi or single) is implemented, request body is not checked
            if (!$this->_checkMethodExist('_create') && !$this->_checkMethodExist('_multiCreate')) {
                $this->_critical(self::RESOURCE_METHOD_NOT_IMPLEMENTED);
            }
            // If one of the methods(multi or single) is implemented, request body must not be empty
            $requestData = $this->getRequest()->getBodyParams();
            if (empty($requestData)) {
                $this->_critical(self::RESOURCE_REQUEST_DATA_INVALID);
            }
            // The create action has the dynamic type which depends on data in the request body
            if ($this->getRequest()->isAssocArrayInRequestBody()) {
                $this->_errorIfMethodNotExist('_create');
                $filteredData = $this->getFilter()->in($requestData);
                if (empty($filteredData)) {
                    $this->_critical(self::RESOURCE_REQUEST_DATA_INVALID);
                }
                $newItemLocation = $this->_create($filteredData);
                $this->getResponse()->setHeader('Location', $newItemLocation);
            } else {
                $this->_errorIfMethodNotExist('_multiCreate');
                $filteredData = $this->getFilter()->collectionIn($requestData);
                $this->_multiCreate($filteredData);
                $this->_render($this->getResponse()->getMessages());
                $this->getResponse()->setHttpResponseCode(Mage_Api2_Model_Server::HTTP_MULTI_STATUS);
            }
            break;
        /* Retrieve */
        case self::ACTION_TYPE_ENTITY . self::OPERATION_RETRIEVE:
            $this->_errorIfMethodNotExist('_retrieve');
            $retrievedData = $this->_retrieve();
            $filteredData  = $this->getFilter()->out($retrievedData);
            $this->_render($filteredData);
            break;
        case self::ACTION_TYPE_COLLECTION . self::OPERATION_RETRIEVE:
            $this->_errorIfMethodNotExist('_retrieveCollection');
            $retrievedData = $this->_retrieveCollection();
            $filteredData  = $this->getFilter()->collectionOut($retrievedData);
            $this->_render($filteredData);
            break;
        /* Update */
        case self::ACTION_TYPE_ENTITY . self::OPERATION_UPDATE:
            $this->_errorIfMethodNotExist('_update');
            $requestData = $this->getRequest()->getBodyParams();
            if (empty($requestData)) {
                $this->_critical(self::RESOURCE_REQUEST_DATA_INVALID);
            }
            $filteredData = $this->getFilter()->in($requestData);
            if (empty($filteredData)) {
                $this->_critical(self::RESOURCE_REQUEST_DATA_INVALID);
            }
            $this->_update($filteredData);
            break;
        case self::ACTION_TYPE_COLLECTION . self::OPERATION_UPDATE:
            $this->_errorIfMethodNotExist('_multiUpdate');
            $requestData = $this->getRequest()->getBodyParams();
            if (empty($requestData)) {
                $this->_critical(self::RESOURCE_REQUEST_DATA_INVALID);
            }
            $filteredData = $this->getFilter()->collectionIn($requestData);
            $this->_multiUpdate($filteredData);
            $this->_render($this->getResponse()->getMessages());
            $this->getResponse()->setHttpResponseCode(Mage_Api2_Model_Server::HTTP_MULTI_STATUS);
            break;
        /* Delete */
        case self::ACTION_TYPE_ENTITY . self::OPERATION_DELETE:
            $this->_errorIfMethodNotExist('_delete');
            $this->_delete();
            break;
        case self::ACTION_TYPE_COLLECTION . self::OPERATION_DELETE:
            $this->_errorIfMethodNotExist('_multiDelete');
            $requestData = $this->getRequest()->getBodyParams();
            if (empty($requestData)) {
                $this->_critical(self::RESOURCE_REQUEST_DATA_INVALID);
            }
            $this->_multiDelete($requestData);
            $this->getResponse()->setHttpResponseCode(Mage_Api2_Model_Server::HTTP_MULTI_STATUS);
            break;
        default:
            $this->_critical(self::RESOURCE_METHOD_NOT_IMPLEMENTED);
            break;
    }
}

Sunday, September 14, 2014

How to get Magento Urls

(for my own reference)

Base Url , Skin Url , Media Url , Js Url , Store Url and Current Url Get Url in phtml files

  • Base Url : Mage::getBaseUrl(); 
  • Skin Url : Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_SKIN); 
  • Unsecure Skin Url : $this->getSkinUrl('images/image_name.jpg'); 
  • Secure Skin Url : $this->getSkinUrl('images/image_name.gif', array('_secure'=>true)); 
  • Media Url : Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_MEDIA); 
  • Js Url : Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_JS); 
  • Store Url : Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB); 
  • Current Url Mage::helper('core/url')->getCurrentUrl(); 

Urls in cms pages or static blocks

  • Base Url : {{store url=""}} 
  • Skin Url : {{skin url='images/image_name.jpg'}} 
  • Media Url : {{media url='/image_name.jpg'}} 
  • Store Url : {{store url='somepage.html'}}

Saturday, July 12, 2014

How to download extensions from Magento Connect manually

Usually you need to install at some point extensions for your Magento shop.
Best practice is to install it manually. That's to keep control and check, whether the extension is ok without breaking your shop installation. There are two ways to do it.

Either you go to freegento.com/ddl-magento-extension.php and enter the extension key from Magento Connect. As result, you get a download link for your module.


Or you use mage, a shell provided by Magento.
Got to the root of your test store

orion:~ user$ cd /Users/user/dev/magento/latest


If you use it the first time, you need to adapt the permissions

orion:~ user$ chmod 550 mage
orion:~ user$ ./mage

Connect commands available:
===========================
channel-add          Add a Channel       
channel-alias        Specify an alias to a channel name
channel-delete       Remove a Channel From the List
channel-info         Retrieve Information on a Channel
channel-login        Connects and authenticates to remote channel server
channel-logout       Logs out from the remote channel server
clear-cache          Clear Web Services Cache
config-get           Show One Setting    
config-help          Show Information About Setting
config-set           Change Setting      
config-show          Show All Settings   
convert              Convert old magento PEAR package to new format
download             Download Package    
info                 Display information about a package
install              Install Package     
install-file         Install Package Archive File
list-available       List Available Packages
list-channels        List Available Channels
list-files           List Files In Installed Package
list-installed       List Installed Packages In The Default Channel
list-upgrades        List Available Upgrades
package              Build Package       
package-dependencies Show package dependencies
package-prepare      Show installation information of package
sync                 Synchronize Manually Installed Packages
sync-pear            Synchronize already Installed Packages by pear
uninstall            Un-install Package  
upgrade              Upgrade Package     
upgrade-all          Upgrade All Packages
If you get the command list above, everything works as it should.
Now you can download your extension, like

orion:~ user$ ./mage install http://connect20.magentocommerce.com/community extension_name

Thursday, July 10, 2014

How to get a list of manufacturers ids

To get an array of the manufacturer names & ids use the following code:


$product = Mage::getModel('catalog/product');
$attributes =Mage::getResourceModel('eav/entity_attribute_collection')
  ->setEntityTypeFilter($product->getResource()->getTypeId())
  ->addFieldToFilter('attribute_code', 'manufacturer');
$attribute = $attributes->getFirstItem()
  ->setEntity($product->getResource());
$manufacturers = $attribute->getSource()->getAllOptions(false);


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


the result looks similar to this:

magento > print_r($manufacturers);
Array
(
  [0] => Array
  (
   [value] => 6
   [label] => Manfucturer A
  )
  [1] => Array
  (
   [value] => 20
   [label] => Manfucturer B
  )
  [2] => Array
  (
   [value] => 21
   [label] => Manfucturer C
  )
  [3] => Array
  (
   [value] => 27
   [label] => Manfucturer D
  )
)
magento >


Saturday, July 5, 2014

Installing JPEGTRAN with drop support on a Mac

JPEGTRAN is needed for jpeg optimizations, especially if you like to do some web scraping and
the images are only accessible behind zoomify. But this is something for another post.

Installation 

  1. Get the source code from here: http://www.ijg.org/
  2. Download jpegsrc.v9a.tar.gz
  3. Get the patch from here: http://jpegclub.org/jpegtran/
  4. Download droppatch.v9a.tar.gz
  5. Uncompress the package, i.e. tar -xzvf /tmp/jpegsrc.v9a.tar.gz
  6. Uncompress the patch to the same directory as jpegtran, i.e. 
    tar -C jpeg-9a -xzvf droppatch.v9a.tar.gz 
  7. Go to the directory that contains the uncompressed original code with the patch, i.e 
    cd /tmp/jpeg-9a
  8. if you don't want to install it to the default locations, add the --prefix arg otherwise without, ./configure --prefix=/some/installation/path/ 
  9. make && make install (you need sudo for installation to default path)
Done!




Thursday, July 3, 2014

How to disable Paypal logo in Magento 1.9.x

had some difficulty to find valid information on how to disable Paypal logo in Magento 1.9.x.
For older versions, there was a setting in Configuration/Payment method/paypal something and enable/disable logo in frontend.

The proper way is to disable it in your local.xml file located
[your_magento_installation]app/design/frontend/[your_template]/[your_package]/local.xml

add the remove tag within
<layout>
             <default>
                  [...]
                  <remove name="paypal.partner.right.logo"/>
                  [...]
             </default>
</layout>

if it doesn't work, there is always a lumberjack method... overwrite the default behaviour from the default template.

So copy from [your_magento_installation]app/design/frontend/base/default/template/paypal/partner/logo.phtml
to  [your_magento_installation]app/design/frontend/[your_template]/default/template/paypal/partner/logo.phtml

and remove the php code.



Dummy style but effective...

if you know the right way to do it over the admin panel, please let me know!
thx