Magento Customer Passwords

Recently I had occasion to export some customer data and use it in another system. This way I don’t need 2 different sign-ons for an external CMS.

Magento stores the hashed password with its salt. You can use this code as a guide to checking Magento customer passwords outside (well, inside, too, I guess…) Magento. This is a php command line script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* Substitute appropriate Mage path: */
require_once( "../../app/Mage.php" );
Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
 
/* argv[1] is assumed to be the email address in our program
 * argv[2] is the password
 */
$customer = Mage::getModel('customer/customer')->setWebsiteId(Mage::app()->getStore()->getWebsiteId())->loadByEmail($argv[1]);
echo "Customer ID: " . $customer->getId() . "\n";
 
/* Load in the password_hash: */
$hash_in_db = $customer->getPasswordHash();
echo "Hashed password as stored in the database: ". $hash_in_db . "\n";
 
/* The hashed password is stored as:
 *      hash.':'.salt
 * We explode at the ':' such that hash is in element 0, salt is in element 1
 */
$a = explode(':', $hash_in_db);
$hash = $a[0];
$salt = $a[1];
 
/* Generate a hash based on what was passed in at argv[2] */
$password_in = $argv[2];
$computed_password =  md5($salt . $password_in);
 
echo "Database=[$hash]  Computed=[$computed_password]\n\n";
 
/* Well, did it match? */
echo ( $computed_password === $hash ) ? "BOOM-MATCH!\n" : "*bbzzt* nope\n";
exit(0);

Thank you, unexpected[it]!

Magento: “An error occurred while saving the URL rewrite”

Working on a new Magento store, I got this error while rebuilding the Catalog URL Rewrites index:

! An error occurred while saving the URL rewrite

After the usual Google searches, I came across this post, which proved to be the answer to my problem: http://www.magentocommerce.com/boards/main.php/viewthread/198534/#t334426

Turns out there’s a conflict introduced by the installation of the osCommerce Migration Extension. However, the post wasn’t exactly clear on which code to change, so I figured I’d post a little more on the issue.

First, I moved all of the osCommerce code out of app/code/core/Mage and into app/code/local/Mage. I should point out, though, that I had already done that even before I ran into this problem because I ran into several issues with the import process that I was (cautiously) optimistic that I could actually fix, as well as several things that I wanted to change (for example, my import source had categories that contained embedded HTML I wished to strip out – and I had several thousand categories …)

The file we need to edit is Mage/Oscommerce/Model/Mysql4/Catalog/Url.php. In that file, we find the Mage_Oscommerce_Model_Mysql4_Catalog_Url, which extends Mage_Catalog_Model_Resource_Eav_Mysql4_Url:

