Send New Product Review Notification Email

January 13, 2015  |  1 Comments  |  by Raj (MagePsycho)  |  Latest, Magento

This is probably a feature which store owner might be looking for.
As of now store owner has to check for new product reviews from Magento backend in frequent manner.

Won’t it be better if admin gets a instant email whenever a new product review is posted? So that whatever action needs to be carried out with the pending review can be done quickly. You don’t need to buy expensive extension in order to get job done. Here I will be sharing a simple trick to achieve this.

1. Crate a skeleton module(for example: MagePsycho_Reviewnotifier) and register the event: review_save_after
File: app/code/local/MagePsycho/Reviewnotifier/etc/config.xml

<frontend>
    ...
    <events>
        <review_save_after>
            <observers>
                <magePsycho_reviewnotifier_review_save_after>
                    <type>singleton</type>
                    <class>magePsycho_reviewnotifier/observer</class>
                    <method>reviewSaveAfter</method>
                </magePsycho_reviewnotifier_review_save_after>
            </observers>
        </review_save_after>
    </events>
    ...
</frontend>

2. Implement the observer model
File: app/code/local/MagePsycho/Reviewnotifier/Model/Observer.php

<?php

/**
 * Observer Model for Review Notifier
 * 
 * @author MagePsycho<magepsycho@gmail.com>
 * @package MagePsycho_Reviewnotifier
 * Class MagePsycho_Reviewnotifier_Model_Observer
 */
class MagePsycho_Reviewnotifier_Model_Observer
{
    /**
     * Send notification email when product review is posted
     *
     * @param Varien_Event_Observer $observer
     */
    public function reviewSaveAfter(Varien_Event_Observer $observer)
    {
        $review = $observer->object;
        if ($review) {
            $emails     = array('info@yourstore.com', 'email1@yourstore.com'); #Edit admin emails
            try {
                //@todo - use configurable email templates
                $this->_sendNotificationEmail($emails, $review);
            } catch (Exception $e) {
                Mage::logException($e);
            }
        } else {
            Mage::log('ERROR::UNABLE TO LOAD REVIEW');
        }
    }

    protected function _sendNotificationEmail($emails, $review)
    {
        if (count($emails)) {
            $product        = Mage::getModel('catalog/product')->load($review->getEntityPkValue());
            $starRatings    = array_values($review->getRatings());

            $body = 'New product review has been posted!<br /><br />';
            $body .= 'Customer Name: ' . $review->getNickname() . '<br />';
            $body .= 'Product: ' . sprintf('<a href="%s" target="_blank">%s</a>', $product->getProductUrl(), $product->getName()) . '<br />';
            $body .= 'Star Rating: ' . (isset($starRatings[0]) ? $starRatings[0] : 'N/A') . '<br />';
            $body .= 'Review Title: ' . $review->getTitle() . '<br />';
            $body .= 'Review Description: ' . $review->getDetail() . '<br />';

            foreach ($emails as $toEmail) {
                $mail = Mage::getModel('core/email');
                $mail->setToName('YourStore Admin');
                $mail->setToEmail($toEmail);
                $mail->setBody($body);
                $mail->setSubject('YourStore: New Product Review');
                $mail->setFromEmail('donotreply@yourstore.com');
                $mail->setFromName("YourStore");
                $mail->setType('html');

                try {
                    $mail->send();
                }
                catch (Exception $e) {
                    Mage::logException($e);
                }
            }
        }
    }
}

Please leave your comments if it works for you or if you have any queries.

Debugging Tips: Filename cannot be empty

November 21, 2014  |  No Comments  |  by Raj (MagePsycho)  |  Latest, Magento

Problem

You may have seen this kind of error frequently logged in your Magento log file var/log/system.log:

Warning: include(): Filename cannot be empty in /app/code/core/Mage/Core/Block/Template.php on line xxx

This error means Magento is trying to include a template block with an empty string set as it’s template.

Solution

In order to find which template file is missing, simply edit the code in Mage_Core_Block_Template::fetchView() as:

public function fetchView($fileName)
{
    ...
    try {
        $includeFilePath = realpath($this->_viewDir . DS . $fileName);
        if (strpos($includeFilePath, realpath($this->_viewDir)) === 0 || $this->_getAllowSymlinks()) {
            /* edit - start */
            if (!is_file($includeFilePath)) {
                Mage::log($fileName, null, 'missing-phtml.log', true);
            }
            /* edit - end */
            include $includeFilePath;
        } else {
            Mage::log('Not valid template file:'.$fileName, Zend_Log::CRIT, null, null, true);
        }

    } catch (Exception $e) {
        ob_get_clean();
        throw $e;
    }
    ...
}

You will see the missing .phtml filenames being logged. And search for that name in your layout xmls which will show you where it went wrong.
Happy Debugging!

Welcome Mass Importer Pro: Price Importer Ver 1.1.0 with new features

August 8, 2014  |  1 Comments  |  by Raj (MagePsycho)  |  Latest, Magento, Updates

Mass Importer Pro: Price Importer is the fastest price import tool available for Magento CE/EE.
Recently MagePsycho team released the most awaited features in Mass Importer Pro: Price Importer Ver 1.1.0

