Copying magento reviews from one store to another

Copying magento reviews one store to another is relatively simple by inserting new records directly in the database.

If you look at the table review_store you’ll see entries like:

review_id  store_id
10         1

To copy the review to a new store you just need to insert a new record, for example to copy the review to the store with id 2:

INSERT INTO review_store VALUES ('10','2');

review_id  store_id
10         2

ebizmarts Sage Pay Suite Pro, Slow Checkout Problem

Just in case it helps someone else in the future. I was having problems with the Sage Pay Suite Pro from ebizmarts on a development site, it was hanging after pressing the “place order” button, and then taking over a minute, before moving to the success page with the payment taken.

First assumption was that it must be a conflict with another custom module, or the theme, and so I started disabling other modules and enabling as much logging as possible, without any joy. I then tried setting up another payment method (cash on delivery), and after pressing “place order” the success page loaded almost immediately.

I tried a different integration method (server rather than direct) but it was also very slow. It occurred to me, it might be the domain name (using a test one, rather than the one registered with ebizmarts). So I used the host file, and updated the development magento site to use the registered one. But no, still really slow.

A bit more googling and I came across this page. I disabled the profiler in the Sagepay settings, and the checkout time was back under 10 seconds. A bit strange because the same issue wasn’t present on the live site, but this seems to have resolved the problem.

Magento – Multiple Shopping Cart Price Rules using the same Coupon Code

This has just gone into production so I can’t 100% vouch for it, but it looks like you can remove the unique index on a coupon code in magento v1.6 (I think the new versions have improved functionality so a coupon can be used with multiple rules).

This means the same coupon code can have multiple rules (for example offering free post off any item purchased, as well as another benefit such as a discount if a certain item is in the basket).

The table in the mySQL database “salesrule_coupon” needs the index “UNQ_SALESRULE_COUPON_CODE” removed…

database_change

The file /app/code/core/Mage/SalesRule/Model/Resource/Coupon.php needs the function_construct() updated…

FROM:

protected function _construct()
    {
        $this->_init('salesrule/coupon', 'coupon_id');
        $this->addUniqueField(array(
            'field' => 'code',
            'title' => Mage::helper('salesRule')->__('Coupon with the same code')
        ));
    }

TO:

protected function _construct()
    {
        $this->_init('salesrule/coupon', 'coupon_id');
        #$this->addUniqueField(array(
        #    'field' => 'code',
        #    'title' => Mage::helper('salesRule')->__('Coupon with the same code')
        #));
    }

You can then create multiple rules, and specify the same coupon code.

Magento problems with checkout_cart_product_add_after event

I’ve recently been writing some code to fire off emails based on abandoned baskets. The site I’m working on doesn’t send the customer to the basket page when an item is added as it’s better to return the customer to the product page so they can more easily added a second variation of the same product (maybe a different colour or size).

So in order to fire off some code when the item is added I used the checkout_cart_product_add_after event. I found two problems using this event – one is that it fires twice when a configurable product is added to the basket. So when using some code like this to iterate through the items in the basket it was adding an extra entry…

$cart = Mage::getModel('checkout/cart')->getQuote();
$items = $cart->getAllVisibleItems();
foreach ($items as $itemId => $item) {
  $mySku = $item->getSku();
  $myItemId = $item->getId();
  $myItemName = $item->getName();
  $myItemQuantity = $item->getQty();
  $myItemPrice = $item->getBasePriceInclTax() - $item->getBaseDiscountAmount();
}

The price was also not getting set correctly, the quick fix is to switch to using a different event – there’s one called checkout_cart_add_product_complete and this one will only fire once, and set the price correctly.

There’s a handy list of all the Magento events available here.

Hosting Magento Images on an External Web Server

I’ve been interested to know for some time if it might be possible to host the product images of a magento website on an external server. The server I’m using to host a magento site (acting as both web and database server) is coming under ever increasing load, despite having some upgrades, and putting plenty of caching in place.

The medium/long term solution is to upgrade the infrastructure, but I’m interested as an interim step whether it might be possible to move the hosting of the imagery to a 3rd party cloud solution (like Amazon web services) to take some of the load/traffic off the magento server, and improve the performance and experience for customers visiting the site.

