2017/03/24

How to setup an event based sales rule, or - grant a discount if the customer experiences an error!

Increase conversion

Monitoring- and Analytictools like Google Analytics are great things to optimize your E-commerce system to sale more of your products.

But, wouldn't it be great to interact with the user right in the situation, when he is leaving the direct way to the checkout and maybe changing his mind?

To guide the user back on the way to a sale you can for example  apply a exclusive discount or put a free product to is cart - event based and automatically.

How to setup an event based sales rule

First off, you'll need to download the latest Notify module for Magento here: https://grafzahl.io/notify-module/

Now you are able to go through the easy install process, which is explained here: https://documentation.grafzahl.io/notify-module/doc/install-setup.html

At this point, we are very close to our goal.

Create a new notification

General settings

To do that, click on your Notify menu in the main navigation of your Adminpanel.

Now you'll see the common Magento grid where you can add a new Item with the upper right "New"-Button.

This page will load:
Notify General Tab
The Notify Configuration Item in the Magento Adminpanel. "General"-Tab.


Fill your Title and Description. These fields are not used for technical purpose. They are only for you to identify the notifications.

The switch "Active" is self-explaining. The transmission type defines, how your notifications or reports are send to you. The default is Email. More transmission types like Slack, SMS or Logfile are on our Todo-List.

The summary type option will give you the possibility to summarize any of the recorded issues of this notification:
  • Immediately: The issue will be sent immediately as it is recorded. The Report configuration will offer you Text-Templates to define the message which is composed for the specific issue.
  • Daily, Weekly, Monthly: The issues of every defined event-trigger will be summarized and reported in the selected frequence. The Report configuration will offer you different elements which you can configure to compose the collected data in any way you would like to see the issues.
Click Immediately here, to get notified immediately. This also is an indication for you, that your sales rule has been applied.

With the Store Multiselect field you can decide, in which shops this notification item is valid and collects issues.

Trigger selection

The trigger selection looks like this:
The Notify Event-Trigger Selection. The left column contains all Areas. When you click on an Area, the second column will list all available Event-Triggers for that Area.


You can select from some defined Trigge-Events that are available for Notify. The name of them are describing most likely when they are triggered and therefor creating a new issue.

The Trigger are devided into Areas. They are explaining where this Event will trigger, like "Admin Panel".
When you click on one of the Areas, you will get a list of all available Events in the second column.
Clicking on one of them will add it to the right column. This indicates that it is selected for your Notification.

We want to apply a sales rule, when the customer opens a 404-Page. Therefor we select "Error" and "User gets 404 page" to get notified every time a customer opens a URL in our online store which does not exists.

To go on with the Rule Conditions and Report Configuration you'll need to save the Notification by now. This enables the Trigger selection and reveal the fields for the Triggers in the next Tabs.

Rules configuration

This Tab will let you filter the issues on information that is available in the situation of the issue. At the moment there are no filter options for the 404-Error-Trigger. So we can skip that.

Report settings

This is where you define what is reported in case of an issue. You will see a Textarea field with available Placeholders on the right. You can type any text you like and add those placeholders to create a message that is most informative for you.

Define your message for that Issue and fill it with the available Placeholders on the right. Click "Save Template" to submit the Template text. Then click "Save and Continue Edit".

Define the automated Action

This configuration is the most interesting. Here you can define what will happen if the issue was reported.




You will get the form above, if you enable the switch on the right. Select the option "Apply a sales rule" on the left side of the form.

In the loaded options on the right, select the sales rule which should be applied to the customers cart after he got a 404 Page.

Now you are ready.

2017/03/16

How to get an Email Notification for every Exception

Magentos error handling is pretty good. PHP Errors, Warnings or Notices are written into the system.log file and Exceptions are caught by Magento to show the customer a (not very good looking) page and write the error stack into a file as a report for the system administrator.

Ok, but how often are those exceptions coming up? Sure - you tested your ecommerce store very well to prevent those situations for your customers, but you know - its software!

Even the best tested site is not 100% bug-free.
Some customers may contact you and report their error but the most wont. And you really want to fix any Exception that is thrown by your shop.

So here is the solution: We'll integrate an instant notification or a daily report for any exception that is occuring on your Magento webshop.