ChangeLog (0.1.5 – 1.1.0)

  • Compatible with Magento CE 1.3.x – 1.9.x & EE 1.12.x – 1.14.x
  • Added cron job based price importing with logging feature
  • Added shell/command line based price importing with logging feature
  • Added price rounding feature (normal rounding, rounding to nearest)
  • Added relative(fixed & percentage) pricing for special, tier & group prices.
  • Changed store view based pricing import to website based(as prices have either global or website level scope)
  • Added store wise price export
  • Added option for deleting special_price, special_from_date, special_to_date by marking the value with ‘x’ or ‘X’
  • Added button for checking csv file format (beside of Price Import button)
  • Added Change log file: Changelog.txt
  • Added Uninstallation file: Uninstall.txt
  • Refactored the code

New Features

1. Cron Job based price importing

To configure this, go to Mass Importer Pro > Manage Settings > Price Settings
You will see the following setting:

Price Importer: Cron Settings

Price Importer: Cron Settings

FieldDescription
Enable CronYes
File Import DirectoryPath from where CSV files will be imported via cron.
Default: var/magepsycho/massimporterpro/price_importerr/cron
Process Imported FilesWhat to do after the files are imported?
Available options are:
No Action
Move To Archive Folder
Delete
Start TimeAt what time cron will run.
If ‘Start Time’ = 00 00 00 and ‘Frequency’ = Daily then equivalent cron expression will be 0 0 * * * (which means cron will be run once a day at midnight)
REF: http://en.wikipedia.org/wiki/Cron
FrequencyFrequency at which cron will run. Options:
Daily
Weekly
Monthly
Error Email RecipientError email will be sent to this address in case of failure
Error Email SenderError Email Sender
Error Email TemplateEmail Template

Notes:
Make sure that cron is enabled for your Magento store.
Refer to the following article for more info:
http://www.magentocommerce.com/wiki/1_-_installation_and_configuration/how_to_setup_a_cron_job

2. Shell/Command line based price importing

There is no any backend settings for Shell based price importing.
You just have to open the terminal and run the following commands.
1. Go to Magento root directory:

cd /path/to/your/magento/root

2. Price Importer help command:

php -f shell/priceimporter.php --help

3. Run price import (without reindexing)

php -f shell/priceimporter.php -- -import "path/to/csv/file.csv"

4. Run price import (with reindexing)

php -f shell/priceimporter.php -- -import "path/to/csv/file.csv" -reindex
Command line price import (with reindexing)

Command line price import (with reindexing)

Command line price import (without reindexing)

Command line price import (without reindexing)

So far in Mass Importer Pro, Prices can be imported via Web Forms, Cron Jobs & Command Line. From wherever you run import, You can view the import history from menu ‘Mass Importer Pro’ > ‘Price Importer’ > ‘Import History’ Tab > Filter the results by ‘Imported Via’.

Price Importer: Import History

Price Importer: Import History

3. Price rounding feature

There are three types of options for price rounding:

Price Importer: Price Rounding

Price Importer: Price Rounding

Rounding TypeDescription
No RoundingNo action with the price
Round NormallyUses php’s round() function. Examples:
9.43 -> 9.00
9.63 -> 10.00
Round to NearestYou have to set Rounding value for this. Examples:
9.43 -> 9.00 + (If Rounding Value = 0.99) = 9.99
9.43 -> 9.00 + (If Rounding Value = 0.50) = 9.50
This option is useful if you want to update your prices ending with .99, .50 etc.

4. Relative(fixed & percentage) based price importing

You can self increment or decrement the prices by fixed value or percentage. Also you can set ‘special_price’, ‘tier_price’ & ‘group_price’ to be certain percentage of ‘price’.
Valid Examples:

ValueDescription
10Fixed Value
+10Increment current value by 10
-10Decrement current value by 10
10%10% of base price (note that there is no any sign in front)
Note: This applies only for price types: special_price, tier_price & group_price
+10%Increment current price by 10%
 -10% Decrement current price by 10%

5. Option to validate your CSV data

You can validate the CSV data before importing any prices.
Go to menu ‘Mass Importer Pro’ > ‘Price Importer’ > Select the import file > Click on the ‘Check Import’ button which will give you the check results.

Price Importer: Check Import Data

Price Importer: Check Import Data

6. Website wise price Import

Before Ver 1.1.0 You had to use combination of sku + store_id as unique identifier to update prices. Since Magento has either global or website level scope for prices, we have replaced ‘store_id’ by ‘website_id’.

In order to find the website_id, go to System > Manage Stores > Click on the related Website > Note the website_id from URL.
By default website_id = 0 (global)

7. Website wise price Export

You can export prices in format compatible with Mass Importer Pro from menu ‘Catalog’ > ‘Manage Products’.
How to export prices is clear from the below screenshot:

Price Importer: Export Prices

Price Importer: Export Prices

Also see: Importing regular, special, tier & group prices using ‘Mass Importer Pro: Price Importer’ Extension