Signing up to AWS (Amazon Web Services) is a pretty straight forward affair. The EC2 Service (Amazon Elastic Compute Cloud) allows you to create a server instance in various locations around the world (Dublin being the closest to the UK). You can choose from different Amazon Machine Images (AMIs), which offer different performance levels and operating systems.

As part of the set-up of the instance you create ssh certificates which allow you to ssh/scp/rsync to/from the server. There are also a few other settings to adjust:

Security groups – these allow you to choose which ports are open (and from where)

Elastic ips – these allow you to create a static ip, which can then be pointed at your server instance (when you stop/terminate an instance its hostname and ip address changes each time it’s started again).

I tarred up the /media directory of the magento site, and scp’d it across to the new server instance…

scp -i **location of key file** media.tar ec2-user@**ipaddress of AWS instance**:**location to place file**

I started apache on the new server instance, and untarred the media directory into the web root, so it has a /media directory with all the various sub directories.

When a customer requested a product image from the live web server I want apache to tell them to go off to the AWS server to download the image. This is achieved by editing the .htaccess file on the live server within the media directory, and adding this entry…

RewriteCond %{HTTPS} !on
RewriteRule ^(.*)\.(jpg)$ http://**AWS IP Address here**/media/$1.jpg

Basically this says that if a jpg file within the media directory is requested (on a non https request) then redirect the browser to get the image from the AWS server instance. The $1 contains any sub directory location path. I wanted to only redirect http requests because the AWS instance doesn’t have a SSL certificate and this might cause alerts when customers were using a secure connection to the site.

To keep the AWS server instance imagery up-to-date I set up a cron job on the magento server to regularly update any changes using rsync…

rsync -vazOe "ssh -i **path to key**" **path to media directory on magento server** ec2-user@**ip of AWS Server**:**path to media directory on AWS server** -u --exclude ".htaccess"

I monitored the apache access and error logs on the AWS server instance to make sure all the images were getting requested ok, and all looked good. Amazon charges using a credit system based on use, so I’ll have to keep an eye on the bills – but so far so good.

ebizmarts Sage Pay Suite PRO, Error: MALFORMED. 3055 : The CardType field is required.

Having recently introduced the ebizmarts Sage Pay Suite PRO integration to hook a magento website up to Sagepay, using the direct method I was periodically getting the error “Error: MALFORMED. 3055 : The CardType field is required”.

After a bit of detective work, I believe this error is getting caused by:

a) The customer going through the onepage checkout, entering their payment details and checking out. They are then passed to their providers 3D secure page. They somehow get back to the website (possibly back button) because the page hasn’t loaded or an error. When they go through the checkout for the second time the payment method is already selected (on the radio button) but the details have cleared (and the form is hidden). They can click continue (probably assuming the details have been stored), but they won’t get passed to the 3D secure page, and instead get the error “MALFORMED. 3055 : The CardType field is required”.

b) They’ve almost completed the checkout (including the payment details), but then exited the checkout perhaps to edit the basket. When they go back to checkout, again the payment method is now pre-selected but the details have cleared (and the form is hidden). They click continue (again probably assuming the details have been stored) but they won’t get passed to the 3D secure page and instead get the error “MALFORMED. 3055 : The CardType field is required”.

I’ve fixed it by removing the default checked status of the radio button in:
/app/design/frontend/base/default/template/checkout/onepage/payment/methods.phtml

Basically removing: checked="checked", so if the customer returns to the payment screen no payment method is pre-selected, and they then must select a method to continue. The payment form appears and it must be filled in, and so the error shouldn’t occur.

Testing ebizmarts Magento Abandoned Cart Module

The abandoned cart module is designed so that the minimum amount of time between a cart being abandoned, and an email going out is 1 hour. Which makes testing a bit tricky having to wait so long before the email gets sent out.

I created a page that would reset the abandoned basket in the database, forcing the email to go out within a min…

Within /app/code/community/Ebizmarts/AbandonedCart/etc/config.xml

Update the value for cron_expr so that it reads “*/1 * * * *” (ie checks once a minute, rather than once per hour)

Update your magento cron job so that it also runs once per minute, on a linux server it might be something like:
*/1 * * * * /bin/sh /srv/www/htdocs/magento_root/cron.sh