With the help of our module Notify for Magento, that is very easy.

The module is a notification system that also can create summaries of the collected data and send them as recurring reports.

To be able to configure a notification or report for a thrown exception, we only need to insert a little piece of code into the exception page:


// prepare data object
$data = Mage::getSingleton('notify/issue_data');
$data->setTrigger('customer_experienced_exception')
    ->setArea('error')
    ->setBacktrace(htmlspecialchars($this->reportData[1]))
    ->setError($this->reportData[0])
    ->setReportId($this->reportId);
// trigger customer_experienced_exception
Mage::helper('notify/notification')->recordIssue($data);

Put this on the end of the file /errors/default/report.phtml.

We have to setup this new trigger for Notify to be able to configure a notification or report in the admin panel.

Open your notify.xml of your custom module that extends Notify or of Grafzahl_Notify module (not recommended).

Add the configuration for our new trigger (config/notify/trigger - node):


<error label="Error">
    <customer_experienced_exception label="User gets an Exception">
        <enable>1</enable>
        <passed_data>
            <error type="string">Error Message</error>
            <report_id type="string">Report Id</report_id>
            <backtrace type="string">Backtrace</backtrace>
        </passed_data>
        <renderer>notify/renderer_error_customerExperiencedException</renderer>
    </customer_experienced_exception>
</error>


Create the renderer class and you are done! The renderer class is only a simple class with extends the renderer abstract like the following:


/**
 * Grafzahl_Notify
 *
 * @category    Grafzahl
 * @package     Grafzahl_Notify
 * @copyright   Copyright (c) 2017 Grafzahl (https://grafzahl.io)
 * @license     https://grafzahl.io/license
 */
class Grafzahl_Notify_Block_Renderer_Error_CustomerExperiencedException
    extends Grafzahl_Notify_Block_Renderer_Abstract
{

}

It is recommended to create those files in your custom module that is extending the default Notify module.

Configure the notification

Now you can configure the report or notification in the admin panel. Read about that in our documenation.

To get this powerful notification module for your Magento store, visit https://grafzahl.io/notify-module

2016/11/20

Fatal Error in Magento: Class Mage_YourModule_Helper_Data not found in app/Mage.php on line 547

This is an error, which I faced some times after late nights in code.

It is a very bad error message, because it does not explain what´s really going wrong. This is also problem, because you do not know where to search for the error. Especially if you done some more changes in your code and you need to go through every single change with your GIT diff.

The reason of this error is, that you or another module of your Magento installations accesses a configuration from the XML files which is not valid. In most cases it is a typing error in a recent added or changed config section.

The solution


To solve this error: go carefully through your XML configuration in config.xml, adminhtml.xml, system.xml or any layout XML file and find the mistyping node.
It could be a close-Tag which is typed different from the open-Tag or vise versa. Or it could be a missing close-Tag.

Sometimes some of this mistakes could also lead to strange behaviour like your custom module is not activated anymore and as a result controllers actions are not working which will cause 404 on routes that were working before this mistakes.

I hope this little hint will save somebody ours of debugging!

2016/11/07

How to display M2E order data or a product attribute in the Magento order grid

Lets assume you wish to extend your Magento order grid in the admin panel with a new column that displays an attribute of the ordered products to get a better overview for your shipping processes.

The most small or midsize online shops use the order grid to manage their shipments with export bulk methods or directly through the grid table.

Therefor it is comfortable to have all information needed in this grid. In this little how-to we will extend our grid to display order information of the m2e module and another example with a product attribute. You can customize the code to get any other attribute.

Create a local module to extend the order grid

We assume that you know how to create a new local module.
Create a proper config/etc.xml.

First create the Grid.php block which will override the core block in the default sales module. Copy your orignal Mage_Adminhtml_Block_Sales_Order_Grid an paste it into your new file. Then it might look like the following class.
Yourname_Yourmodule_Block_Adminhtml_Sales_Order_Grid:

class Yourname_Yourmodule_Block_Adminhtml_Sales_Order_Grid
    extends Mage_Adminhtml_Block_Widget_Grid
{

    public function __construct()
    {
        parent::__construct();
        $this->setId('sales_order_grid');
        $this->setUseAjax(true);
        $this->setDefaultSort('created_at');
        $this->setDefaultDir('DESC');
        $this->setSaveParametersInSession(true);
    }

    /**
     * Retrieve collection class
     *
     * @return string
     */
    protected function _getCollectionClass()
    {
        return 'sales/order_grid_collection';
    }

    protected function _prepareCollection()
    {
        $collection = Mage::getResourceModel($this->_getCollectionClass());

        $this->setCollection($collection);
        return parent::_prepareCollection();
    }

    protected function _prepareColumns()
    {
        $this->addColumn('real_order_id', array(
            'header'=> Mage::helper('sales')->__('Order #'),
            'width' => '80px',
            'type'  => 'text',
            'index' => 'increment_id',
        ));

        if (!Mage::app()->isSingleStoreMode()) {
            $this->addColumn('store_id', array(
                'header'    => Mage::helper('sales')->__('Purchased From (Store)'),
                'index'     => 'store_id',
                'type'      => 'store',
                'store_view'=> true,
                'display_deleted' => true,
            ));
        }

        $this->addColumn('created_at', array(
            'header' => Mage::helper('sales')->__('Purchased On'),
            'index' => 'created_at',
            'type' => 'datetime',
            'width' => '100px',
        ));

        $this->addColumn('billing_name', array(
            'header' => Mage::helper('sales')->__('Bill to Name'),
            'index' => 'billing_name',
        ));

        $this->addColumn('shipping_name', array(
            'header' => Mage::helper('sales')->__('Ship to Name'),
            'index' => 'shipping_name',
        ));

        $this->addColumn('base_grand_total', array(
            'header' => Mage::helper('sales')->__('G.T. (Base)'),
            'index' => 'base_grand_total',
            'type'  => 'currency',
            'currency' => 'base_currency_code',
        ));

        $this->addColumn('grand_total', array(
            'header' => Mage::helper('sales')->__('G.T. (Purchased)'),
            'index' => 'grand_total',
            'type'  => 'currency',
            'currency' => 'order_currency_code',
        ));

        $this->addColumn('order_type', array(
            'header' => Mage::helper('sales')->__('Order Type'),
            'width' => '100px',
            'align' => 'left',
            'index' => 'order_type',
            'renderer' => 'yourmodule/adminhtml_sales_grid_renderer_m2eAttribute',
            'filter_condition_callback' => array($this, '_filterM2eConditionCallback')
        ));

        $this->addColumn('status', array(
            'header' => Mage::helper('sales')->__('Status'),
            'index' => 'status',
            'type'  => 'options',
            'width' => '70px',
            'options' => Mage::getSingleton('sales/order_config')->getStatuses(),
        ));

        if (Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/view')) {
            $this->addColumn('action',
                array(
                    'header'    => Mage::helper('sales')->__('Action'),
                    'width'     => '50px',
                    'type'      => 'action',
                    'getter'     => 'getId',
                    'actions'   => array(
                        array(
                            'caption' => Mage::helper('sales')->__('View'),
                            'url'     => array('base'=>'*/sales_order/view'),
                            'field'   => 'order_id'
                        )
                    ),
                    'filter'    => false,
                    'sortable'  => false,
                    'index'     => 'stores',
                    'is_system' => true,
            ));
        }

        $this->addRssList('rss/order/new', Mage::helper('sales')->__('New Order RSS'));

        $this->addExportType('*/*/exportCsv', Mage::helper('sales')->__('CSV'));
        $this->addExportType('*/*/exportExcel', Mage::helper('sales')->__('Excel XML'));

        return parent::_prepareColumns();
    }

    protected function _prepareMassaction()
    {
        $this->setMassactionIdField('entity_id');
        $this->getMassactionBlock()->setFormFieldName('order_ids');
        $this->getMassactionBlock()->setUseSelectAll(false);

        if (Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/cancel')) {
            $this->getMassactionBlock()->addItem('cancel_order', array(
                 'label'=> Mage::helper('sales')->__('Cancel'),
                 'url'  => $this->getUrl('*/sales_order/massCancel'),
            ));
        }

        if (Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/hold')) {
            $this->getMassactionBlock()->addItem('hold_order', array(
                 'label'=> Mage::helper('sales')->__('Hold'),
                 'url'  => $this->getUrl('*/sales_order/massHold'),
            ));
        }

        if (Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/unhold')) {
            $this->getMassactionBlock()->addItem('unhold_order', array(
                 'label'=> Mage::helper('sales')->__('Unhold'),
                 'url'  => $this->getUrl('*/sales_order/massUnhold'),
            ));
        }

        $this->getMassactionBlock()->addItem('pdfinvoices_order', array(
             'label'=> Mage::helper('sales')->__('Print Invoices'),
             'url'  => $this->getUrl('*/sales_order/pdfinvoices'),
        ));

        $this->getMassactionBlock()->addItem('pdfshipments_order', array(
             'label'=> Mage::helper('sales')->__('Print Packingslips'),
             'url'  => $this->getUrl('*/sales_order/pdfshipments'),
        ));

        $this->getMassactionBlock()->addItem('pdfcreditmemos_order', array(
             'label'=> Mage::helper('sales')->__('Print Credit Memos'),
             'url'  => $this->getUrl('*/sales_order/pdfcreditmemos'),
        ));

        $this->getMassactionBlock()->addItem('pdfdocs_order', array(
             'label'=> Mage::helper('sales')->__('Print All'),
             'url'  => $this->getUrl('*/sales_order/pdfdocs'),
        ));

        $this->getMassactionBlock()->addItem('print_shipping_label', array(
             'label'=> Mage::helper('sales')->__('Print Shipping Labels'),
             'url'  => $this->getUrl('*/sales_order_shipment/massPrintShippingLabel'),
        ));

        return $this;
    }

    public function getRowUrl($row)
    {
        if (Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/view')) {
            return $this->getUrl('*/sales_order/view', array('order_id' => $row->getId()));
        }
        return false;
    }

    public function getGridUrl()
    {
        return $this->getUrl('*/*/grid', array('_current'=>true));
    }

    /**
     * filter callback to find the order_type
     * of orders through m2e (amazon, ebay, ...)
     *
     * @param object $collection
     * @param object $column
     * @return Yourname_Yourmodule_Block_Adminhtml_Sales_Order_Grid
     */
    public function _filterM2eConditionCallback($collection, $column) {
        if (!$value = $column->getFilter()->getValue()) {
            return $this;
        }
        if (!empty($value) && strtolower($value) != 'magento') {
            $this->getCollection()->getSelect()
                ->join(
                    'm2epro_order',
                    'main_table.entity_id=m2epro_order.magento_order_id',
                    array('component_mode')
                    )
                ->where(
                 'm2epro_order.component_mode = "' . strtolower($value) . '"');
        } elseif(strtolower($value) == 'magento') {
            $this->getCollection()->getSelect()
                ->join(
                    'm2epro_order',
                    'main_table.entity_id=m2epro_order.magento_order_id',
                    array('component_mode')
                    )
                ->where(
                 'm2epro_order.component_mode = NULL');
        }

        return $this;
    }
}