Those were the major updates on newer version. Besides we have fixed some bugs & improved the functionality.
If you have any queries/issues regarding the extension, please do not hesitate to Contact Us.

Extend product_media.list Api to retrieve images always from configurable product

March 18, 2014  |  No Comments  |  by Raj (MagePsycho)  |  Latest, Magento, Web Service

Don’t you think updating same contents like media images and other attributes in all simple products of a configurable products is an overhead? For an easier management, it’s better to update frontend visible data only in configurable product. But this may led missing of data if someone makes an API call for simple product, for example: product_media.list

But we have a solution i.e. to retrieve the data (ex. images) always from the configurable product.
1. Define XML Rewrite

<global>
    <models>        
        <catalog>
            <rewrite>
                <product_attribute_media_api>MagePsycho_Apiextender_Model_Catalog_Product_Attribute_Media_Api</product_attribute_media_api>
            </rewrite>
        </catalog>
    </models>
</global>    

2. Extend Mage_Catalog_Model_Product_Attribute_Media_Api

<?php

/**
 * @category   MagePsycho
 * @package    MagePsycho_Apiextender
 * @author     magepsycho@gmail.com
 * @website    http://www.magepsycho.com
 * @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
class MagePsycho_Apiextender_Model_Catalog_Product_Attribute_Media_Api extends Mage_Catalog_Model_Product_Attribute_Media_Api
{

    /**
     * Retrieve product
     *
     * @param int|string $productId
     * @param string|int $store
     * @param  string $identifierType
     * @return Mage_Catalog_Model_Product
     */
    protected function _initProduct($productId, $store = null, $identifierType = null)
    {
        $product = Mage::helper('catalog/product')->getProduct($productId, $this->_getStoreId($store), $identifierType);

        //tweak $product if product is simple and part of configurable product
        if ($product && $product->getId()) {
            if ($product->getTypeId() == Mage_Catalog_Model_Product_Type::TYPE_SIMPLE) {
                $parentIds = Mage::getModel('catalog/product_type_configurable')->getParentIdsByChild($product->getId());
                if (isset($parentIds[0])) {
                    $product = Mage::helper('catalog/product')->getProduct($parentIds[0], $this->_getStoreId($store), $identifierType);
                }
            }
        }

        if (!$product->getId()) {
            $this->_fault('product_not_exists');
        }

        return $product;
    }
}

3. And you can test the API (using XML-RPC) as:

<?php
$mageFilename = '/path/to/app/Mage.php';
require_once $mageFilename;
Mage::setIsDeveloperMode(true);
ini_set('display_errors', 1);
umask(0);
Mage::app();

try{
    $client = new Zend_XmlRpc_Client('http://your-store.com/api/xmlrpc/');
    $session = $client->call('login', array('apiUser', 'apiPass'));

    $images = $client->call('call', array($session, 'product_media.list', array('some-sku')));
    Zend_Debug::dump($images);

    $client->call('endSession', array($session));
} catch (Exception $e) {
    echo 'API Error:: ' . $e->getMessage();
}

How to set product attribute values to take values from default store?

February 7, 2014  |  No Comments  |  by Raj (MagePsycho)  |  Magento, Mysql

Have you ever ticked a ‘Use Default Value’ checkbox for Price (in store view level) and noticed the changes in the database?

If you tick the ‘Use Default Value’ checkbox for any product attribute (say Price) in store view level and save it. This will delete the related row in the database for that attribute, for the specific product, for that store id. And attribute will now take the default value.

Lets explain this scenario in detail.

1st Approach

Use Default Value - Before Tick

Fig: Before Checkbox Tick

Use Default Value Checkbox

Fig: After Checkbox Tick

So if you want to set the default value for any attribute, you can just tick the checkbox and save the product.
This process can be overhead in case of hundreds of product.

2nd Approach

Alternatively, you can use SQL approach which is actually the background operation.
1 Find entity_type_id

SELECT entity_type_id FROM eav_entity_type WHERE entity_type_code = 'catalog_product';

2 Find attribute_id

SELECT attribute_id FROM eav_attribute WHERE entity_type_id = 10 AND attribute_code = 'price';

Suppose attribute_code = price and entity_type_id = 10
3 Check if row for that attribute exists

SELECT * FROM catalog_product_entity_decimal WHERE entity_type_id = 10 AND attribute_id = 99 AND store_id = 3 AND entity_id = 123;

4 Delete that particular row

DELETE FROM catalog_product_entity_decimal WHERE entity_type_id = 10 AND attribute_id = 99 AND store_id = 3 AND entity_id = 123;

Now try to run SQL #3, will now give you the empty result.

So in general, You can use the following SQL

DELETE FROM {table_name}
WHERE entity_type_id = {entity_type_id}
AND attribute_id = {attribute_id}
AND store_id = {store_id}
AND entity_id = {entity_id}

In case you want to set the default value for all the products in that store just remove the ‘AND entity_id = {entity_id}’ filter.

Caution:
In order to save you from worst-case scenario, just take a backup of your DB.

Last but not least, must thank @MariusStrajeru for his valuable suggestions and willing to help nature.