I then created a page that updated the sales_flat_quote table for a certain customer email address, and reset the ebizmarts fields:


  UPDATE
    sales_flat_quote
  SET
    ebizmarts_abandonedcart_counter = 0,
    ebizmarts_abandonedcart_flag = 0,
    ebizmarts_abandonedcart_token = NULL,
    created_at = DATE_SUB(NOW(), INTERVAL 3 HOUR),
    updated_at = DATE_SUB(NOW(), INTERVAL 2 HOUR)
  WHERE
    customer_email = 'my email address here'

Whenever the page is visited the quote is updated, and a new email gets sent out.

Magento Freezing on Currency Change

I’ve recently been looking at introducing multi-currency options on a Magento website (so the customer can see the price in pounds, dollars, euros etc). The problem I’ve been finding was that the website would freeze after I made a new choice of currency, and eventually the web server time out the request.

Today I had a chance to see what’s going on…

By adding mysql database logging I could see magento was making repeated SQL statements like this…

SELECT `sales_flat_quote`.* FROM `sales_flat_quote` WHERE (`sales_flat_quote`.`entity_id`='321') AND (store_id IN ('1')) AND (is_active = 1)

In fact there were over 8K requests to the sales_flat* tables in a very short space of time!

Eventually with a bit of googling I narrowed down the problem to a rogue bit of code in a custom module used for quantity based discounts across multiple different colour/size variations of the same configurable product.

The line of code causing the problem was:
if($items = Mage::getSingleton('checkout/session')->getQuote()->getItemsCollection())

This caused an infinite loop where the getting of the items in the collection caused it to get the prices again, which in turn fired this piece of code off once again, and so on…

Changing the code to:

$quoteId = Mage::getSingleton('checkout/session')->getQuoteId();
$quote = Mage::getModel('sales/quote')->load($quoteId);
if($items = $quote->getItemsCollection())

has solved the problem.

Linking to a Page from Magento Category Navigation

To create a new page on Magento, which you can link to from the Category navigation, you can try the following:

Create a new directory inside your chosen template, for example:
/Catalogue/app/design/frontend/default/modern/template/NewDirectoryName

Then create a new file in there called content.phtml
/Catalogue/app/design/frontend/default/modern/template/NewDirectoryName/content.phtml

and add some content for the new page.

In magento admin go to:
CMS->Static Blocks

Add New Block (top right)

Block Title: Block Name
Identifier: block_identifier

In the white content space paste in:
{{block type=”core/template” template=”NewDirectoryName/content.phtml”}}

Save Block (top right)

Catalogue->Manage Categories

Create the Category

Display Settings Tab

Display Mode: Static Block Only

CMS Block: Chose the one you created earlier

Custom Design Tab

Page Layout: 1 Column

Save Category

You should now have a link in the navigation, which will load the content from content.phtml

Magento Google Product Feed

I’ve beeen working on producing a new google product feed, using the product data from a Magento database.

This has been made more complex because google requires additional attributes for clothes/shoes, and wants the data at colour/size level, including a unique image for every colour variation.

The code I’m developing iterates through the “live” products in certain categories. Pulling out the data and producing an RSS2 feed.

Most of the products are Configurable products, so in order to access the child products I used this code, which iterates through each child, and then gets the colour and size attribute values…


  #Get Configurable Product ($obj is the product from a collection)
  $product_tmp = Mage::getModel('catalog/product')->load($obj->entity_id);

  #Get Children
  $conf = Mage::getModel('catalog/product_type_configurable')->setProduct($product_tmp);
  $col = $conf->getUsedProductCollection()->addAttributeToSelect('*')->addFilterByRequiredOptions();
  foreach($col as $simple_product) {
    $attributes = $simple_product->getAttributes();
    foreach ($attributes as $attribute) {
      $value = $attribute->getFrontend()->getValue($simple_product);
      if ($value != "" && $attribute->getName() == 'color' && $value != "No") { 			
        echo "      " . $value . "\n"; 
        $thisColour = $value;
      }
      if ($value != "" && $value != 'No' && strpos(strtolower($simple_product->getResource()->getAttribute($attribute->getName())->getFrontendLabel()), 'size')) { 
        echo "      " . $value . "\n"; 
        $thisSize = $value;
      }    		
    }
  }