What has changed to the original class, is the following snippet to add a new column and the relating callback method named _filterM2eConditionCallback.


$this->addColumn('order_type', array(
    'header' => Mage::helper('sales')->__('Order Type'),
    'width' => '100px',
    'align' => 'left',
    'index' => 'order_type',
    'renderer' => 'yourmodule/adminhtml_sales_grid_renderer_m2eAttribute',
    'filter_condition_callback' => array($this, '_filterM2eConditionCallback')
));

As you can see, the column has a filter_condition_callback which manage the select to filter through the right tables. The renderer block is the other thing that is special about our column. The renderer will take care of the value that is displayed in the column. Lets take a look at the renderer:
Yourname_Yourmodule_Block_Adminhtml_Sales_Grid_Renderer_M2eAttribute


class Yourname_Yourmodule_Block_Adminhtml_Sales_Grid_Renderer_M2eAttribute
    extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract
{
    public function render(Varien_Object $row)
    {
        // do whatever you need, to display your data
        // get the id of the row order data
        $orderId = $row->getEntityId();
        // get the related m2e order data
        $orders = Mage::getModel('M2ePro/Order')
            ->getCollection()
            ->addFieldToSelect('component_mode')
            ->addFieldToFilter('magento_order_id', $orderId);

        if($orders) {
            $data = $orders->getFirstItem()->getData();
            if(isset($data['component_mode'])) {
                return ucfirst($data['component_mode']);
            }
        }
        // return the string "magento" if there is no m2e relation
        return 'Magento';
    }
}