class Mage_Oscommerce_Model_Mysql4_Catalog_Url extends Mage_Catalog_Model_Resource_Eav_Mysql4_Url
{
    protected function _getCategories($categoryIds, $storeId = null, $path = null)
    {
        $isActiveAttribute = Mage::getModel('eav/entity_attribute')->loadByCode('catalog_category', 'is_active');
        $categories = array();

Basically, all the post wants us to do is short circuit the _getCategories function by returning its parents’ version instead.

class Mage_Oscommerce_Model_Mysql4_Catalog_Url extends Mage_Catalog_Model_Resource_Eav_Mysql4_Url
{
    protected function _getCategories($categoryIds, $storeId = null, $path = null)
    {
        return parent::_getCategories();
 
        $isActiveAttribute = Mage::getModel('eav/entity_attribute')->loadByCode('catalog_category', 'is_active');
        $categories = array();

Note: Please don’t confuse this with a “good, permanent” solution. This is a nice way to be able to continue to refine my import using osCommerce Migration, and be minimally invasive. Once I have this thing working properly, I will simply remove the osCommerce Migration Extension from my site – once I’m live, I won’t need it any more.

Magento API: Select Orders by Date Range

Here’s snippet of code I was able to use to get all of yesterday’s orders. You can see more variations for the use of DateTime here. I also wrote a front end to allow a selection of dates, but you can figure that out. This was the hard part to me. How the heck do I pass the from and to dates to Magento’s sales_order.list call?

Also note that Magento internally store dates and times in UTC. So I have to convert to that offset to get my orders in my local time. time_local_to_utc is a little function I wrote to do that. Conversely, time_utc_to_local converts it back. (Code later in this post).

Remember, much of the code I publish here is very specific for a particular implementation. This code is designed to be able to pull back all payment information into a spreadsheet for an accounting department. This is not a generic solution to a generic Magento problem. Rather, I intended this to help other folks get a start on pulling back orders by date!

$day = new DateTime();
$day->modify( "-1 day" );
 
$fromDate = $day->format("Y-m-d 00:00:00");
$toDate = $day->format("Y-m-d 23:59:59" );
 
$utc_from=time_local_to_utc($fromDate);
$utc_to=time_local_to_utc($toDate);
 
$proxy = new SoapClient('YOURSITE/api/soap/?wsdl');
$sessionId = $proxy->login('YUOUR_LOGIN', 'YOUR_PASS');
$order_headers = $proxy->call($sessionId, 'sales_order.list',
        array(
            array(
                'created_at'=>
                    array(
                        'from'=>$utc_from,
                        'to'=>$utc_to
                    )
                )
            )
        );
$howManyOrders = count($order_headers);

Here are the 2 time functions. They could certainly be made more general and really should be refactored (pass in “to” and “from” zones…), but you get the idea. And maybe there’s something to be said for the inline main code being simple to read.

function
time_local_to_utc($date) {
    $dt = new DateTime($date, new DateTimeZone("America/New_York"));
    $dt->setTimezone(new DateTimeZone("UTC"));
    return( $dt->format("Y-m-d H:i:s") );
}
 
function
time_utc_to_local($date) {
    $dt = new DateTime($date, new DateTimeZone("UTC"));
    $dt->setTimezone(new DateTimeZone("America/New_York"));
    return( $dt->format("Y-m-d H:i:s") );
}

Magento and jQuery: Ajax Add to Cart Button

Introduction

This post describes adding an Ajax add-to-cart feature on my Magento’s product view page. This was an interesting exercise, and I learned a lot from a number of different sources. I combined their collected wisdom, and applied it as described here. Some of that wisdom was my own. You’ll need to bring in jQuery and jQuery UI as described in my previous post, Magento and jQuery.

Goal

Provide add to cart button that will give us the familiar ajax modal feel while it’s working, and then let us know it’s added the item. Then, present the user with the option of staying on the current page, or proceeding to the checkout page.

Prep Work I: Reflect the Correct number of Items in “My Cart”

In order for our work to correctly update the “My Cart” link in the header, I needed to add identification information to that section. You can see in the highlighted lines of code from my template/page/html/header.phtml. I applied a style to the whole thing in case the designers wanted to improve its appearance. And then I added div around only the number itself:

template/page/html/header.phtml

<div class="up-top-cart-status">
    <a href="<?php echo $this->getUrl('checkout/cart'); ?>"><img src="<?php echo $this->getSkinUrl(); ?>images/cart-icon.png" alt="Cart" />My Order
        <?php
    $cart = Mage::getModel('checkout/cart')->getQuote()->getData();
    $cart_qty = (!empty($cart['items_qty'])) ? $cart['items_qty'] : 0;
    printf('(<div style="display:inline" id="up-top-number-in-cart">%d</div> Items)', $cart_qty);
          ?>
     </a>
</div>

Prep Work II: Build the php Code for the Ajax Call

This code was based on work found at iFuel Interactive’s Adding to the Cart with a jQuery Ajax Call in Magento. I’ve modified the code to use php’s json_encode() function. I believe that, as it’s php, we should work in php’s milieu instead of handcrafting the JSON ourselves.

Of special note, I am calling getSummaryCount after I’ve saved the cart. This call did not yield the correct number of items in the cart until after I’d saved it. This way, I’m able to put the current number of items into the JSON returned back to the caller.

/mxwest_atc.php

<?php
include_once 'app/Mage.php';
Mage::app();
try{
    $result = array();
    // usage /mxwest_atc.php?product_id=838qty=1

    if(!isset($_GET['sku'])) {
        $sku = '';
    }
    else {
        $sku = $_GET['sku'];
    }

    if(!isset($_GET['product_id'])) {
        $product_id = '';
    }
    else {
        $product_id = $_GET['product_id'];
    }
    if(!isset($_GET['qty'])) {
        $qty = '1';
    }
    else { 
        $qty = $_GET['qty'];
    }

    if ($sku != ""){
        $product_id = Mage::getModel('catalog/product')->getIdBySku("$sku");
        if ($product_id == '') {
            $session->addError("<strong>Product Not Added</strong>The SKU you entered ($sku) was not found.");
        }
    }

    $request = Mage::app()->getRequest();
    $product = Mage::getModel('catalog/product')->load($product_id);
    $session = Mage::getSingleton('core/session', array('name'=>'frontend'));
    $cart = Mage::helper('checkout/cart')->getCart();

    $cart->addProduct($product, $qty);

    $session->setLastAddedProductId($product->getId());
    $session->setCartWasUpdated(true);

    $cart->save();

    $items_in_cart = Mage::helper('checkout/cart')->getSummaryCount();

    $result['result']="success";
    $result['message']="Added!";
    $result['items_in_cart'] = "$items_in_cart";

    echo json_encode($result);
}
catch (Exception $e) {
    $result['result'] = 'error';
    $result['message'] =  $e->getMessage();
    echo json_encode($result);
}
?>

Add to Cart: Pulling it All Together

template/catalog/product/view/addtocart.phtml

Here, we are setting up the input fields and naming the various input elements correctly. In my case, “minimum quantity” also means “quantity multiplier.” That is, I must order a multiple of minimum quantity. If minimum quantity is 4, I must order 4, 8, 12 or so on. My code will round the order up. If you tried to order 3, I’d round you up to 4. Your code might not need this. If that’s the case, just don’t use the qtyM variables, and don’t call upFixQtyMult.

template/catalog/product/view/addtocart.phtml, Part 1 – php

Of note here is that I am uniquely identifying the qty field; this isn’t necessary when I’m using this code for only 1 item. But if this code is used multiple times on a page, I’ll need unique identifiers. “A-ha!” I here you say “- the SKU isn’t unique!” Calm down. It’s not finished yet.

The UI experience will be encapsulated in the div id dialog. The subsequent jQuery UI code will use that div to keep the user up to date on what’s happening. See line 17, highlighted.

<?php
    $_product = $this->getProduct();
    $_myProductSku = $_product->getSku();
    $_mxwMinQty = $this->getMinimalQty($_product);
?>
<?php $buttonTitle = $this->__('Add to Cart'); ?>
<?php if($_product->isSaleable()): ?>
    <div class="add-to-cart">
        <?php if(!$_product->isGrouped()): ?>
        <label for="qty"><?php echo $this->__('Qty:') ?></label>
        <input type="text" size="4" name="qty" id="qty<?=$_myProductSku?>" maxlength="4" value="<?=$_mxwMinQty?>" title="<?php echo $this->__('Qty') ?>" class="input-text qty" />
        <input type="hidden" id="qtyM<?=$_myProductSku?>" value="<?=$_mxwMinQty?>" />
        <?php endif; ?>
        <br /><a href="#"><img src="<?php echo $this->getSkinUrl(); ?>images/buy-now-btn.png" alt="Buy Now" onclick="addItemToCart('<?=$_myProductSku?>');" alt='<?=$buttonTitle ?>' /></a>
        <?php echo $this->getChildHtml('', true, true) ?>
    </div>
<div class="ui-widget" id="dialog" title="Adding to Cart"></div>

template/catalog/product/view/addtocart.phtml, Part 2 – the Javascript

jQuery here is represented by $jQ. For more details on how we arrived at that nomenclature, please see my earlier post Magento and jQuery, which explains how I brought jQuery in in its noConflict() mode. That post also describes the reasoning behind the “ui-xxx” styling – that’s what jQuery UI’s ThemeRoller produces. Again, please consult the previous post for those details.

This code is pretty straightforward. Call the mxwest_atc.php script. It will return a JSON encoded response in (duh) “response.” The fancy bit of updating the header for number of items in cart is highlighted at lines 20 and 21. (You did remember to do the work described in “Prep Work I”, above, right?). I used the variable “o” as shorthand for object. Although I generally prefer more descriptive variable names, I figured the 2 lines here were comprehensible enough that I could shorthand it.

<script type="text/javascript">
function
addItemToCart(sku) {
    $jQ("#dialog").dialog('destroy');
    $jQ("#dialog").html("<center>Adding ...<p><img src='<?=$this->getSkinUrl()?>/images/up-ajax-loader.gif'></p></center>");
    $jQ("#dialog").dialog({show: 'scale', hide: 'scale', modal: 'true'});

    var qty_id = "#qty"+sku.toString();
    var qty = $jQ(qty_id).val();

    var qtyM_id = "#qtyM"+sku.toString();
    var qtyM = $jQ(qtyM_id).val();

    qty = upFixQtyMult(parseInt(qty),parseInt(qtyM));

    var cart_parms = "sku=" + sku + "&qty=" + qty;
    var myUrl = "<?=Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB);?>/mxwest_atc.php?"+cart_parms;
    var jqhxr = $jQ.ajax({ url: myUrl })
        .success( function(response){
            o = $jQ.parseJSON(response);    
            $jQ("#up-top-number-in-cart").html(o.items_in_cart);
            $jQ("#dialog").html("<span class=\"ui-icon ui-icon-info\"></span>Thanks! " + sku + " was added to your cart." );
            $jQ("#dialog").addClass("ui-state-highlight ui-corner-all");
            $jQ("#dialog").dialog({hide: 'scale',
                buttons: {
                    "Keep Shopping": function() { $jQ(this).dialog("close"); },
                    "Checkout": function() { $jQ(this).dialog("close"); window.location = "<?=Mage::getUrl('checkout/cart', array('_secure' => true));?>"; }
                }
            });
        })
        .error( function() {
            $jQ("#dialog").html("<span class=\"ui-icon ui-icon-alert\"></span>Sorry, something bad happened. " + sku + " not added.");
            $jQ("#dialog").addClass("ui-state-error ui-corner-all");
            $jQ("#dialog").dialog({ buttons: { "Ok": function() { $jQ(this).dialog("close"); } } });
        });
}
function
upFixQtyMult(qty,qtyM) {
    var m = +qtyM;
    var q = m;
    if(isNaN(qty)) return +m;
    q = +qty;
    if( q <= m ) return m;
    return +((m<=1)?q:(q+(m-(q%m))));
}
</script>

For completeness sake … let’s close that php if!

<?php endif; ?>

End Note

Hope this helps! Magento is incredibly feature-rich, but I also find it incredibly dense to penetrate. I think that much of the User Experience still needs to abstracted out of php and back into jQuery UI (or similar) to improve the User Experience. There are many parts of the code that make me scratch my head. A head full of headaches at times. Hopefully, though, I helped shed a little light on using this cantankerous rascal in a more effective way. Let me know what you think!