Now we look at the filter callback:


/**
 * filter callback to find the order_type
 * of orders through m2e (amazon, ebay, ...)
 *
 * @param object $collection
 * @param object $column
 * @return Yourname_Yourmodule_Block_Adminhtml_Sales_Order_Grid
 */
public function _filterM2eConditionCallback($collection, $column) {
    if (!$value = $column->getFilter()->getValue()) {
        return $this;
    }
    if (!empty($value) && strtolower($value) != 'magento') {
        $this->getCollection()->getSelect()
            // join to the m2mepro order table and select component_mode
            ->join(
                'm2epro_order',
                'main_table.entity_id=m2epro_order.magento_order_id',
                array('component_mode')
                )
            ->where(
             'm2epro_order.component_mode = "' . strtolower($value) . '"');
    } elseif(strtolower($value) == 'magento') {
        $this->getCollection()->getSelect()
            ->join(
                'm2epro_order',
                'main_table.entity_id=m2epro_order.magento_order_id',
                array('component_mode')
                )
            ->where(
             'm2epro_order.component_mode = NULL');
    }

    return $this;
}


Another example to select a product attribute in the order grid

This is an example of a filter callback to get data of a specific product attribute:


public function _filterShippingCostConditionCallback($collection, $column) {
    if (!$value = $column->getFilter()->getValue()) {
        return $this;
    }
    if (!empty($value)) {
        $this->getCollection()->getSelect()
            // join to to the flat order item table
            ->join(
                'sales_flat_order_item',
                'main_table.entity_id=sales_flat_order_item.order_id',
                array('product_id')
                )
            // join to the value table for the products
            ->join(
                'catalog_product_entity_decimal',
                'sales_flat_order_item.product_id=catalog_product_entity_decimal.entity_id',
                array('value')
            )
            // where condition to select the value
            ->where(
                // attribute_id relates to your attribute
             'catalog_product_entity_decimal.value = "' . ($value) . '" AND catalog_product_entity_decimal.attribute_id=247');
    }
    return $this;
}


The first join will get the table of the order items, to get the relation between the order and the ordered items. From there we join the second table from the product id of the previously selected items. Here you have to check which of the eav table has your attribute value. If you have an attribute with a text field. catalog_product_entity_varchar will be the right one.

In the condition you have to adapt the attribute_id to the one you are trying to select.

Now go to your adminpanel and check the result.

2016/11/02

6 steps to create a custom admin controller with a entity grid in Magento

If you don´t do this all day, you might need some tiny reminder on how to create a customer resource entity in Magento. This post will give you a list of the things you need to do  to list your entity data in the admin panel.

Here it is:

1. Setting up the config.xml

Create the common config nodes like helpers, blocks, models. In the following is only written what is relevant for your entities (this belongs to your global node):

<models>
 <modulename>
   <class>Vendor_ModuleName_Model</class>
   <resourceModel>modulename_resource</resourceModel>
 </modulename>

 <modulename_resource>
   <class>Vendor_ModuleName_Model_Resource</class>
   <entities>
     <dataset>
       <table>modulename_dataset</table>
     </dataset>
   </entities>
 </modulename_resource>
</models>
<resources>
 <modulename_setup>
   <setup>
     <module>Vendor_ModuleName</module>
   </setup>
 </modulename_setup>
</resources>

modulename is the key for our model name. It is derived from our module name: ModuleName. Our entity will be called dataset and is located in the database within the table modulename_dataset.
The node is defining what resource model to use under the key modulename_resource which you can use in Mage methods like 

Mage::getResourceModel('modulename_dataset')


This is related to the resource definition (under node ) in this config with the classname and its entities (in this case dataset).

The node in the last lines are preparing our setup class where you define the SQL to create the tables for our entity.

2. Setting up the SQL Setup

Create a new file in your modules folder:  sql/modulename_setup/install-0.1.0.php (the last folder name is defined by our previous set node. You can do a lot of magic in this setup file but for simplicity we just will create a new table.


/* @var $installer Mage_Core_Model_Resource_Setup */
$installer = $this;
$installer->startSetup();
$installer->run("

DROP TABLE IF EXISTS {$this->getTable('modulename_dataset')};
CREATE TABLE {$this->getTable('modulename_dataset')} (
  `id` int(255) NOT NULL auto_increment,
  `anyfield` int(255) NOT NULL,
  `text` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Your own table';

$installer->endSetup();


The relation for $this->getTable() we defined in our config above in the node
.
This file is executed when you access your adminpanel the first time after you created the module and the module you created has the exact version number as in the filename.

3. Create the models and resource models

Create the dataset model in app/code/community/Vendor/ModuleName/Model/Dataset.php

class Vendor_ModuleName_Model_Dataset
    extends Mage_Core_Model_Abstract
{
    const CACHE_TAG             = 'modulename_dataset';
    protected $_cacheTag        = 'modulename_dataset';
    protected $_append_class    = '';

    protected function _construct()
    {
        $this->_init('modulename/dataset');
    }
}

The string in the _init method is related to resouce model key and resource name.

Info: It has to be exactly the same as your classname of the model - case sensitive! If you name it DataSet.php (classname = Vendor_ModuleName_Model_DataSet) then you have to give _init this string: 'modulename/dataSet'. And you have to update your part in the config.xml!

Create the dataset resource model in app/code/community/Vendor/ModuleName/Model/Resource/Dataset.php

class Vendor_ModuleName_Model_Resource_Dataset
    extends Mage_Core_Model_Resource_Db_Abstract
{
    /**
     * Initialize resource
     *
     */
    protected function _construct()
    {
        $this->_init('modulename/dataset', 'id');
    }
}

The same here. The second parameter in _init belongs to the id field in your table.

Create the dataset resource collection in app/code/community/Vendor/ModuleName/Model/Resource/Dataset/Collection.php

class Vendor_ModuleName_Model_Resource_Dataset_Collection
    extends Mage_Core_Model_Resource_Db_Collection_Abstract
{
    /**
     * construct
     */
    public function _construct()
    {
        $this->_init('modulename/dataset');
    }
}


4. Create the adminhtml controller


class Vendor_ModuleName_Adminhtml_ModuleName_DatasetController
    extends Mage_Adminhtml_Controller_Action
{
    protected function _construct()
    {
        // Define module dependent translate
        $this->setUsedModuleName('Vendor_ModuleName');
    }
    /**
     * Init actions
     *
     * @return Vendor_ModuleName_Adminhtml_ModuleName_DatasetController
     */
    protected function _initAction()
    {
        return $this;
    }

    /**
     * configure action
     * includes the listing of configuration
     * items
     */
    public function indexAction()
    {
        $this->_title($this->__('ModuleName'));
        $this->loadLayout();

        $this->_addContent($this->getLayout()->createBlock('modulename/adminhtml_dataset'));

        $this->renderLayout();
    }
}


The block we create in the index action, we will define in the next step. This for sure requires the node in you configuration, which we did not wrote here.

5. Create the blocks

class Vendor_ModuleName_Block_Adminhtml_Dataset
    extends Mage_Adminhtml_Block_Widget_Container
{
    public function __construct()
    {
        parent::__construct();
        $this->setTemplate('modulename/dataset.phtml');
    }

    /**
     * Prepare button and grid
     *
     * @return Vendor_ModuleName_Block_Adminhtml_Dataset
     */
    protected function _prepareLayout()
    {
        $this->setChild('grid', $this->getLayout()->createBlock('modulename/adminhtml_dataset_grid', 'adminhtml_modulename_dataset.grid'));
        return parent::_prepareLayout();
    }

    /**
     * Render grid
     *
     * @return string
     */
    public function getGridHtml()
    {
        return $this->getChildHtml('grid');
    }
}


Create the grid block, which we created in this block.

class Vendor_ModuleName_Block_Adminhtml_Dataset_Grid
    extends Mage_Adminhtml_Block_Widget_Grid
{
    /**
     * construct
     */
    public function __construct()
    {
        parent::__construct();
        $this->setId('moduleNameGrid');
        $this->setDefaultSort('id');
        $this->setSaveParametersInSession(true);
        $this->setDefaultDir('DESC');
    }

    /**
     * prepare collection
     *
     * @return Vendor_ModuleName_Block_Adminhtml_Dataset_Grid
     */
    protected function _prepareCollection()
    {
        $collection = Mage::getModel('modulename/dataset')->getCollection();
        $this->setCollection($collection);
        parent::_prepareCollection();

        return $this;
    }

    /**
     * prepare Columns
     */
    public function _prepareColumns()
    {
        $baseUrl = $this->getUrl();

        $this->addColumn('id',array(
            'header'        => Mage::helper('modulename')->__('Id'),
            'align'         => 'center',
            'index'         => 'id',
            'width'         => '1%'
        ));
        $this->addColumn('text',array(
            'header'        => Mage::helper('modulename')->__('Text'),
            'align'         => 'left',
            'index'         => 'type',
            'width'         => '29%',
            'filter_index'  => 'type'
        ));
        return parent::_prepareColumns();
    }

    public function getRowUrl($row) {
        // only works if you create that controller action
        return $this->getUrl('*/*/edit',array('id' => $row->getEntityId()));
    }
}


This creates the grid for you. You can define which fields are displayed in the grid.

6. Create the adminhtml template


<div class="content-header">
<table cellspacing="0">
<tr>
<td style="width:50%;"><h3 class="icon-head head-dataset"><?php echo />Mage::helper('modulename')->__('See data') ?></h3></td>
<td class="a-right">
</td>
</tr>
</table>
</div>
<div>
<?php echo $this->getGridHtml() ?>
</div>

This was a summery of what to do to make the grid work. Some steps, like setting up the locale files or helper are not explained in this steps, but as a professional developer you know how to do this.

2016/11/01

How a german online-shop reduced customer questions by 80%

A customer of our new module Advanced Category Navigation has a shop layout, that is nearly the same as the default Magento template.

The products of this store need a little more information, because all products are used as boxes for specific tires and rims with several measurements.

Before this shop was using the Advanced Category Navigation they were receiving many phone calls from customers every day who needed help to find the right product.
After the module was installed in his shop the the shop owner was exited, because there were almost no more phone calls from his customers.

Our customer is a great example for how to use the Advanced Category Navigation to get a great overview with brief descriptions for your products.


The Magento module helped his customers to find the correct product by adding descriptions to the categories, using categories as groups and by displaying the products right next to the categories. This will give a better summary to the user. This let the user decide much faster, which of the products are best for him.

You will find our module Grafzahl AdvancedCategoryNavigation in our shop or on magentocommerce.com/connect.

We also provide a detailed documentation for an easy installation and fast setup of the module.

Purchase AdvancedCategoryNavigation in our Store.

2016/10/31

Secure your shop against brute force attacks!

Magento published a post which says that some URLs in your Magento installation may vulnerable to brute force attacks and they talk about an increasing number of attacks on Magento stores.

Secure /admin path

This URL leads you to your adminpanel with a default Magento installation. You should change that path URI to something cryptic to increase your security level.
You can do that in your Magento system configuration: Advanced > Admin.
Additionally you can secure it by denying all access by .htaccess except your IP:

order deny,allow
deny from all
allow from x.x.x.x

 

Secure /rss/catalog and /rss/order

This can be done by adding new rewrite conditions to your .htacces.
Add these lines to your /magento_root/.htaccess:

## block access to admin rss feed
    RewriteCond %{REQUEST_URI} ^.*/rss/catalog[OR,NC]
    RewriteCond %{REQUEST_URI} ^.*/rss/order[NC]
    RewriteRule ^(.*)$ https://%{HTTP_HOST}/ [R=302,L]

This will redirect the /rss/ feed directly to your homepage. If you have no SSL change the last line to

RewriteRule ^(.*)$ http://%{HTTP_HOST}/ [R=302,L]

But you should add SSL to your shop.

Secure /downloader 

This path is used to install or uninstall modules in your shop. To secure this place from unauthorized access we have to deny access with the .htaccess in the /downloader folder. Add these lines to your /downloader/.htaccess file:

order deny,allow
deny from all

If you want to use the downloader, you can temporary comment these lines or whitelist your IP, if you add this line:

allow from x.x.x.x

Check other vulnerabilities.

To be sure you secured all known vulnerabilities, this page is good to check states of all available patches: https://www.magereport.com/