<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Magento Design and Development &#187; Branko Ajzele</title>
	<atom:link href="http://inchoo.net/author/branko/feed/" rel="self" type="application/rss+xml" />
	<link>http://inchoo.net</link>
	<description>Magento Design and Magento Development Professionals - Inchoo</description>
	<lastBuildDate>Wed, 16 May 2012 12:17:41 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Validate your input, Magento style</title>
		<link>http://inchoo.net/ecommerce/magento/programming-magento/validate-your-input-magento-style/</link>
		<comments>http://inchoo.net/ecommerce/magento/programming-magento/validate-your-input-magento-style/#comments</comments>
		<pubDate>Sun, 25 Mar 2012 12:04:06 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[input]]></category>
		<category><![CDATA[Magento]]></category>
		<category><![CDATA[sanitize]]></category>
		<category><![CDATA[security]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=12923</guid>
		<description><![CDATA[I&#8217;m sure most fo you will agree that Magento&#8217;s frontend validation for form input fields is a nice feature. All it takes is for you to add some CSS classes &#8230;]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m sure most fo you will agree that Magento&#8217;s frontend validation for form input fields is a nice feature. All it takes is for you to add some CSS classes to the input fields and then upon form submission validation is triggered that outputs, by default, red colored messages that point the possible validation failures etc. This validation is done on the client side via Java Script.<span id="more-12923"></span></p>
<p>For example, if you open the app/design/frontend/base/default/template/customer/form/register.phtml file and focus on the input fields and their &#8220;class&#8221; attributes you will see some of them named: &#8220;required-entry&#8221;, &#8220;validate-email&#8221;, &#8220;validate-zip-international&#8221;, etc. All of these are tied to the Java Script validation rules defined under js/prototype/validation.js file.</p>
<pre class="brush: jscript; title: ; notranslate">
Validation.add('IsEmpty', '', function(v) {
    return  (v == '' || (v == null) || (v.length == 0) || /^\s+$/.test(v)); // || /^\s+$/.test(v));
});

Validation.addAllThese([
    ['validate-select', 'Please select an option.', function(v) {
                return ((v != &quot;none&quot;) &amp;&amp; (v != null) &amp;&amp; (v.length != 0));
            }],
    ['required-entry', 'This is a required field.', function(v) {
                return !Validation.get('IsEmpty').test(v);
            }],
    ['validate-number', 'Please enter a valid number in this field.', function(v) {
                return Validation.get('IsEmpty').test(v) || (!isNaN(parseNumber(v)) &amp;&amp; !/^\s+$/.test(parseNumber(v)));
            }],
    ['validate-digits', 'Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.', function(v) {
                return Validation.get('IsEmpty').test(v) ||  !/[^\d]/.test(v);
            }],
    ['validate-digits-range', 'The value is not within the specified range.', function(v, elm) {
                var result = Validation.get('IsEmpty').test(v) ||  !/[^\d]/.test(v);
                var reRange = new RegExp(/^digits-range-[0-9]+-[0-9]+$/);
                $w(elm.className).each(function(name, index) {
                    if (name.match(reRange) &amp;&amp; result) {
                        var min = parseInt(name.split('-')[2], 10);
                        var max = parseInt(name.split('-')[3], 10);
                        var val = parseInt(v, 10);
                        result = (v &gt;= min) &amp;&amp; (v &lt;= max);
                    }
                });
                return result;
            }],
    ['validate-alpha', 'Please use letters only (a-z or A-Z) in this field.', function (v) {
                return Validation.get('IsEmpty').test(v) ||  /^[a-zA-Z]+$/.test(v)
            }],
    ['validate-code', 'Please use only letters (a-z), numbers (0-9) or underscore(_) in this field, first character should be a letter.', function (v) {
                return Validation.get('IsEmpty').test(v) ||  /^[a-z]+[a-z0-9_]+$/.test(v)
            }],
    ['validate-alphanum', 'Please use only letters (a-z or A-Z) or numbers (0-9) only in this field. No spaces or other characters are allowed.', function(v) {
                return Validation.get('IsEmpty').test(v) ||  /^[a-zA-Z0-9]+$/.test(v) /*!/\W/.test(v)*/
            }],
    ['validate-street', 'Please use only letters (a-z or A-Z) or numbers (0-9) or spaces and # only in this field.', function(v) {
                return Validation.get('IsEmpty').test(v) ||  /^[ \w]{3,}([A-Za-z]\.)?([ \w]*\#\d+)?(\r\n| )[ \w]{3,}/.test(v)
            }],
    ['validate-phoneStrict', 'Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890.', function(v) {
                return Validation.get('IsEmpty').test(v) || /^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/.test(v);
            }],
    ['validate-phoneLax', 'Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890.', function(v) {
                return Validation.get('IsEmpty').test(v) || /^((\d[-. ]?)?((\(\d{3}\))|\d{3}))?[-. ]?\d{3}[-. ]?\d{4}$/.test(v);
            }],
    ['validate-fax', 'Please enter a valid fax number. For example (123) 456-7890 or 123-456-7890.', function(v) {
                return Validation.get('IsEmpty').test(v) || /^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/.test(v);
            }],
    ['validate-date', 'Please enter a valid date.', function(v) {
                var test = new Date(v);
                return Validation.get('IsEmpty').test(v) || !isNaN(test);
            }],
    ['validate-email', 'Please enter a valid email address. For example johndoe@domain.com.', function (v) {
                //return Validation.get('IsEmpty').test(v) || /\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/.test(v)
                //return Validation.get('IsEmpty').test(v) || /^[\!\#$%\*/?|\^\{\}`~&amp;\'\+\-=_a-z0-9][\!\#$%\*/?|\^\{\}`~&amp;\'\+\-=_a-z0-9\.]{1,30}[\!\#$%\*/?|\^\{\}`~&amp;\'\+\-=_a-z0-9]@([a-z0-9_-]{1,30}\.){1,5}[a-z]{2,4}$/i.test(v)
                return Validation.get('IsEmpty').test(v) || /^([a-z0-9,!\#\$%&amp;'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&amp;'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i.test(v)
            }],
    ['validate-emailSender', 'Please use only visible characters and spaces.', function (v) {
                return Validation.get('IsEmpty').test(v) ||  /^[\S ]+$/.test(v)
                    }],
    ['validate-password', 'Please enter 6 or more characters. Leading or trailing spaces will be ignored.', function(v) {
                var pass=v.strip(); /*strip leading and trailing spaces*/
                return !(pass.length&gt;0 &amp;&amp; pass.length &lt; 6);
            }],
    ['validate-admin-password', 'Please enter 7 or more characters. Password should contain both numeric and alphabetic characters.', function(v) {
                var pass=v.strip();
                if (0 == pass.length) {
                    return true;
                }
                if (!(/[a-z]/i.test(v)) || !(/[0-9]/.test(v))) {
                    return false;
                }
                return !(pass.length &lt; 7);
            }],
    ['validate-cpassword', 'Please make sure your passwords match.', function(v) {
                var conf = $('confirmation') ? $('confirmation') : $$('.validate-cpassword')[0];
                var pass = false;
                if ($('password')) {
                    pass = $('password');
                }
                var passwordElements = $$('.validate-password');
                for (var i = 0; i &lt; passwordElements.size(); i++) {
                    var passwordElement = passwordElements[i];
                    if (passwordElement.up('form').id == conf.up('form').id) {
                        pass = passwordElement;
                    }
                }
                if ($$('.validate-admin-password').size()) {
                    pass = $$('.validate-admin-password')[0];
                }
                return (pass.value == conf.value);
            }],
    ['validate-url', 'Please enter a valid URL. Protocol is required (http://, https:// or ftp://)', function (v) {
                v = (v || '').replace(/^\s+/, '').replace(/\s+$/, '');
                return Validation.get('IsEmpty').test(v) || /^(http|https|ftp):\/\/(([A-Z0-9]([A-Z0-9_-]*[A-Z0-9]|))(\.[A-Z0-9]([A-Z0-9_-]*[A-Z0-9]|))*)(:(\d+))?(\/[A-Z0-9~](([A-Z0-9_~-]|\.)*[A-Z0-9~]|))*\/?$/i.test(v)
            }],
    ['validate-clean-url', 'Please enter a valid URL. For example http://www.example.com or www.example.com', function (v) {
                return Validation.get('IsEmpty').test(v) || /^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+.(com|org|net|dk|at|us|tv|info|uk|co.uk|biz|se)$)(:(\d+))?\/?/i.test(v) || /^(www)((\.[A-Z0-9][A-Z0-9_-]*)+.(com|org|net|dk|at|us|tv|info|uk|co.uk|biz|se)$)(:(\d+))?\/?/i.test(v)
            }],
    ['validate-identifier', 'Please enter a valid URL Key. For example &quot;example-page&quot;, &quot;example-page.html&quot; or &quot;anotherlevel/example-page&quot;.', function (v) {
                return Validation.get('IsEmpty').test(v) || /^[a-z0-9][a-z0-9_\/-]+(\.[a-z0-9_-]+)?$/.test(v)
            }],
    ['validate-xml-identifier', 'Please enter a valid XML-identifier. For example something_1, block5, id-4.', function (v) {
                return Validation.get('IsEmpty').test(v) || /^[A-Z][A-Z0-9_\/-]*$/i.test(v)
            }],
    ['validate-ssn', 'Please enter a valid social security number. For example 123-45-6789.', function(v) {
            return Validation.get('IsEmpty').test(v) || /^\d{3}-?\d{2}-?\d{4}$/.test(v);
            }],
    ['validate-zip', 'Please enter a valid zip code. For example 90602 or 90602-1234.', function(v) {
            return Validation.get('IsEmpty').test(v) || /(^\d{5}$)|(^\d{5}-\d{4}$)/.test(v);
            }],
    ['validate-zip-international', 'Please enter a valid zip code.', function(v) {
            //return Validation.get('IsEmpty').test(v) || /(^[A-z0-9]{2,10}([\s]{0,1}|[\-]{0,1})[A-z0-9]{2,10}$)/.test(v);
            return true;
            }],
    ['validate-date-au', 'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.', function(v) {
                if(Validation.get('IsEmpty').test(v)) return true;
                var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
                if(!regex.test(v)) return false;
                var d = new Date(v.replace(regex, '$2/$1/$3'));
                return ( parseInt(RegExp.$2, 10) == (1+d.getMonth()) ) &amp;&amp;
                            (parseInt(RegExp.$1, 10) == d.getDate()) &amp;&amp;
                            (parseInt(RegExp.$3, 10) == d.getFullYear() );
            }],
    ['validate-currency-dollar', 'Please enter a valid $ amount. For example $100.00.', function(v) {
                // [$]1[##][,###]+[.##]
                // [$]1###+[.##]
                // [$]0.##
                // [$].##
                return Validation.get('IsEmpty').test(v) ||  /^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/.test(v)
            }],
    ['validate-one-required', 'Please select one of the above options.', function (v,elm) {
                var p = elm.parentNode;
                var options = p.getElementsByTagName('INPUT');
                return $A(options).any(function(elm) {
                    return $F(elm);
                });
            }],
    ['validate-one-required-by-name', 'Please select one of the options.', function (v,elm) {
                var inputs = $$('input[name=&quot;' + elm.name.replace(/([\\&quot;])/g, '\\$1') + '&quot;]');

                var error = 1;
                for(var i=0;i&lt;inputs.length;i++) {
                    if((inputs[i].type == 'checkbox' || inputs[i].type == 'radio') &amp;&amp; inputs[i].checked == true) {
                        error = 0;
                    }

                    if(Validation.isOnChange &amp;&amp; (inputs[i].type == 'checkbox' || inputs[i].type == 'radio')) {
                        Validation.reset(inputs[i]);
                    }
                }

                if( error == 0 ) {
                    return true;
                } else {
                    return false;
                }
            }],
    ['validate-not-negative-number', 'Please enter a valid number in this field.', function(v) {
                v = parseNumber(v);
                return (!isNaN(v) &amp;&amp; v&gt;=0);
            }],
    ['validate-state', 'Please select State/Province.', function(v) {
                return (v!=0 || v == '');
            }],

    ['validate-new-password', 'Please enter 6 or more characters. Leading or trailing spaces will be ignored.', function(v) {
                if (!Validation.get('validate-password').test(v)) return false;
                if (Validation.get('IsEmpty').test(v) &amp;&amp; v != '') return false;
                return true;
            }],
    ['validate-greater-than-zero', 'Please enter a number greater than 0 in this field.', function(v) {
                if(v.length)
                    return parseFloat(v) &gt; 0;
                else
                    return true;
            }],
    ['validate-zero-or-greater', 'Please enter a number 0 or greater in this field.', function(v) {
                if(v.length)
                    return parseFloat(v) &gt;= 0;
                else
                    return true;
            }],
    ['validate-cc-number', 'Please enter a valid credit card number.', function(v, elm) {
                // remove non-numerics
                var ccTypeContainer = $(elm.id.substr(0,elm.id.indexOf('_cc_number')) + '_cc_type');
                if (ccTypeContainer &amp;&amp; typeof Validation.creditCartTypes.get(ccTypeContainer.value) != 'undefined'
                        &amp;&amp; Validation.creditCartTypes.get(ccTypeContainer.value)[2] == false) {
                    if (!Validation.get('IsEmpty').test(v) &amp;&amp; Validation.get('validate-digits').test(v)) {
                        return true;
                    } else {
                        return false;
                    }
                }
                return validateCreditCard(v);
            }],
    ['validate-cc-type', 'Credit card number does not match credit card type.', function(v, elm) {
                // remove credit card number delimiters such as &quot;-&quot; and space
                elm.value = removeDelimiters(elm.value);
                v         = removeDelimiters(v);

                var ccTypeContainer = $(elm.id.substr(0,elm.id.indexOf('_cc_number')) + '_cc_type');
                if (!ccTypeContainer) {
                    return true;
                }
                var ccType = ccTypeContainer.value;

                if (typeof Validation.creditCartTypes.get(ccType) == 'undefined') {
                    return false;
                }

                // Other card type or switch or solo card
                if (Validation.creditCartTypes.get(ccType)[0]==false) {
                    return true;
                }

                // Matched credit card type
                var ccMatchedType = '';

                Validation.creditCartTypes.each(function (pair) {
                    if (pair.value[0] &amp;&amp; v.match(pair.value[0])) {
                        ccMatchedType = pair.key;
                        throw $break;
                    }
                });

                if(ccMatchedType != ccType) {
                    return false;
                }

                if (ccTypeContainer.hasClassName('validation-failed') &amp;&amp; Validation.isOnChange) {
                    Validation.validate(ccTypeContainer);
                }

                return true;
            }],
     ['validate-cc-type-select', 'Card type does not match credit card number.', function(v, elm) {
                var ccNumberContainer = $(elm.id.substr(0,elm.id.indexOf('_cc_type')) + '_cc_number');
                if (Validation.isOnChange &amp;&amp; Validation.get('IsEmpty').test(ccNumberContainer.value)) {
                    return true;
                }
                if (Validation.get('validate-cc-type').test(ccNumberContainer.value, ccNumberContainer)) {
                    Validation.validate(ccNumberContainer);
                }
                return Validation.get('validate-cc-type').test(ccNumberContainer.value, ccNumberContainer);
            }],
     ['validate-cc-exp', 'Incorrect credit card expiration date.', function(v, elm) {
                var ccExpMonth   = v;
                var ccExpYear    = $(elm.id.substr(0,elm.id.indexOf('_expiration')) + '_expiration_yr').value;
                var currentTime  = new Date();
                var currentMonth = currentTime.getMonth() + 1;
                var currentYear  = currentTime.getFullYear();
                if (ccExpMonth &lt; currentMonth &amp;&amp; ccExpYear == currentYear) {
                    return false;
                }
                return true;
            }],
     ['validate-cc-cvn', 'Please enter a valid credit card verification number.', function(v, elm) {
                var ccTypeContainer = $(elm.id.substr(0,elm.id.indexOf('_cc_cid')) + '_cc_type');
                if (!ccTypeContainer) {
                    return true;
                }
                var ccType = ccTypeContainer.value;

                if (typeof Validation.creditCartTypes.get(ccType) == 'undefined') {
                    return false;
                }

                var re = Validation.creditCartTypes.get(ccType)[1];

                if (v.match(re)) {
                    return true;
                }

                return false;
            }],
     ['validate-ajax', '', function(v, elm) { return true; }],
     ['validate-data', 'Please use only letters (a-z or A-Z), numbers (0-9) or underscore(_) in this field, first character should be a letter.', function (v) {
                if(v != '' &amp;&amp; v) {
                    return /^[A-Za-z]+[A-Za-z0-9_]+$/.test(v);
                }
                return true;
            }],
     ['validate-css-length', 'Please input a valid CSS-length. For example 100px or 77pt or 20em or .5ex or 50%.', function (v) {
                if (v != '' &amp;&amp; v) {
                    return /^[0-9\.]+(px|pt|em|ex|%)?$/.test(v) &amp;&amp; (!(/\..*\./.test(v))) &amp;&amp; !(/\.$/.test(v));
                }
                return true;
            }],
     ['validate-length', 'Text length does not satisfy specified text range.', function (v, elm) {
                var reMax = new RegExp(/^maximum-length-[0-9]+$/);
                var reMin = new RegExp(/^minimum-length-[0-9]+$/);
                var result = true;
                $w(elm.className).each(function(name, index) {
                    if (name.match(reMax) &amp;&amp; result) {
                       var length = name.split('-')[2];
                       result = (v.length &lt;= length);
                    }
                    if (name.match(reMin) &amp;&amp; result &amp;&amp; !Validation.get('IsEmpty').test(v)) {
                        var length = name.split('-')[2];
                        result = (v.length &gt;= length);
                    }
                });
                return result;
            }],
     ['validate-percents', 'Please enter a number lower than 100.', {max:100}],
     ['required-file', 'Please select a file', function(v, elm) {
         var result = !Validation.get('IsEmpty').test(v);
         if (result === false) {
             ovId = elm.id + '_value';
             if ($(ovId)) {
                 result = !Validation.get('IsEmpty').test($(ovId).value);
             }
         }
         return result;
     }],
     ['validate-cc-ukss', 'Please enter issue number or start date for switch/solo card type.', function(v,elm) {
         var endposition;

         if (elm.id.match(/(.)+_cc_issue$/)) {
             endposition = elm.id.indexOf('_cc_issue');
         } else if (elm.id.match(/(.)+_start_month$/)) {
             endposition = elm.id.indexOf('_start_month');
         } else {
             endposition = elm.id.indexOf('_start_year');
         }

         var prefix = elm.id.substr(0,endposition);

         var ccTypeContainer = $(prefix + '_cc_type');

         if (!ccTypeContainer) {
               return true;
         }
         var ccType = ccTypeContainer.value;

         if(['SS','SM','SO'].indexOf(ccType) == -1){
             return true;
         }

         $(prefix + '_cc_issue').advaiceContainer
           = $(prefix + '_start_month').advaiceContainer
           = $(prefix + '_start_year').advaiceContainer
           = $(prefix + '_cc_type_ss_div').down('ul li.adv-container');

         var ccIssue   =  $(prefix + '_cc_issue').value;
         var ccSMonth  =  $(prefix + '_start_month').value;
         var ccSYear   =  $(prefix + '_start_year').value;

         var ccStartDatePresent = (ccSMonth &amp;&amp; ccSYear) ? true : false;

         if (!ccStartDatePresent &amp;&amp; !ccIssue){
             return false;
         }
         return true;
     }]
]);
</pre>
<p>If we extract those from code, here is the list of validations available on client side trough Javascript:</p>
<ul>
<li>IsEmpty</li>
<li>validate-select</li>
<li>required-entry</li>
<li>validate-number</li>
<li>validate-digits</li>
<li>validate-digits-range</li>
<li>validate-alpha</li>
<li>validate-code</li>
<li>validate-alphanum</li>
<li>validate-street</li>
<li>validate-phoneStrict</li>
<li>validate-phoneLax</li>
<li>validate-fax</li>
<li>validate-date</li>
<li>validate-email</li>
<li>validate-emailSender</li>
<li>validate-password</li>
<li>validate-admin-password</li>
<li>validate-cpassword</li>
<li>validate-url</li>
<li>validate-clean-url</li>
<li>validate-identifier</li>
<li>validate-xml-identifier</li>
<li>validate-ssn</li>
<li>validate-zip</li>
<li>validate-zip-international</li>
<li>validate-date-au</li>
<li>validate-currency-dollar</li>
<li>validate-one-required</li>
<li>validate-one-required-by-name</li>
<li>validate-not-negative-number</li>
<li>validate-state</li>
<li>validate-new-password</li>
<li>validate-greater-than-zero</li>
<li>validate-zero-or-greater</li>
<li>validate-cc-number</li>
<li>validate-cc-type</li>
<li>validate-cc-type-select</li>
<li>validate-cc-exp</li>
<li>validate-cc-cvn</li>
<li>validate-ajax</li>
<li>validate-data</li>
<li>validate-css-length</li>
<li>validate-length</li>
<li>validate-percents</li>
<li>required-file</li>
<li>validate-cc-ukss</li>
</ul>
<p>The above mentioned handles the validation of data on the client side. However, client side data can be falsified/manipulated. What we need is the server side validation as well. Let&#8217;s check out few examples to see how Magento is handling the server side of the things. </p>
<p>First lets check out the &#8220;Customer Account Create&#8221; logic. Creating a new customer submits the POST of data to the Mage_Customer_AccountController->createPostAction(). Since customer and customer address are &#8220;EAV models&#8221;, they have a special way of handling the validation of data, they pass the POST data to the validateData() method of your custom class instance which extends the Mage_Eav_Model_Form class. Below is a code snippet that shows this:</p>
<pre class="brush: php; title: ; notranslate">
/* @var $customerForm Mage_Customer_Model_Form */
$customer = Mage::getModel('customer/customer');

$customerForm = Mage::getModel('customer/form');
$customerForm-&gt;setFormCode('customer_account_create')
    -&gt;setEntity($customer);

$customerData = $customerForm-&gt;extractData($this-&gt;getRequest());
// ...
$customerErrors = $customerForm-&gt;validateData($customerData);
</pre>
<p>Validating data on the &#8220;data models&#8221; is done somewhat different, here the model itself is suppose to implement the validate() method. For example, lets check out the Mage_Review_Model_Review model and the action of saving/creating the review itself, Mage_Review_ProductController->postAction():</p>
<pre class="brush: php; title: ; notranslate">
    public function postAction()
    {
        if ($data = Mage::getSingleton('review/session')-&gt;getFormData(true)) {
            $rating = array();
            if (isset($data['ratings']) &amp;&amp; is_array($data['ratings'])) {
                $rating = $data['ratings'];
            }
        } else {
            $data   = $this-&gt;getRequest()-&gt;getPost();
            $rating = $this-&gt;getRequest()-&gt;getParam('ratings', array());
        }

        if (($product = $this-&gt;_initProduct()) &amp;&amp; !empty($data)) {
            $session    = Mage::getSingleton('core/session');
            /* @var $session Mage_Core_Model_Session */
            $review     = Mage::getModel('review/review')-&gt;setData($data);
            /* @var $review Mage_Review_Model_Review */

            $validate = $review-&gt;validate();
            if ($validate === true) {
            // ...
</pre>
<p>The validate() points to the Mage_Review_Model_Review->validate(), which is implemented into the model itself:</p>
<pre class="brush: php; title: ; notranslate">
    public function validate()
    {
        $errors = array();

        $helper = Mage::helper('customer');

        if (!Zend_Validate::is($this-&gt;getTitle(), 'NotEmpty')) {
            $errors[] = $helper-&gt;__('Review summary can\'t be empty');
        }

        if (!Zend_Validate::is($this-&gt;getNickname(), 'NotEmpty')) {
            $errors[] = $helper-&gt;__('Nickname can\'t be empty');
        }

        if (!Zend_Validate::is($this-&gt;getDetail(), 'NotEmpty')) {
            $errors[] = $helper-&gt;__('Review can\'t be empty');
        }

        if (empty($errors)) {
            return true;
        }
        return $errors;
    }
</pre>
<p>As you can see, there is not anything Magento specific here actually, only Zend_Validate calls. However, its a good practice to define and implement validate() method if you are using data models.</p>
<p>Additionally, there is a Mage_Core_Model_Input_Filter_MaliciousCode class which implements the filter() method for filtering out malicious code as the name of the class suggests. You can open the class itself and look for $_expressions to see what expressions it covers by default.</p>
<p>Hope it helps. Cheers.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/programming-magento/validate-your-input-magento-style/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Installing &amp; setting up PHPUnit manually without PEAR</title>
		<link>http://inchoo.net/tools-frameworks/installing-setting-up-phpunit-manually-without-pear/</link>
		<comments>http://inchoo.net/tools-frameworks/installing-setting-up-phpunit-manually-without-pear/#comments</comments>
		<pubDate>Sat, 24 Mar 2012 09:13:42 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Tools & Frameworks]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=12715</guid>
		<description><![CDATA[If there is one thing I hate in PHP world then that&#8217;s PEAR. With full respect to it, but it simply does not work for me. I usually jump around &#8230;]]></description>
			<content:encoded><![CDATA[<p>If there is one thing I hate in PHP world then that&#8217;s PEAR. With full respect to it, but it simply does not work for me. I usually jump around from Linux, Windows, OSX on a monthly basis and honestly I have never had it work out of the box. There where always some installation issues with PEAR itself then with the packages. Experience I&#8217;m describing here is completely subjective so please do not look at it as me trashing the PEAR. If it works for you, then great. If not, then here is s little HowTo on installing the PHPUnit manually without PEAR.<span id="more-12715"></span></p>
<p>You can find the source code of PHPUnit at Sebastian Bergmann GITHub ccount, https://github.com/sebastianbergmann/phpunit/. If you scroll down the page there, you fill find the list of git repositories for core PHPUnit and its dependencies:</p>
<pre class="brush: php; title: ; notranslate">
git clone git://github.com/sebastianbergmann/phpunit.git
git clone git://github.com/sebastianbergmann/dbunit.git
git clone git://github.com/sebastianbergmann/php-file-iterator.git
git clone git://github.com/sebastianbergmann/php-text-template.git
git clone git://github.com/sebastianbergmann/php-code-coverage.git
git clone git://github.com/sebastianbergmann/php-token-stream.git
git clone git://github.com/sebastianbergmann/php-timer.git
git clone git://github.com/sebastianbergmann/phpunit-mock-objects.git
git clone git://github.com/sebastianbergmann/phpunit-selenium.git
git clone git://github.com/sebastianbergmann/phpunit-story.git
git clone git://github.com/sebastianbergmann/php-invoker.git
</pre>
<p>Using the above list, you pull those packages into the appropriate folder on your disk, I usually create the /lib folder and then add it to my PHP include_path path.</p>
<p>Here is how the structure of my folders looks like.</p>
<p><a href="http://inchoo.net/wp-content/uploads/2012/03/phpunit-git-structure.png"><img src="http://inchoo.net/wp-content/uploads/2012/03/phpunit-git-structure.png" alt="" title="phpunit-git-structure" width="266" height="401" class="alignnone size-full wp-image-12904" /></a></p>
<p>Once you have pulled everthing from GIT, then you can modify the /lib/PHPUnit/phpunit.bat file to something like:</p>
<pre class="brush: php; title: ; notranslate">
if &quot;%PHPBIN%&quot; == &quot;&quot; set PHPBIN=&quot;C:\WampDeveloper\Components\Php\php.exe&quot;
if not exist &quot;%PHPBIN%&quot; if &quot;%PHP_PEAR_PHP_BIN%&quot; neq &quot;&quot; goto USE_PEAR_PATH
GOTO RUN
:USE_PEAR_PATH
set PHPBIN=%PHP_PEAR_PHP_BIN%
:RUN
&quot;%PHPBIN%&quot; -d safe_mode=Off &quot;C:\Users\branko\Server\lib\PHPUnit\phpunit.php&quot; %*
</pre>
<p>Now add the &#8220;C:\Users\branko\Server\lib\PHPUnit&#8221; to system path variable, so that &#8220;phpunit&#8221; command on console executes the &#8220;C:\Users\branko\Server\lib\PHPUnit\phpunit.bat&#8221;. You also need to have add php.exe to system path.</p>
<p>Finally, if you pulled the above packages from GIT each to it&#8217;s own folder under some /lib folder then you need to add those to the php.ini include_path, maybe something like:</p>
<pre class="brush: php; title: ; notranslate">
include_path = &quot;.:C:/Users/branko/Server/lib:C:/Users/branko/Server/lib/phpunit/php-file-iterator:C:/Users/branko/Server/lib/phpunit/php-code-coverage:C:/Users/branko/Server/lib/phpunit/php-invoker:C:/Users/branko/Server/lib/phpunit/php-text-template:C:/Users/branko/Server/lib/phpunit/php-timer:C:/Users/branko/Server/lib/phpunit/php-token-stream:C:/Users/branko/Server/lib/phpunit/phpunit-mock-objects:C:/Users/branko/Server/lib/phpunit/phpunit-selenium:C:/Users/branko/Server/lib/phpunit/phpunit-story:C:/Users/branko/Server/lib/phpunit:C:/Users/branko/Server/lib/phing-2.4.9/classes&quot;
</pre>
<p>The above steps should do it, now all you need to do is to actually write some simple unit test and see if it works:</p>
<pre class="brush: php; title: ; notranslate">
&lt; ?php

class DummyTest extends PHPUnit_Framework_TestCase
{
    public function testFail()
    {
        $this-&gt;fail('Your test successfully failed!');
    }
}
</pre>
<p>Save the above code under test1.php then run the console command &#8220;phpunit test1.php&#8221; (given that you are in the folder where test1.php is). If you see the message &#8220;Your test successfully failed!&#8221; then you got everything setup OK.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/tools-frameworks/installing-setting-up-phpunit-manually-without-pear/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Automatically invoice/ship/complete order in Magento</title>
		<link>http://inchoo.net/ecommerce/magento/magento-orders/automatically-invoice-ship-complete-order-in-magento/</link>
		<comments>http://inchoo.net/ecommerce/magento/magento-orders/automatically-invoice-ship-complete-order-in-magento/#comments</comments>
		<pubDate>Tue, 28 Feb 2012 14:55:57 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Orders]]></category>
		<category><![CDATA[invoice]]></category>
		<category><![CDATA[Magento]]></category>
		<category><![CDATA[order]]></category>
		<category><![CDATA[payment]]></category>
		<category><![CDATA[shipment]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=12683</guid>
		<description><![CDATA[Various merchants, various demands. Imagine you have a private on site sale or something like that, where your checkout requirements are pretty simply: create invoice / ship and complete the &#8230;]]></description>
			<content:encoded><![CDATA[<p>Various merchants, various demands. Imagine you have a private on site sale or something like that, where your checkout requirements are pretty simply: create invoice / ship and complete the order all at once. For example, you are doing the checkout for bunch of people standing in front of you, paying you money right on the spot. In such scenario overload of manually creating an invoice and shipment can be too much.<span id="more-12683"></span></p>
<p>Thus, having your Magento automatically invoice/ship/complete orders can be a logical request. So how do we do that?</p>
<p>Easy! All you need to do is to observe the <strong>sales_order_save_after</strong> event or observe the <strong>controller_action_predispatch</strong> event and target the <strong>Mage_Checkout_OnepageController::successAction()</strong> catching the <strong>Mage::getSingleton(&#8216;checkout/session&#8217;)->getLastOrderId()</strong>. In this example I decided to demonstrate the possible (not ideal, or not even the best) &#8220;<strong>sales_order_save_after</strong> event&#8221; approach.</p>
<p>If we where to code everything in form of a module/extension, then all we need are two files. Here is the content of config.xml of our module:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;config&gt;
    &lt;modules&gt;
        &lt;Inchoo_Invoicer&gt;
            &lt;version&gt;1.0.0.0&lt;/version&gt;
        &lt;/Inchoo_Invoicer&gt;
    &lt;/modules&gt;
    &lt;global&gt;
        &lt;models&gt;
            &lt;inchoo_invoicer&gt;
                &lt;class&gt;Inchoo_Invoicer_Model&lt;/class&gt;
            &lt;/inchoo_invoicer&gt;
        &lt;/models&gt;
        &lt;events&gt;
            &lt;sales_order_save_after&gt;
                &lt;observers&gt;
                    &lt;inchoo_invoicer_automatically_complete_order&gt;
                        &lt;class&gt;inchoo_invoicer/observer&lt;/class&gt;
                        &lt;method&gt;automaticallyInvoiceShipCompleteOrder&lt;/method&gt;
                    &lt;/inchoo_invoicer_automatically_complete_order&gt;
                &lt;/observers&gt;
            &lt;/sales_order_save_after&gt;
        &lt;/events&gt;
    &lt;/global&gt;
&lt;/config&gt;
</pre>
<p>And here is the code for our observer model.</p>
<pre class="brush: php; title: ; notranslate">
class Inchoo_Invoicer_Model_Observer
{
    /**
     * Mage::dispatchEvent($this-&gt;_eventPrefix.'_save_after', $this-&gt;_getEventData());
     * protected $_eventPrefix = 'sales_order';
     * protected $_eventObject = 'order';
     * event: sales_order_save_after
     */
    public function automaticallyInvoiceShipCompleteOrder($observer)
    {
        $order = $observer-&gt;getEvent()-&gt;getOrder();

        $orders = Mage::getModel('sales/order_invoice')-&gt;getCollection()
                        -&gt;addAttributeToFilter('order_id', array('eq'=&gt;$order-&gt;getId()));
        $orders-&gt;getSelect()-&gt;limit(1);  

        if ((int)$orders-&gt;count() !== 0) {
            return $this;
        }

        if ($order-&gt;getState() == Mage_Sales_Model_Order::STATE_NEW) {

            try {
                if(!$order-&gt;canInvoice()) {
                    $order-&gt;addStatusHistoryComment('Inchoo_Invoicer: Order cannot be invoiced.', false);
                    $order-&gt;save();
                }

                //START Handle Invoice
                $invoice = Mage::getModel('sales/service_order', $order)-&gt;prepareInvoice();

                $invoice-&gt;setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_OFFLINE);
                $invoice-&gt;register();

                $invoice-&gt;getOrder()-&gt;setCustomerNoteNotify(false);
                $invoice-&gt;getOrder()-&gt;setIsInProcess(true);
                $order-&gt;addStatusHistoryComment('Automatically INVOICED by Inchoo_Invoicer.', false);

                $transactionSave = Mage::getModel('core/resource_transaction')
                    -&gt;addObject($invoice)
                    -&gt;addObject($invoice-&gt;getOrder());

                $transactionSave-&gt;save();
                //END Handle Invoice

                //START Handle Shipment
                $shipment = $order-&gt;prepareShipment();
                $shipment-&gt;register();

                $order-&gt;setIsInProcess(true);
                $order-&gt;addStatusHistoryComment('Automatically SHIPPED by Inchoo_Invoicer.', false);

                $transactionSave = Mage::getModel('core/resource_transaction')
                    -&gt;addObject($shipment)
                    -&gt;addObject($shipment-&gt;getOrder())
                    -&gt;save();
                //END Handle Shipment
            } catch (Exception $e) {
                $order-&gt;addStatusHistoryComment('Inchoo_Invoicer: Exception occurred during automaticallyInvoiceShipCompleteOrder action. Exception message: '.$e-&gt;getMessage(), false);
                $order-&gt;save();
            }
        }

	return $this;
    }
}
</pre>
<p>Before I go any further, I must emphasize that there is a certain known buggy code within automaticallyInvoiceShipCompleteOrder method <img src='http://inchoo.net/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> . This is to keep the example simple. Meaning this code should be written with more detailed checks if it where to go on live site. As you can see, the first thing I&#8217;m doing above is looking into the sales/order_invoice collection to check if there are any existing invoices created for the particular order. If there are none, then I proceed with creating both invoice and shipment.</p>
<p>Next in line is the simple check for order state, IF &#8220;new&#8221; (which means created right now) then do the entire process of invoicing and shipment. This IF condition is a good place if you wish ti limit this code to orders created by specific Payment gateway, for example Check/Money which has the &#8220;checkmo&#8221; code, in which case all you need to do is call <strong>if ($order->getPayment()->getMethod == &#8216;checkmo&#8217;)</strong>, etc. The above code was tested on Magento 1.6.2.0. If you plan using it on some live site, please test it thoroughly before and adjust it to your specific needs.</p>
<p>Cheers.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/magento-orders/automatically-invoice-ship-complete-order-in-magento/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Building a &#8220;Facebook Like&#8221; button extension for Magento in 15 minutes</title>
		<link>http://inchoo.net/ecommerce/magento/magento-products/building-a-facebook-like-button-extension-for-magento-in-15-minutes/</link>
		<comments>http://inchoo.net/ecommerce/magento/magento-products/building-a-facebook-like-button-extension-for-magento-in-15-minutes/#comments</comments>
		<pubDate>Mon, 27 Feb 2012 14:00:04 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Products]]></category>
		<category><![CDATA[extension]]></category>
		<category><![CDATA[facebook]]></category>
		<category><![CDATA[like]]></category>
		<category><![CDATA[Magento]]></category>
		<category><![CDATA[product]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=12666</guid>
		<description><![CDATA[Facebook LIKE button is pretty easy and straight forward to implement. All you need to do is to copy paste 2-3 sections from Facebook developer site adjust them to your &#8230;]]></description>
			<content:encoded><![CDATA[<p>Facebook LIKE button is pretty easy and straight forward to implement. All you need to do is to copy paste 2-3 sections from Facebook developer site adjust them to your needs and you are done.<span id="more-12666"></span></p>
<p>Since we will be coding this for Magento, why not make it an extension, Inchoo_Flike. Imagine I&#8217;m working under the budget and my time is limited, I&#8217;ll do my best to code is as fast as I can, but still keeping the &#8220;Magento way of things&#8221; in mind. With that in mind, here is the content of my extensions config.xml:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;config&gt;
    &lt;modules&gt;
        &lt;Inchoo_Flike&gt;
            &lt;version&gt;1.0.0.0&lt;/version&gt;
        &lt;/Inchoo_Flike&gt;
    &lt;/modules&gt;
    &lt;frontend&gt;
        &lt;layout&gt;
            &lt;updates&gt;
                &lt;inchoo_flike&gt;
                    &lt;file&gt;inchoo/flike/flike.xml&lt;/file&gt;
                &lt;/inchoo_flike&gt;
            &lt;/updates&gt;
        &lt;/layout&gt;
    &lt;/frontend&gt;
&lt;/config&gt;
</pre>
<p>As implied within the above&#8217;s config I need to add the inchoo/flike/flike.xml under the default theme layout folder. Here is the content of flike.xml file:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;layout version=&quot;0.1.0&quot;&gt;
    &lt;catalog_product_view&gt;
        &lt;reference name=&quot;head&quot;&gt;
            &lt;block type=&quot;core/template&quot; name=&quot;inchoo_flike_tags&quot; template=&quot;inchoo/flike/tags.phtml&quot; before=&quot;-&quot; /&gt;
        &lt;/reference&gt;
        &lt;reference name=&quot;after_body_start&quot;&gt;
            &lt;block type=&quot;core/template&quot; name=&quot;inchoo_flike_js&quot; template=&quot;inchoo/flike/js.phtml&quot; before=&quot;-&quot; /&gt;
        &lt;/reference&gt;
        &lt;reference name=&quot;alert.urls&quot;&gt;
            &lt;block type=&quot;core/template&quot; name=&quot;inchoo_flike_button&quot; template=&quot;inchoo/flike/button.phtml&quot; before=&quot;-&quot; /&gt;
        &lt;/reference&gt;
    &lt;/catalog_product_view&gt;
&lt;/layout&gt;
</pre>
<p>For sake of simplicity and budget restriction, I decided to stay within the frames of &#8220;core/template&#8221; blocks. I can spare a minute or two on this one <img src='http://inchoo.net/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> . Now we move on to the files mentioned in the flike.xml file: tags.phtml, js.phtml, button.phtml.</p>
<p>File tags.phtml contains the &#8220;Open Graph Tags&#8221;, as we want to have the correct product image, description, etc. when we LIKE our product. Since this stuff needs to go under the &#8220;head&#8221; of HTML page, I injected it under the head block. Here is the content of tags.phtml:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php $_product = Mage::registry('current_product') ?&gt;
&lt;?php if ($_product &amp;&amp; $_product-&gt;getId()): ?&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;&lt;?php echo $this-&gt;stripTags($_product-&gt;getName(), null, true) ?&gt;&quot; /&gt;
&lt;meta property=&quot;og:type&quot; content=&quot;product&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;&lt;?php echo $this-&gt;helper('catalog/image')-&gt;init($_product, 'small_image')-&gt;resize(130, 110); ?&gt;&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;&lt;?php echo $_product-&gt;getProductUrl() ?&gt;&quot; /&gt;
&lt;meta property=&quot;og:site_name&quot; content=&quot;&lt;?php echo Mage::app()-&gt;getStore()-&gt;getName() ?&gt;&quot; /&gt;
&lt;?php endif; ?&gt;
</pre>
<p>Notice the detail around the image size. This conforms to the official Facebook definition of <em>og:image</em> tag: <em>The og:image is the URL to the image that appears in the Feed story. The thumbnail&#8217;s width AND height must be at least 50 pixels, and cannot exceed 130&#215;110 pixels. The ratio of both height divided by width and width divided by height (w/h, h/w) cannot exceed 3.0. For example, an image of 126&#215;39 pixels will not be displayed, as the ratio of width divided by height is greater than 3.0 (126/39 = 3.23). Images will be resized proportionally.</em></p>
<p>File js.phtml containes the JavaScript code that goes along with LIKE button:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php $_product = Mage::registry('current_product') ?&gt;
&lt;?php if ($_product &amp;&amp; $_product-&gt;getId()): ?&gt;
&lt;div id=&quot;fb-root&quot;&gt;&lt;/div&gt;
&lt;script&gt;(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) return;
  js = d.createElement(s); js.id = id;
  js.src = &quot;//connect.facebook.net/en_US/all.js#xfbml=1&quot;;
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));&lt;/script&gt;
&lt;?php endif; ?&gt;
</pre>
<p>And finally, file button.phtml contains the actual button:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php $_product = Mage::registry('current_product') ?&gt;
&lt;?php if ($_product &amp;&amp; $_product-&gt;getId()): ?&gt;
&lt;div class=&quot;fb-like&quot; data-href=&quot;&lt;?php echo $_product-&gt;getProductUrl() ?&gt;&quot; data-send=&quot;true&quot; data-width=&quot;450&quot; data-show-faces=&quot;true&quot;&gt;&lt;/div&gt;
&lt;?php endif; ?&gt;
</pre>
<p>And that&#8217;s it. If you did not do any heavy modifications around your custom theme, more precisely the product view page by removing the &#8220;alert.urls&#8221; block, then you should be able to see Facebook LIKE button there, right near the product name.</p>
<p>Surely there is room for improvement to this little extension. Consider it only as a quick example.<br />
Cheers.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/magento-products/building-a-facebook-like-button-extension-for-magento-in-15-minutes/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Magento EE, use reward points in X percent of total (subtotal) checkout value</title>
		<link>http://inchoo.net/ecommerce/magento/magento-checkout/magento-ee-use-reward-points-in-x-percent-of-total-subtotal-checkout-value/</link>
		<comments>http://inchoo.net/ecommerce/magento/magento-checkout/magento-ee-use-reward-points-in-x-percent-of-total-subtotal-checkout-value/#comments</comments>
		<pubDate>Mon, 27 Feb 2012 10:00:02 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Checkout]]></category>
		<category><![CDATA[checkout]]></category>
		<category><![CDATA[Magento]]></category>
		<category><![CDATA[points]]></category>
		<category><![CDATA[reward]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=12655</guid>
		<description><![CDATA[Magento EE is a great platform, it offers so much features out of the box. However, merchants can be relentless sometimes and always ask for that little extra around a &#8230;]]></description>
			<content:encoded><![CDATA[<p>Magento EE is a great platform, it offers so much features out of the box. However, merchants can be relentless sometimes and always ask for that little extra around a given feature. So happens that I&#8217;ve been working on a project recently where we needed to squeeze a little extra from Magenot&#8217;s Reward Points functionality. We need the possibility to use reward points in X percent of total (subtotal) checkout value. This is not a built in option that you can configure, so it required a bit of custom coding.<span id="more-12655"></span></p>
<p>I&#8217;ll be honest with you, every time I need to poke around Magento checkout I get all nervous and jumpy, as you never know how much time it will take you. After some careful planning and analysis I came up with the quick solution. Why not simply override the method that returns the reward points balance and fake the reward points amount by implementing the &#8220;use reward points in X percent of total (subtotal) checkout value&#8221; logic. There is one major drawback to this, if we override the method to fake the amount of reward point&#8217;s then the amount would be faked trough entire site, which we do not want. We just want to fake it throughout the checkout process until we create a successful order.</p>
<p>So where do we begin (first off all, we are talking about Magento EE so I&#8217;m unable to give you a more detailed code overview, only what I coded)? We will begin by rewriting the Enterprise_Reward_Model_Reward class trough your modules config.xml file.</p>
<pre class="brush: xml; title: ; notranslate">
&lt;global&gt;
	&lt;models&gt;
		&lt;enterprise_reward&gt;
			&lt;rewrite&gt;
				&lt;reward&gt;Inchoo_MyModule_Model_Reward&lt;/reward&gt;
			&lt;/rewrite&gt;
		&lt;/enterprise_reward&gt;
	&lt;/models&gt;
&lt;/global&gt;
</pre>
<p>Then the implementation of Inchoo_MyModule_Model_Reward itself.</p>
<pre class="brush: php; title: ; notranslate">
class Inchoo_MyModule_Model_Reward extends Enterprise_Reward_Model_Reward
{
    public function getPointsBalance()
    {
        $request = Mage::app()-&gt;getRequest();

        if ($request-&gt;getModuleName() == 'checkout') {
            if (($quote = Mage::getModel('checkout/session')-&gt;getQuote())) {
                $totals = $quote-&gt;getTotals();
                $cartTotal = floor($totals[&quot;subtotal&quot;]-&gt;getValue());
                if ($cartTotal &gt; 0) {
                    $rpPercent = 23; /* some integer value representing percent, could read this trough some config */

                    $rpAllowedMax = $cartTotal * ($rpPercent / 100);
                    $rpAllowedMax = floor($rpAllowedMax);

                    if ((int)$this-&gt;getData('points_balance') &gt;= $rpAllowedMax) {
                        return $rpAllowedMax;
                    }
                }
            }
        }

        return $this-&gt;getData('points_balance');
    }
}
</pre>
<p>The best part of all is the getPointsBalance() method itself. If you look at the parent class, you will see that the method does not exit there. It&#8217;s one of those Magento/PHP magic method implemented trough Varien_Object. So, by implementing the method our-self we get the control of the returned balance value, while still being able to fetch the true non-touched value of points_balance by calling the $reward->getData(&#8216;points_balance&#8217;) later in the code.</p>
<p>And for the grand finale, we simply limit the special &#8220;use reward points in X percent of total (subtotal) checkout value&#8221; function behavior to &#8220;checkout&#8221; module by usage of <strong>if($request->getModuleName() == &#8216;checkout&#8217;)</strong>. If you trace the controller actions more thoroughly, you can even limit it to a controller action level. Just be sure to test everything later so that you do not break calls to $reward->getPointsBalance() on a places that should not implement the above logic.</p>
<p>You might also change the default message for Reward Points shown on the Payment step of the checkout to something like:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php echo Mage::helper('enterprise_reward')-&gt;__('Use my reward points, %s available out of %d total', Mage::helper('enterprise_reward')-&gt;formatReward($this-&gt;getPointsBalance(), $this-&gt;getCurrencyAmount()), $this-&gt;getReward()-&gt;getData('points_balance')); ?&gt;
</pre>
<p>With this simple approach I was able to get the exact behavior I needed. I do realize that the approach I took might not be the best optimized, or in the best spirit of MVC since I was doing a module/controller action check within the module, but it strikes the right balance between &#8220;do it on time and do it stable&#8221; <img src='http://inchoo.net/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>I realize this article is somewhat specific and might not apply directly to wider public, but I hope it was useful, at least as an idea <img src='http://inchoo.net/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> .</p>
<p>Cheers.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/magento-checkout/magento-ee-use-reward-points-in-x-percent-of-total-subtotal-checkout-value/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Listing out Previous/Next products in Magento (Inchoo_Prevnext extension)</title>
		<link>http://inchoo.net/ecommerce/magento/magento-products/listing-out-previousnext-products-in-magento-inchoo_prevnext-extension/</link>
		<comments>http://inchoo.net/ecommerce/magento/magento-products/listing-out-previousnext-products-in-magento-inchoo_prevnext-extension/#comments</comments>
		<pubDate>Mon, 27 Feb 2012 09:05:43 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Products]]></category>
		<category><![CDATA[extension]]></category>
		<category><![CDATA[Magento]]></category>
		<category><![CDATA[next]]></category>
		<category><![CDATA[previous]]></category>
		<category><![CDATA[product]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=12643</guid>
		<description><![CDATA[There are several Previous/Next extensions out there for Magento by now. Honestly I do not know how any of them works as I never used any . I decided to &#8230;]]></description>
			<content:encoded><![CDATA[<p>There are several Previous/Next extensions out there for Magento by now. Honestly I do not know how any of them works as I never used any <img src='http://inchoo.net/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> . I decided to code my own since I wanted to tackle the challenge of coding the previous/next that works alongside the layered navigation filter and sort order in the product listing grid.<span id="more-12643"></span></p>
<p>Here is an example code for the extension (partial but fully functional) I just posted few minutes ago on the Magento Connect (still waiting for approval, I&#8217;ll update the post with the link once it&#8217;s approved).</p>
<p>First we start of with the config.xml file of the Inchoo_Prevnext extension:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;config&gt;
...
    &lt;frontend&gt;
        &lt;events&gt;
            &lt;controller_action_postdispatch&gt;
                &lt;observers&gt;
                    &lt;inchoo_prevnext_set_filtered_category_product_collection&gt;
                        &lt;class&gt;inchoo_prevnext/observer&lt;/class&gt;
                        &lt;method&gt;setInchooFilteredCategoryProductCollection&lt;/method&gt;
                    &lt;/inchoo_prevnext_set_filtered_category_product_collection&gt;
                &lt;/observers&gt;
            &lt;/controller_action_postdispatch&gt;
        &lt;/events&gt;
    &lt;/frontend&gt;
...
&lt;/config&gt;
</pre>
<p>Now we implement the actual observer method. Here I am grabbing the loaded product collection, the one last looked at the category product listing page. As you can see, I&#8217;m focusing down to the &#8220;category&#8221; controller, &#8220;view&#8221; action, extracting the product id&#8217;s from collection and setting them under the inchoo_filtered_category_product_collection session key. This way when I go on the product view page I can read the last viewed products grid and then decide what is previous and what next product. Next time I&#8217;m on category product listing grid this code will simply reset the id&#8217;s to those shown in the grid.</p>
<pre class="brush: php; title: ; notranslate">
class Inchoo_Prevnext_Model_Observer
{
    public function setInchooFilteredCategoryProductCollection()
    {
        /**
         * There might be some illogical buggy behavior when coming directly
         * from &quot;Related products&quot; / &quot;Recently viewed&quot; products block.
         * Nothing that should break the page however.
         */
	if (Mage::app()-&gt;getRequest()-&gt;getControllerName() == 'category' &amp;&amp; Mage::app()-&gt;getRequest()-&gt;getActionName() == 'view') {

		$products = Mage::app()-&gt;getLayout()
				-&gt;getBlockSingleton('Mage_Catalog_Block_Product_List')
				-&gt;getLoadedProductCollection()
				-&gt;getColumnValues('entity_id');

		Mage::getSingleton('core/session')
				-&gt;setInchooFilteredCategoryProductCollection($products);

		unset($products);
	}

	return $this;
    }
}
</pre>
<p>And finally the helper class to host the getPreviousProduct and getNextProduct methods.</p>
<pre class="brush: php; title: ; notranslate">
class Inchoo_Prevnext_Helper_Data extends Mage_Core_Helper_Abstract
{
    /**
     * @return Mage_Catalog_Model_Product or FALSE
     */
    public function getPreviousProduct()
    {
            $prodId = Mage::registry('current_product')-&gt;getId();

            $positions = Mage::getSingleton('core/session')-&gt;getInchooFilteredCategoryProductCollection();

            if (!$positions) {
                $positions = array_reverse(array_keys(Mage::registry('current_category')-&gt;getProductsPosition()));
            }

            $cpk = @array_search($prodId, $positions);

            $slice = array_reverse(array_slice($positions, 0, $cpk));

            foreach ($slice as $productId) {
                    $product = Mage::getModel('catalog/product')
                                                    -&gt;load($productId);

                    if ($product &amp;&amp; $product-&gt;getId() &amp;&amp; $product-&gt;isVisibleInCatalog() &amp;&amp; $product-&gt;isVisibleInSiteVisibility()) {
                            return $product;
                    }
            }

            return false;
    }

    /**
     * @return Mage_Catalog_Model_Product or FALSE
     */
    public function getNextProduct()
    {
            $prodId = Mage::registry('current_product')-&gt;getId();

            $positions = Mage::getSingleton('core/session')-&gt;getInchooFilteredCategoryProductCollection();

            if (!$positions) {
                $positions = array_reverse(array_keys(Mage::registry('current_category')-&gt;getProductsPosition()));
            }            

            $cpk = @array_search($prodId, $positions);

            $slice = array_slice($positions, $cpk + 1, count($positions));

            foreach ($slice as $productId) {
                    $product = Mage::getModel('catalog/product')
                                                    -&gt;load($productId);

                    if ($product &amp;&amp; $product-&gt;getId() &amp;&amp; $product-&gt;isVisibleInCatalog() &amp;&amp; $product-&gt;isVisibleInSiteVisibility()) {
                            return $product;
                    }
            }

            return false;
    }
}
</pre>
<p>There is a known &#8220;issue&#8221; with this approach, that is it does not compensate for the rest of the possible pages in the category product grid. This is due to the fact that at the moment of fetching the id&#8217;s of category listed products we still do now know the ids of the products for possible other pages. This however would require more load on the server as it requires one or two extra database queries. Additionally, there might be some illogical prev/next from time to time (like if you are landing on a product directly without ever being on some category view page within the current session, etc) <img src='http://inchoo.net/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />  -Thus there is a room for improvement, but it&#8217;s important to know that nothing will get broken due to this.</p>
<p>Hope this code was helpful, and some of you find the room to improve it.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/magento-products/listing-out-previousnext-products-in-magento-inchoo_prevnext-extension/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Custom Magento Events: Customer First Order</title>
		<link>http://inchoo.net/ecommerce/magento/custom-magento-events-customer-first-order/</link>
		<comments>http://inchoo.net/ecommerce/magento/custom-magento-events-customer-first-order/#comments</comments>
		<pubDate>Sat, 28 Jan 2012 11:09:20 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Events & Observers]]></category>
		<category><![CDATA[Magento]]></category>
		<category><![CDATA[custom event]]></category>
		<category><![CDATA[event]]></category>
		<category><![CDATA[observer]]></category>
		<category><![CDATA[order]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=12190</guid>
		<description><![CDATA[Often you will stumble upon a case where Magento lacks certain events that you can easily observe. Various business cases can sometimes truly stretch the boundaries of even the best &#8230;]]></description>
			<content:encoded><![CDATA[<p>Often you will stumble upon a case where Magento lacks certain events that you can easily observe. Various business cases can sometimes truly stretch the boundaries of even the best shopping carts like Magento. Luckily, creating or more properly said dispatching your own event in Magento is pretty straight forward task.<span id="more-12190"></span></p>
<p>Imagine the following business case, where your client, the merchant says: <em><strong>I need to give some reward points to Customer A who invited Customer B. These reward points will be assigned one time only and at the moment when Customer B creates it&#8217;s first order in the system</strong></em>.</p>
<p>Ideal scenario would be if you had the built in Magento event like &#8220;customer_first_order&#8221; or if you need more finer tuning to target the very state of the order, for example &#8220;customer_first_order_that_reached_state_complete&#8221;. Following along with the client requirement above, I will show you how you can easily make additional events with just some basic thinking invested into the whole process.</p>
<p>So where do we start? I will start from the built in &#8220;sales_order_save_after&#8221; event. Logically I need to do something after the order is created. From there I will do the logic that checks if this is customers first order or not. If you are new to Magento and you do a lookup/search on entire Magento installation code you will not find the expression <em>Mage::dispatchEvent(&#8216;sales_order_save_after&#8217;, array(&#8216;object&#8217;=>$this));</em> anywhere.</p>
<p>However you will find the defined values for <em>$_eventPrefix</em> and <em>$_eventObject</em> properties under the <em>Mage_Sales_Model_Order</em> class. Since <em>Mage_Sales_Model_Order</em> somewere down the line inherits from <em>Mage_Core_Model_Abstract</em> you can check it&#8217;s <em>_afterSave()</em> method and easily conclude that <em>sales_order_save_after</em> event comes from the expression <em>Mage::dispatchEvent($this->_eventPrefix.&#8217;_save_after&#8217;, $this->_getEventData());</em>.</p>
<p>The most important thing here for us is to &#8220;catch&#8221; the parameters that are passed to event. Function call <em>$this->_getEventData())</em> basically returns the array of <em>array(&#8216;data_object&#8217; => $this, $this->_eventObject => $this);</em>. As we mentioned previously <em>$_eventObject</em> property has the value of &#8220;<em>order</em>&#8221; set under the <em>Mage_Sales_Model_Order</em> class. What this means that all we need to do in our &#8220;<em>sales_order_save_after</em>&#8221; event observer in order to grab the order passed to the event is an expression like <em>$observer->getEvent()->Order();</em>.</p>
<p>From there on, we will start implementing the code for our specific client requirement. Before we do so, here is the actual code you need in order to have your <em>sales_order_save_after</em> event observer functional.</p>
<p><strong>config.xml</strong> from within your extension:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;config&gt;
//...
	&lt;frontend&gt;
		&lt;events&gt;
            &lt;sales_order_save_after&gt;
                &lt;observers&gt;
                    &lt;customer_first_order&gt;
                        &lt;class&gt;myClassGroup/observer&lt;/class&gt;
                        &lt;method&gt;handleCustomerFirstOrder&lt;/method&gt;
                    &lt;/customer_first_order&gt;
                &lt;/observers&gt;
            &lt;/sales_order_save_after&gt;
		&lt;/events&gt;
	&lt;/frontend&gt;
//...
&lt;/config&gt;
</pre>
<p><strong>Observer.php</strong> from within your extension:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php

class MyCompany_MyExtension_Model_Observer
{
	public function handleCustomerFirstOrder($observer)
	{

	}
}
</pre>
<p>Now we need to add the necessary logic that implements client&#8217;s specific requirement, which transforms the above Observer.php into something like this:</p>
<pre class="brush: php; title: ; notranslate">
class MyCompany_MyExtension_Model_Observer
{
    private static $_handleCustomerFirstOrderCounter = 1;

	public function handleCustomerFirstOrder($observer)
	{
        $orders = Mage::getModel('sales/order')
                    -&gt;getCollection()
                    -&gt;addFieldToSelect('increment_id')
                    -&gt;addFieldToFilter('customer_id', array('eq' =&gt; $observer-&gt;getEvent()-&gt;getOrder()-&gt;getCustomerId()));

        //$orders-&gt;getSelect()-&gt;limit(2);

        //if ($orders-&gt;count() == 1) {
        if ($orders-&gt;getSize() == 1) {
            if (self::$_handleCustomerFirstOrderCounter &gt; 1) {
                return $this;
            }

            self::$_handleCustomerFirstOrderCounter++;

			Mage::dispatchEvent('customer_first_order', array('order' =&gt; $observer-&gt;getEvent()-&gt;getOrder()));
		}		

		return $this;
	}
}
</pre>
<p>Several things to explain here. </p>
<p>First the use of <em>$_handleCustomerFirstOrderCounter</em>. I have noticed that during the same page request on order after save action, depending on possible other existing event observers that might do order re-save action, etc. my code within <em>handleCustomerFirstOrder($observer)</em> method can get executed twice or more times, thus I used static variable in a form of counter to specifically allow it to execute only once.</p>
<p>Second, you will notice the limit I put on the colelction, <em>$orders->getSelect()->limit(2);</em>. Reason for this is the logic that says &#8220;check if this is customers first order&#8221;. In order to do that, I need to know if there are other order in the system for the same customer. Since there is no need to fetch/load or even query for all of the order&#8217;s of the same customers I merely said &#8220;tray to grab two orders from this customer&#8221; then with <em>if ($orders->count() == 1)</em> expression I merely said, if total count of grabbed orders is one then this is the first order by this customer. Now you will notice that I commented out the <em>$orders->getSelect()->limit(2);</em> and <em>if ($orders->count() == 1)</em> expressions at the end and went with the use of getSize() method on the collection. Looks nicer and does the same thing. Method getSize() comes from the Varien_Data_Collection_Db class and internally it actually calls the getSelectCountSql() method that specifically executes count on the database, <em>$countSelect->columns(&#8216;COUNT(*)&#8217;);</em> ,and returns just the count value. Thus making it even faster.</p>
<p>And finally, in place of implementing my reward points logic code directly there, I decided to trigger the <em>customer_first_order</em> event for this case. Reason is simply, maybe other developers in the system would like do do something else on this event, so why not dispatch it.</p>
<p>To conclude the client&#8217;s request I simply implement one more observer that handles the <em>customer_first_order</em> event and within that observer I give customer A it&#8217;s earned reward points.</p>
<p>Hopefully this article was helpful for some.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/custom-magento-events-customer-first-order/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Geocoding Customer Addresses in Magento via Google Maps</title>
		<link>http://inchoo.net/ecommerce/magento/geocoding-customer-addresses-in-magento-via-google-maps/</link>
		<comments>http://inchoo.net/ecommerce/magento/geocoding-customer-addresses-in-magento-via-google-maps/#comments</comments>
		<pubDate>Wed, 14 Dec 2011 14:35:22 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Extensions]]></category>
		<category><![CDATA[Magento]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[extension]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[map]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=11874</guid>
		<description><![CDATA[Geocoding is the process of finding associated geographic coordinates (often expressed as latitude and longitude) from other geographic data, such as street addresses, or zip codes (postal codes)&#8230; so says &#8230;]]></description>
			<content:encoded><![CDATA[<p>Geocoding is the process of finding associated geographic coordinates (often expressed as latitude and longitude) from other geographic data, such as street addresses, or zip codes (postal codes)&#8230; so says Wikipedia. Geocoding an address is pretty simple if you are using Google Maps API. Since all we do here is Magento, let me show you how easily you can geocode customer address.<span id="more-11874"></span></p>
<p>We will start with adding the Gmap.php helper to our extension:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * @author Branko Ajzele &lt;ajzele@gmail.com&gt;
 */
class Inchoo_Extension_Helper_Gmap extends Mage_Core_Helper_Abstract
{
    const GOOGLE_MAPS_HOST = 'maps.google.com';
    const CONFIG_PATH_GOOGLE_MAPS_API_KEY = 'inchoo_google/maps/api_key';

    public function getGoogleMapsApiKey()
    {
        return Mage::getStoreConfig(self::CONFIG_PATH_GOOGLE_MAPS_API_KEY);
    }

    /**
     *
     * @param Mage_Customer_Model_Address $address
     * @param boolean $saveCoordinatesToAddress
     * @param boolean $forceLookup
     * @return array Returns the array of float values like {[0] =&gt; float(18.4438156) [1] =&gt; float(45.5013644)}
     */
    public function fetchAddressGeoCoordinates(Mage_Customer_Model_Address $address, $saveCoordinatesToAddress = true, $forceLookup = false)
    {
        /**
         * USAGE EXAMPLE

            $customer = Mage::getModel('customer/customer');
            $customer-&gt;setWebsiteId(Mage::app()-&gt;getWebsite()-&gt;getId());
            $customer-&gt;loadByEmail('some-email@domain.com');

            Mage::helper('inchoo/gmap')-&gt;fetchAddressGeoCoordinates($customer-&gt;getDefaultBillingAddress())

         */

        $coordinates = array();

        if ($address-&gt;getId() &amp;&amp; $address-&gt;getGmapCoordinatePointX() &amp;&amp; $address-&gt;getGmapCoordinatePointY() &amp;&amp; $forceLookup == false) {
            $coordinates = array($address-&gt;getGmapCoordinatePointX(), $address-&gt;getGmapCoordinatePointY());
        } else if (!$address-&gt;getGmapCoordinatePointX() &amp;&amp; !$address-&gt;getGmapCoordinatePointY())
            OR ($address-&gt;getId() &amp;&amp; $address-&gt;getGmapCoordinatePointX() &amp;&amp; $address-&gt;getGmapCoordinatePointY() &amp;&amp; $forceLookup == true)) {

            $lineAddress = $address-&gt;getStreet1(). ', '.$address-&gt;getPostcode().' '.$address-&gt;getCity().', '.$address-&gt;getCountry();

            $client = new Zend_Http_Client();
            $client-&gt;setUri('http://'.self::GOOGLE_MAPS_HOST.'/maps/geo');
            $client-&gt;setMethod(Zend_Http_Client::GET);
            $client-&gt;setParameterGet('output', 'json');
            $client-&gt;setParameterGet('key', $this-&gt;getGoogleMapsApiKey());
            $client-&gt;setParameterGet('q', $lineAddress);

            $response = $client-&gt;request();

            if ($response-&gt;isSuccessful() &amp;&amp; $response-&gt;getStatus() == 200) {
                $_response = json_decode($response-&gt;getBody());
                $_coordinates = @$_response-&gt;Placemark[0]-&gt;Point-&gt;coordinates;

                if (is_array($_coordinates) &amp;&amp; count($_coordinates) &gt;= 2) {

                    $coordinates = array_slice($_coordinates, 0, 2);

                    if ($saveCoordinatesToAddress) {
                        try {
                            $address-&gt;setGmapLng($coordinates[0]);
                            $address-&gt;setGmapLat($coordinates[1]);

                            $address-&gt;save();
                        } catch (Exception $e) {
                            Mage::logException($e);
                        }
                    }
                }
            }
        }

        return $coordinates;
    }
}
</pre>
<p>As you can see above in the code, function getGoogleMapsApiKey() pulls the API key info from my system configuration. I will not show you that part here. So for the sake of simplicity if you do not know how to code a configuration for your module you can just return a raw API key string here (surely this approach is then just for testing/playing not on production).</p>
<p>Since this helper references the two non-defualt-existing attributes on the address we will need to add those attributes. We do so trough install/upgrade scripts (you know, the files under app/code/{codePool}/Company/Extension/sql/extension_setup/ folder). I will not give you the full example of the file and it&#8217;s corresponding config.xml entry, as you should know how to do it yourself already, rather just the partial code that goes into the install/upgrade script in order to add the two missing customer address attributes:</p>
<pre class="brush: php; title: ; notranslate">
//Add attribute that will be used for Google Maps as a coordinate lng
$installer-&gt;addAttribute('customer_address', 'gmap_lng', array(
    'type'     =&gt; 'varchar',
    'input'    =&gt; 'hidden',
    'visible'  =&gt; false,
    'required' =&gt; false
));
//Add attribute that will be used for Google Maps as a coordinate lat
$installer-&gt;addAttribute('customer_address', 'gmap_lat', array(
    'type'     =&gt; 'varchar',
    'input'    =&gt; 'hidden',
    'visible'  =&gt; false,
    'required' =&gt; false
));
</pre>
<p>Next, we will add the observer to the proper _beforeSave() dispatched event of the Mage_Customer_Model_Address model class. Since Mage_Customer_Model_Address extends the Mage_Customer_Model_Address_Abstract which defines the &#8220;protected $_eventPrefix = &#8216;customer_address&#8217;;&#8221; we can easily conclude the all it takes is to create observer for &#8220;customer_address_save_before&#8221; event (Mage_Core_Model_Abstract::_beforeSave() &#8230; we have Mage::dispatchEvent($this-&gt;_eventPrefix.&#8217;_save_before&#8217;, $this-&gt;_getEventData());).</p>
<p>So we add something like this to the config.xml file of our extension:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;global&gt;

    &lt;events&gt;
        &lt;customer_address_save_before&gt;
            &lt;observers&gt;
                &lt;inchoo_observer_customer_address_save_before&gt;
                    &lt;type&gt;singleton&lt;/type&gt;
                    &lt;class&gt;Inchoo_Extension_Model_Observer&lt;/class&gt;
                    &lt;method&gt;geocodeAddress&lt;/method&gt;
                &lt;/inchoo_observer_customer_address_save_before&gt;
            &lt;/observers&gt;
        &lt;/customer_address_save_before&gt;
    &lt;/events&gt;

&lt;/global&gt;
</pre>
<p>Then we create the observer Inchoo_Extension_Model_Observer with something like:</p>
<p>Within that observer we simply need to call something like:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
class Inchoo_Extension_Model_Observer
{
	public function geocodeAddress()
	{
		Mage::helper('inchoo/gmap')
			-&gt;fetchAddressGeoCoordinates($observer-&gt;getEvent()-&gt;getCustomerAddress());

		return $this;
	}
}
</pre>
<p>Basically that&#8217;s it. Now each time an address is saved in Magento, there should be a lookup on Google Maps API in order to fetch the longitude and latitude coordinates. Please note, that all of this is just to fetch the coordinates and save them under the address. Showing the actual map somewhere on the frontend or backend requires a bit more input.</p>
<p>To show the Google Map on the frontend with the proper location marker shown on the map you need two things. First we need to add the script like shown below to the head part of the page:</p>
<pre class="brush: php; title: ; notranslate">
&lt;script type=&quot;text/javascript&quot; src=&quot;http://maps.googleapis.com/maps/api/js?key=&lt;?php echo Mage::helper('inchoo/gmap')-&gt;getGoogleMapsApiKey() ?&gt;&amp;sensor=false&quot;&gt;&lt;/script&gt;
</pre>
<p>Then finally we need to add something like:</p>
<pre class="brush: php; title: ; notranslate">

&lt;div id=&quot;address&lt;?php echo $_address-&gt;getId() ?&gt;-gmap&quot; style=&quot;width:480px; height:480px;&quot;&gt;&lt;/div&gt;

&lt;script type=&quot;text/javascript&quot;&gt;
//&lt;![CDATA[

	Event.observe(window, 'load', function() {

			  var myLatlng = new google.maps.LatLng(&lt;?php echo $_address-&gt;getGmapLat() ?&gt;,&lt;?php echo $_address-&gt;getGmapLng() ?&gt;);
			  var myOptions = {
				zoom: 4,
				center: myLatlng,
				mapTypeId: google.maps.MapTypeId.ROADMAP
			  }
			  var map = new google.maps.Map(document.getElementById(&quot;address&lt;?php echo $_address-&gt;getId() ?&gt;-gmap&quot;), myOptions);

			  var marker = new google.maps.Marker({
				  position: myLatlng,
				  map: map,
				  title:&quot;Address Location&quot;
			  });

	});
//]]&gt;
&lt;/script&gt;
</pre>
<p>And that&#8217;s it. Please note, the code above might not be fully copy paste, check the JavaScript when you implement it, and test if your customer address attributes are installed correctly. This article is not for Magento beginners so if you experience some difficulties implementing this approach, please consult the <a href="http://www.magentocommerce.com/knowledge-base">Magento Knowledge Base</a> first.</p>
<p><em>P.S. I was playing with this on Magento CE 1.6.1.0.</em></p>
<p>Hope this was helpful. Cheers.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/geocoding-customer-addresses-in-magento-via-google-maps/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Reusing Magento&#8217;s calendar control</title>
		<link>http://inchoo.net/ecommerce/magento/reusing-magento-calendar-control/</link>
		<comments>http://inchoo.net/ecommerce/magento/reusing-magento-calendar-control/#comments</comments>
		<pubDate>Tue, 06 Dec 2011 09:13:58 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Frontend]]></category>
		<category><![CDATA[Magento]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[block]]></category>
		<category><![CDATA[calendar]]></category>
		<category><![CDATA[control]]></category>
		<category><![CDATA[widget]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=11783</guid>
		<description><![CDATA[One of the cool things in Magento is that there is so many controls (widgets, blocks), etc. that you can reuse on the frontend or on the backend. One such &#8230;]]></description>
			<content:encoded><![CDATA[<p>One of the cool things in Magento is that there is so many controls (widgets, blocks), etc. that you can reuse on the frontend or on the backend. One such control is the calendar control. Calendar control is mostly just used on the backend, aka &#8220;adminhtml&#8221; area. You can easily figure that one out if you do a quick search for a &#8220;Calendar.setup&#8221; string on &#8220;app/design&#8221; folder, in which case you would get several search results but all from &#8220;app/design/adminhtml/&#8221; folder.<span id="more-11783"></span></p>
<p><a href="http://inchoo.net/wp-content/uploads/2011/12/Snap_2011.12.06_09h39m21s_001_.png"><img src="http://inchoo.net/wp-content/uploads/2011/12/Snap_2011.12.06_09h39m21s_001_-300x191.png" alt="" title="Snap_2011.12.06_09h39m21s_001_" width="300" height="191" class="alignnone size-thumbnail wp-image-11790" /></a></p>
<p>So the question is how to get the calendar (from image above) to show on the frontend area? Easily, all you need is to include some CSS, few JavaScript&#8217;s, image and you are done. To make things more flexible, I wrote a little block class that does this for you.</p>
<pre class="brush: php; title: ; notranslate">
class Inchoo_Module_Block_Calendar extends Mage_Core_Block_Template
{
    protected function _construct()
    {
        parent::_construct();
        $this-&gt;setTemplate('inchoo/calendar.phtml');
    }

    protected function _prepareLayout()
    {
        $this-&gt;_injectCalendarControlJsCSSInHTMLPageHead();

        return parent::_prepareLayout();
    }    

    private function _injectCalendarControlJsCSSInHTMLPageHead()
    {
        $this-&gt;getLayout()-&gt;getBlock('head')-&gt;append(
            $this-&gt;getLayout()-&gt;createBlock(
                'Mage_Core_Block_Html_Calendar',
                'html_calendar',
                array('template' =&gt; 'page/js/calendar.phtml')
            )
        );

        $this-&gt;getLayout()-&gt;getBlock('head')
                -&gt;addItem('js_css', 'calendar/calendar-win2k-1.css')
                -&gt;addJs('calendar/calendar.js')
                -&gt;addJs('calendar/calendar-setup.js');

        return $this;
    }
}
</pre>
<p>Please note that I placed this block class under the Inchoo/Module namespace, where you can easily make fully new YourCompany/Calendar extension and just put this in it, if you wish your entire extension just for calendar control.</p>
<p>Hopefully, _construct() method is clear to everyone in this case?! If not, then please skip this article entirely and jumo to the <a href="http://www.magentocommerce.com/knowledge-base/entry/magento-for-dev-part-4-magento-layouts-blocks-and-templates">Magento Wiki</a> <img src='http://inchoo.net/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>The core of the functionality here lies in the _injectCalendarControlJsCSSInHTMLPageHead() method which is called by _prepareLayout() method. If you study it closely you will see that _injectCalendarControlJsCSSInHTMLPageHead() adds the necessary CSS and JS to the head portion of the HTML page that gets rendered. </p>
<p>With this approach, all we need to do later in our XML layout files is just call our Calendar block like:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;block type=&quot;inchoo/calendar&quot; name=&quot;inchoo.calendar&quot; /&gt;
</pre>
<p>And finally the content of your app/design/frontedn/SOMEPACKAGE/SOMETHEME/template/inchoo/calendar.phtml file should look like:</p>
<pre class="brush: jscript; title: ; notranslate">
&lt;span&gt;
    &lt;img style=&quot;&quot; title=&quot;Select Date&quot; id=&quot;date_select_trig&quot; class=&quot;v-middle&quot; alt=&quot;&quot; src=&quot;&lt;?php echo $this-&gt;getSkinUrl(&quot;images/calendar.gif&quot;);?&gt; &quot;/&gt;
    &lt;input type=&quot;text&quot; style=&quot;width: 120px;&quot; class=&quot;input-text&quot; value=&quot;&quot; id=&quot;selected_date&quot; name=&quot;selected_date&quot;/&gt;
    &lt;script type=&quot;text/javascript&quot;&gt;
    //&lt;![CDATA[
        Calendar.setup({
            inputField: &quot;selected_date&quot;,
            ifFormat: &quot;%m/%e/%Y %H:%M:%S&quot;,
            showsTime: true,
            button: &quot;date_select_trig&quot;,
            align: &quot;Bl&quot;,
            singleClick : true
        });
    //]]&gt;
    &lt;/script&gt;
&lt;/span&gt;
</pre>
<p>If all of the three files are in place then you should be able to see something like shown on image below.</p>
<p><a href="http://inchoo.net/wp-content/uploads/2011/12/Snap_2011.12.06_09h58m20s_002_.png"><img src="http://inchoo.net/wp-content/uploads/2011/12/Snap_2011.12.06_09h58m20s_002_-300x207.png" alt="" title="Snap_2011.12.06_09h58m20s_002_" width="300" height="207" class="alignnone size-thumbnail wp-image-11788" /></a></p>
<p>Additionally, please keep in mind that this is really basic approach. You could do a bit more abstraction to it so that XML layout block accepts few more parameters that would be passed to a Calendar.php block class, like show a bit different calendar, do a bit different format pickup, etc. so that you can truly reuse the same control in various ways.</p>
<p><em>P.S. If Magento is having trouble loading calendar/calendar.js, calendar/calendar-setup.js, calendar/calendar-win2k-1.css then try doing a seach on entire installation and moving them to your theme then modify the addItem, addJs in your Calendar.php block class.</em></p>
<p>Hope this was helpful.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/reusing-magento-calendar-control/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Creating an EAV based model(s) in Magento</title>
		<link>http://inchoo.net/ecommerce/magento/creating-an-eav-based-models-in-magento/</link>
		<comments>http://inchoo.net/ecommerce/magento/creating-an-eav-based-models-in-magento/#comments</comments>
		<pubDate>Sun, 04 Dec 2011 12:58:14 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Extensions]]></category>
		<category><![CDATA[Magento]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[eav]]></category>
		<category><![CDATA[extension]]></category>
		<category><![CDATA[model]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=11755</guid>
		<description><![CDATA[Magento database heavily utilizes an Entity-Attribute-Value (EAV) data model. However, the cost of flexibility is often complexity. The process of manipulating EAV data in Magento is often more difficult than &#8230;]]></description>
			<content:encoded><![CDATA[<p>Magento database heavily utilizes an Entity-Attribute-Value (EAV) data model. However, the cost of flexibility is often complexity. The process of manipulating EAV data in Magento is often more difficult than manipulating flat relational tables.</p>
<p>All Magento Models inherit from the Mage_Core_Model_Abstract. Difference between simple Model or an EAV Model is its Model Resource. </p>
<p>To build a model with proper collection object in Magento you need 4 things<span id="more-11755"></span>:</p>
<ul>
<li>model class</li>
<li>resource class</li>
<li>collection class</li>
<li>install script (the one found under mymodule_setup folder of your extension)</li>
</ul>
<style>
.gist-highlight .line {
	height:auto!important;
	float:none!important;
	float:none!important;
	background:none!important;
}
</style>
<p>To extend this little further there is one more file you would most likely need when building an EAV based model, the Setup.php which extends from Mage_Eav_Model_Entity_Setup. You would need to implement the getDefaultEntities() method in it as this is what&#8217;s called during the extension installation in Magento.</p>
<p>Imagine your module is called Phonebook and put into the /Inchoo namespace under the local code pool. Logically you would need a model class like User which represents single entry into the phonebook. With this in mind you already need a structure like:</p>
<ul>
<li>app/etc/modules/Inchoo_Phonebook.xml</li>
<li>app/code/local/Inchoo/Phonebook/etc/config.xml</li>
<li>app/code/local/Inchoo/Phonebook/Model/User.php</li>
<li>app/code/local/Inchoo/Phonebook/Model/Resource/User.php</li>
<li>app/code/local/Inchoo/Phonebook/Model/Resource/User/Collection.php</li>
<li>app/code/local/Inchoo/Phonebook/Model/Resource/Setup.php</li>
<li>app/code/local/Inchoo/Phonebook/sql/inchoo_phonebook_setup/install-1.0.0.0.php</li>
</ul>
<p>Now that we outlined the file structure, let&#8217;s get down to it and see what the content of files should look like in order for it to work.</p>
<p>app/etc/modules/Inchoo_Phonebook.xml<br />
<script src="https://gist.github.com/1430081.js?file=gistfile1.xml"></script></p>
<p>app/code/local/Inchoo/Phonebook/etc/config.xml<br />
<script src="https://gist.github.com/1430085.js?file=gistfile1.xml"></script></p>
<p>app/code/local/Inchoo/Phonebook/Model/User.php<br />
<script src="https://gist.github.com/1430086.js?file=gistfile1.aw"></script></p>
<p>app/code/local/Inchoo/Phonebook/Model/Resource/User.php<br />
<script src="https://gist.github.com/1430090.js?file=gistfile1.aw"></script></p>
<p>app/code/local/Inchoo/Phonebook/Model/Resource/User/Collection.php<br />
<script src="https://gist.github.com/1430091.js?file=gistfile1.aw"></script></p>
<p>app/code/local/Inchoo/Phonebook/Model/Resource/Setup.php<br />
<script src="https://gist.github.com/1430096.js?file=gistfile1.aw"></script></p>
<p>app/code/local/Inchoo/Phonebook/sql/inchoo_phonebook_setup/install-1.0.0.0.php<br />
<script src="https://gist.github.com/1430098.js?file=gistfile1.aw"></script></p>
<p>For each attribute type like &#8220;varchar&#8221;, &#8220;text&#8221;, &#8220;int&#8221;&#8230; you need to create a corresponding table like inchoo_phonebook_user_entity_varchar, inchoo_phonebook_user_entity_text, inchoo_phonebook_user_entity_int. Study the install-1.0.0.0.php and the config.xml file to see how to get that one done.</p>

<a href='http://inchoo.net/ecommerce/magento/creating-an-eav-based-models-in-magento/attachment/snap_2011-12-04_13h06m02s_001_/' title='Snap_2011.12.04_13h06m02s_001_'><img width="300" height="165" src="http://inchoo.net/wp-content/uploads/2011/12/Snap_2011.12.04_13h06m02s_001_-300x165.png" class="attachment-thumbnail" alt="Snap_2011.12.04_13h06m02s_001_" title="Snap_2011.12.04_13h06m02s_001_" /></a>
<a href='http://inchoo.net/ecommerce/magento/creating-an-eav-based-models-in-magento/attachment/snap_2011-12-04_13h07m00s_003_core-resource-blog-localhost-table/' title='Snap_2011.12.04_13h07m00s_003_core-resource -blog -localhost- - Table'><img width="300" height="235" src="http://inchoo.net/wp-content/uploads/2011/12/Snap_2011.12.04_13h07m00s_003_core-resource-blog-localhost-Table-300x235.png" class="attachment-thumbnail" alt="Snap_2011.12.04_13h07m00s_003_core-resource -blog -localhost- - Table" title="Snap_2011.12.04_13h07m00s_003_core-resource -blog -localhost- - Table" /></a>
<a href='http://inchoo.net/ecommerce/magento/creating-an-eav-based-models-in-magento/attachment/snap_2011-12-04_13h07m34s_004_-nibccapture-02f1dcaf-8f72-4627-b675-014a58d4f832/' title='Snap_2011.12.04_13h07m34s_004_-nibCCapture-02f1dcaf-8f72-4627-b675-014a58d4f832'><img width="300" height="176" src="http://inchoo.net/wp-content/uploads/2011/12/Snap_2011.12.04_13h07m34s_004_-nibCCapture-02f1dcaf-8f72-4627-b675-014a58d4f832-300x176.png" class="attachment-thumbnail" alt="Snap_2011.12.04_13h07m34s_004_-nibCCapture-02f1dcaf-8f72-4627-b675-014a58d4f832" title="Snap_2011.12.04_13h07m34s_004_-nibCCapture-02f1dcaf-8f72-4627-b675-014a58d4f832" /></a>
<a href='http://inchoo.net/ecommerce/magento/creating-an-eav-based-models-in-magento/attachment/snap_2011-12-04_13h08m10s_005_eav-entity-type-blog-localhost-table/' title='Snap_2011.12.04_13h08m10s_005_eav-entity-type -blog -localhost- - Table'><img width="300" height="157" src="http://inchoo.net/wp-content/uploads/2011/12/Snap_2011.12.04_13h08m10s_005_eav-entity-type-blog-localhost-Table-300x157.png" class="attachment-thumbnail" alt="Snap_2011.12.04_13h08m10s_005_eav-entity-type -blog -localhost- - Table" title="Snap_2011.12.04_13h08m10s_005_eav-entity-type -blog -localhost- - Table" /></a>
<a href='http://inchoo.net/ecommerce/magento/creating-an-eav-based-models-in-magento/attachment/snap_2011-12-04_13h11m03s_006_inchoo-phonebook-user-entity-blog-localhost-table/' title='Snap_2011.12.04_13h11m03s_006_inchoo-phonebook-user-entity -blog -localhost- - Table'><img width="300" height="199" src="http://inchoo.net/wp-content/uploads/2011/12/Snap_2011.12.04_13h11m03s_006_inchoo-phonebook-user-entity-blog-localhost-Table-300x199.png" class="attachment-thumbnail" alt="Snap_2011.12.04_13h11m03s_006_inchoo-phonebook-user-entity -blog -localhost- - Table" title="Snap_2011.12.04_13h11m03s_006_inchoo-phonebook-user-entity -blog -localhost- - Table" /></a>
<a href='http://inchoo.net/ecommerce/magento/creating-an-eav-based-models-in-magento/attachment/snap_2011-12-04_13h11m18s_007_inchoo-phonebook-user-entity-int-blog-localhost-table/' title='Snap_2011.12.04_13h11m18s_007_inchoo-phonebook-user-entity-int -blog -localhost- - Table'><img width="300" height="204" src="http://inchoo.net/wp-content/uploads/2011/12/Snap_2011.12.04_13h11m18s_007_inchoo-phonebook-user-entity-int-blog-localhost-Table-300x204.png" class="attachment-thumbnail" alt="Snap_2011.12.04_13h11m18s_007_inchoo-phonebook-user-entity-int -blog -localhost- - Table" title="Snap_2011.12.04_13h11m18s_007_inchoo-phonebook-user-entity-int -blog -localhost- - Table" /></a>
<a href='http://inchoo.net/ecommerce/magento/creating-an-eav-based-models-in-magento/attachment/snap_2011-12-04_13h11m32s_008_inchoo-phonebook-user-entity-text-blog-localhost-table/' title='Snap_2011.12.04_13h11m32s_008_inchoo-phonebook-user-entity-text -blog -localhost- - Table'><img width="300" height="215" src="http://inchoo.net/wp-content/uploads/2011/12/Snap_2011.12.04_13h11m32s_008_inchoo-phonebook-user-entity-text-blog-localhost-Table-300x215.png" class="attachment-thumbnail" alt="Snap_2011.12.04_13h11m32s_008_inchoo-phonebook-user-entity-text -blog -localhost- - Table" title="Snap_2011.12.04_13h11m32s_008_inchoo-phonebook-user-entity-text -blog -localhost- - Table" /></a>
<a href='http://inchoo.net/ecommerce/magento/creating-an-eav-based-models-in-magento/attachment/snap_2011-12-04_13h11m42s_009_inchoo-phonebook-user-entity-varchar-blog-localhost-table/' title='Snap_2011.12.04_13h11m42s_009_inchoo-phonebook-user-entity-varchar -blog -localhost- - Table'><img width="300" height="181" src="http://inchoo.net/wp-content/uploads/2011/12/Snap_2011.12.04_13h11m42s_009_inchoo-phonebook-user-entity-varchar-blog-localhost-Table-300x181.png" class="attachment-thumbnail" alt="Snap_2011.12.04_13h11m42s_009_inchoo-phonebook-user-entity-varchar -blog -localhost- - Table" title="Snap_2011.12.04_13h11m42s_009_inchoo-phonebook-user-entity-varchar -blog -localhost- - Table" /></a>
<a href='http://inchoo.net/ecommerce/magento/creating-an-eav-based-models-in-magento/attachment/model-4/' title='model'><img width="300" height="118" src="http://inchoo.net/wp-content/uploads/2011/12/model-e1323080904528-300x118.jpg" class="attachment-thumbnail" alt="model" title="model" /></a>
<a href='http://inchoo.net/ecommerce/magento/creating-an-eav-based-models-in-magento/attachment/featured-image-598/' title='Featured Image'><img width="300" height="118" src="http://inchoo.net/wp-content/uploads/2011/12/model-e1323080904528-300x118.jpg" class="attachment-thumbnail" alt="Featured Image" title="Featured Image" /></a>

<p>Finally, you can test your model and it’s collection by running the following somewhere in your code:<br />
<script src="https://gist.github.com/1430101.js?file=gistfile1.aw"></script></p>
<p>And that&#8217;s it. Although simple, there is some typing to be done in order to get the EAV based model functional. Hope it helps someone. </p>
<p>Cheers.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/creating-an-eav-based-models-in-magento/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>MagentoU “Fundamentals of Magento Development” impressions</title>
		<link>http://inchoo.net/ecommerce/magento/magentou-fundamentals-of-magento-development-impressions/</link>
		<comments>http://inchoo.net/ecommerce/magento/magentou-fundamentals-of-magento-development-impressions/#comments</comments>
		<pubDate>Wed, 02 Nov 2011 09:43:54 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Magento]]></category>
		<category><![CDATA[Starting up]]></category>
		<category><![CDATA[certification]]></category>
		<category><![CDATA[course]]></category>
		<category><![CDATA[education]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=11198</guid>
		<description><![CDATA[Few days ago I was attending the MagentoU’s “Fundamentals of Magento Development” course in London. It’s a five day course covering Magento internals inside out (at least that’s the idea &#8230;]]></description>
			<content:encoded><![CDATA[<p>Few days ago I was attending the MagentoU’s “Fundamentals of Magento Development” course in London. It’s a five day course covering Magento internals inside out (at least that’s the idea <img src='http://inchoo.net/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> ). Price of the course at the moment is around $3,850.00, which is not a small amount to pay for a five day education. Good thing is that we needed to pay only 50% of the cost as Magento Silver Partner. So is it worth the money?<span id="more-11198"></span></p>
<p>Course itself is targeted towards both developers who are new to Magento, as well as those developers who are experienced with Magento platform. It takes you trough the the process of Magento initialization, Rendering, Request Flow, Adminhtml, Database and EAV. The great part about it is the lecture-lab format, meaning you actually do the coding with the trainer.</p>
<p><a href="http://www.magentocommerce.com/services/certification-board" title="Magento Developer Certification Advisory Board">Ben Marks</a> from Blue Acorn was the trainer conducting the course. I can only say words of praise to his approach to lecturing. He was on top all of the tricky questions we threw at him. I knew Ben from before the course, as he was doing Magento for over three years now. His experience was an extra bonus on top of the training materials we got. Additionally I enjoyed his cheerful mood throughout entire course.</p>
<p>Company sent me to the course as we where focused on getting the first hand “big picture” about Magento developer education process. Meaning how it was organized, what it covers, and in general how much developers can learn in just a five day course. All in all, I would say it was a positive experience. Regardless of over 3 years of everyday Magento development its really refreshing to catch on a few tricks and share experience. I believe class was successful at completely clarifying  the fundamentals of the xml structure, control flow, and navigating through and making sense of the many files of Magento. Trainer, Ben, was really in depth with things, showing great understanding of a specific application areas.</p>
<p><a href="http://inchoo.net/wp-content/uploads/2011/11/IMAG0106.jpg"><img src="http://inchoo.net/wp-content/uploads/2011/11/IMAG0106-300x179.jpg" alt="" title="IMAG0106" width="300" height="179" class="alignnone size-thumbnail wp-image-11212" /></a> <a href="http://inchoo.net/wp-content/uploads/2011/11/IMAG0105.jpg"><img src="http://inchoo.net/wp-content/uploads/2011/11/IMAG0105-300x179.jpg" alt="" title="IMAG0105" width="300" height="179" class="alignnone size-thumbnail wp-image-11211" /></a></p>
<p><a href="http://inchoo.net/wp-content/uploads/2011/11/IMAG0104.jpg"><img src="http://inchoo.net/wp-content/uploads/2011/11/IMAG0104-300x179.jpg" alt="" title="IMAG0104" width="300" height="179" class="alignnone size-thumbnail wp-image-11210" /></a> <a href="http://inchoo.net/wp-content/uploads/2011/11/IMAG0103.jpg"><img src="http://inchoo.net/wp-content/uploads/2011/11/IMAG0103-300x179.jpg" alt="" title="IMAG0103" width="300" height="179" class="alignnone size-thumbnail wp-image-11209" /></a></p>
<p><a href="http://inchoo.net/wp-content/uploads/2011/11/IMAG0101.jpg"><img src="http://inchoo.net/wp-content/uploads/2011/11/IMAG0101-300x179.jpg" alt="" title="IMAG0101" width="300" height="179" class="alignnone size-thumbnail wp-image-11208" /></a> <a href="http://inchoo.net/wp-content/uploads/2011/11/IMAG0097.jpg"><img src="http://inchoo.net/wp-content/uploads/2011/11/IMAG0097-300x179.jpg" alt="" title="IMAG0097" width="300" height="179" class="alignnone size-thumbnail wp-image-11207" /></a></p>
<p>Although I must admit I am looking forward to the possible next level course, something like “Advanced Magento Developement” which might be more suited for us doing Magento development on a day to day basis. Course whose focus wound not be on the functional and structural but on “best practices, specific features (checkout, order handling, payment gateways, shipping gateways)” side of things.</p>
<p>As mentioned, the course is intended for new as well as those experienced to Magento development. I remind you, the thing about Magento is that it’s really huge, meaning there is so many things you need to know, which is why I would say this course hits the target when it comes to the “fundamentals”. I find it ideal for people who are experienced in PHP but are mostly just starting with Magento or have very little Magento experience, or are use to doing Magento development in a non Magento way of doing things. For those, I see a great value in it, as it will most certainly set you on the right track.</p>
<p>To conclude, if you recognized your self in what I wrote above and you can afford it, I suggest you take it.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/magentou-fundamentals-of-magento-development-impressions/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>CheckItOut, alternative checkout for Magento</title>
		<link>http://inchoo.net/ecommerce/magento/magento-checkout/checkitout-alternative-checkout-for-magento/</link>
		<comments>http://inchoo.net/ecommerce/magento/magento-checkout/checkitout-alternative-checkout-for-magento/#comments</comments>
		<pubDate>Sat, 22 Oct 2011 07:32:54 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Checkout]]></category>
		<category><![CDATA[checkout]]></category>
		<category><![CDATA[extension]]></category>
		<category><![CDATA[Magento]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=11080</guid>
		<description><![CDATA[If for some reason you are not a big fan of Magento’s default One Page Checkout concept / workflow, it might be worth checking out alternative. Usually these alternatives come &#8230;]]></description>
			<content:encoded><![CDATA[<p>If for some reason you are not a big fan of Magento’s default One Page Checkout concept / workflow, it might be worth checking out alternative. Usually these alternatives come in a form of so called &#8220;one step checkout&#8221; where main difference is that entire customer data / payment data / shipment data is displayed not just on one page, but in one &#8220;piece&#8221;. Why is this important? Well, it usually leads to a bit better user experience. Even a fraction better user experience can make a difference between new order and abandoned cart. No one likes abandoned carts <img src='http://inchoo.net/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> .<span id="more-11080"></span></p>
<p>Let me present you with one possible &#8220;one step checkout&#8221; like alternative, the <a href="http://www.ecomdev.org/shop/checkitout.html">CheckItOut!</a> Magento extension by <a href="http://www.ecomdev.org/shop/">EcomDev</a>.</p>
<p>There are several key things that make this extension stand up. Before everything, extension is simple. Simple in terms that it modifies standard Magento checkout in the places where it is really needed. Usually this is not something people who buy extension pay attention. But I cannot stress enough how important it is to use stable extension that do not poke around core Magento functionality a lot. The simpler, the better, as long as it do it’s job. Quick overview of the code show most fo the focus on custom checkout controller EcomDev_CheckItOut_OnepageController which in turn extends the default checkout controller Mage_Checkout_OnepageController. This is &#8220;turned on&#8221; by a router definition within app\code\community\EcomDev\CheckItOut\etc\config.xml file. Meaning CheckItOut inherits all of the default controller behaviour then simply implements it’s stuff in the proper controller method. This approach is relatively solid as it is straight forward and you know that at any point you simply disable module and your default checkout kicks in.</p>
<p>Code itself looks &#8220;rock solid&#8221; and it looks like they achieved a lot of functionality without a single class rewrite (except controller). Layout seems to be easy modifiable due to the nice CSS files that go along with it.</p>
<p>On top of that there is a quality developer manual that goes with it. We all love good documentation when it comes to Magento.</p>
<p>This is all nice and all, but what about real frontend features? </p>
<p>Given that the extension is based on the standard Magento checkout, all payment methods and additional Magento customizations are supported. Meaning, it should be fully compatible with features like: Gift cards, Customer balance, Reward points, etc.</p>
<p>Additionally extension functionality can be disabled for all websites or a particular website. Plus, you can return standard Magento checkout at any time by changing configuration field in the admin interface.</p>
<p>Seems like a nice product, especially since it costs only €249.00. I say only, because you should think twice before you actually pay anyone to code custom checkout just for your Magento store.</p>
<p>All in all, definitely worth checking out if you are in need for &#8220;one step checkout&#8221; like process.</p>
<p>Cheers.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/magento-checkout/checkitout-alternative-checkout-for-magento/feed/</wfw:commentRss>
		<slash:comments>24</slash:comments>
		</item>
		<item>
		<title>Configuring Magento for development</title>
		<link>http://inchoo.net/ecommerce/magento/configuring-magento-for-development/</link>
		<comments>http://inchoo.net/ecommerce/magento/configuring-magento-for-development/#comments</comments>
		<pubDate>Thu, 20 Oct 2011 06:41:22 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Magento]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Starting up]]></category>
		<category><![CDATA[configuration]]></category>
		<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=11033</guid>
		<description><![CDATA[New to the Magento or just used to doing things one way? Here are few tips for configuring Magento for development, in case you overlooked them. Please note, the more &#8230;]]></description>
			<content:encoded><![CDATA[<p>New to the Magento or just used to doing things one way? Here are few tips for configuring Magento for development, in case you overlooked them. Please note, the more proper title of this article would be something like: Configuring Magento for development on local machine (after installation config). Meaning the tips outlined here only apply after the Magento is already installed.<span id="more-11033"></span></p>
<p>Here are the steps that you should do in order to set your Magento more suited for development:</p>
<ul>
<li>System > Cache Management > Disable All</li>
<li>System > Configuration > Advanced > Developer > Log Settings > Enabled => Yes</li>
<li>System > Configuration > Web > Search Engine Optimization > Use Wbe Server Rewrites => Yes</li>
<li>System > Index Management > Reindex All</li>
<li>Open .htaccess and set: SetEnv MAGE_IS_DEVELOPER_MODE &#8220;true&#8221; at the end of the file</li>
<li>Open .htaccess and set: php_value display_errors On somewhere within &#60;IfModule mod_php5.c&#62;</li>
<li>Rename /errors/local.xml.sample to /errors/local.xml</li>
<li>Create one sample customer with full valid American address (for example use US/California, city Alamo with ZIP 94507), and one with full valid non American address (other country) to test payment and shipping gateways properly</li>
<li>Compensate for the possible lack of email server by doing something like explained in <a href="http://inchoo.net/ecommerce/magento/magento-l-e-s-s/">this article</a></li>
</ul>
<p>Hope it helps.</p>
<p>Cheers.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/configuring-magento-for-development/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Magento Trusted Extensions</title>
		<link>http://inchoo.net/ecommerce/magento/trusted-extensions/</link>
		<comments>http://inchoo.net/ecommerce/magento/trusted-extensions/#comments</comments>
		<pubDate>Fri, 14 Oct 2011 16:21:18 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Extensions]]></category>
		<category><![CDATA[Magento]]></category>
		<category><![CDATA[extension]]></category>
		<category><![CDATA[trusted]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=10968</guid>
		<description><![CDATA[About ten months ago I wrote an article titled Magento Extensions Quality Assurance Certification. If you read it, then you know it was a pure result of my imagination. In &#8230;]]></description>
			<content:encoded><![CDATA[<p>About ten months ago I wrote an article titled <a href="http://inchoo.net/ecommerce/magento/magento-extensions-quality-assurance-certification/">Magento Extensions Quality Assurance Certification</a>. If you read it, then you know it was a pure result of my imagination. In that article I spoke about (then) current state of various extensions, their conflicts, etc. Finally concluding how nice it would be to have some sort of extension certification program. Certification program that would be in charge of checking several important parts of your extension inner working, etc. All with the final goal of classifying your extension as safe.<span id="more-10968"></span></p>
<p>As I said, this was around ten months ago. During that time folks at Magento wasted no time. Just recently they announced several interesting new things, one of which is <a href="https://www.magentocommerce.com/magento-connect/trusted_extension/">&#8220;<strong>Trusted Extension</strong>&#8221; program/process</a>. For the time being this only applies to the Magento GO platform, but as they announced it, it should come in play for Community Edition of Magento as well.</p>
<p>For all involved in Magento development it’s clear that such process of marking extension as &#8220;trusted&#8221; is all but simply. Extension reviewers really need to be up to the task when it comes to knowing Magento inside out. Magento folks included an extra step in this process, the legal one. Meaning, besides variety of technical tests your extension will need to pass, you as a developer will need to commit both from technical/support and legal side.</p>
<p>Technical tests are to include things like Code review, Security Checks, Magento database usage, interface and consistency, Remote calls to 3rd party systems, Memory usage, Performance impact and load impact, Overall behavior in multi-tenant SaaS environment.</p>
<p>From the legal stand, developers who are accepted to the process will sign on dedicated business and developer agreement with Magento.</p>
<p>At the end of the day Magento does not guarantee functional accuracy of specific extension behavior, which is pretty fare enough given that I do not see any mentions of this being a paid process.</p>
<p>All in all, I’m happy to see something happening in regards to &#8220;marking&#8221; the quality extensions. Time will show how much of actual benefit this will bring.</p>
<p>Combine this with all of the latest official Magento education materials/courses popping out and I’m sure where are in for a bright, more quality eCommerce business with this great platform.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/trusted-extensions/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Magento certification, why bother?</title>
		<link>http://inchoo.net/ecommerce/magento/magento-certification-why-bother/</link>
		<comments>http://inchoo.net/ecommerce/magento/magento-certification-why-bother/#comments</comments>
		<pubDate>Mon, 10 Oct 2011 13:40:29 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Magento]]></category>
		<category><![CDATA[Starting up]]></category>
		<category><![CDATA[certification]]></category>
		<category><![CDATA[exam]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=10879</guid>
		<description><![CDATA[Few days ago Magento announced their plans for Magento certification program. As they stated, initial round of certification will be a Beta exam, held on Innovate Developer Conference in San &#8230;]]></description>
			<content:encoded><![CDATA[<p>Few days ago Magento announced their <a href="http://www.magentocommerce.com/blog/comments/innovate-magento-beta-certification-exam/">plans for Magento certification program</a>. As they stated, initial round of certification will be a Beta exam, held on Innovate Developer Conference in San Francisco, October 12 and 13! Those who pass the exam based on the Beta exam analysis, will actually earn the official Magento Developer certification. Final reward for those who pass the exam? <span id="more-10879"></span>They will receive Magento certificate and be able to use the official logo with their name and title. Clearly there is more to it than just a piece of paper, so let me share my thoughts and views with you.</p>
<p>The thing about the certification is that it’s not only about the piece of paper or a card issued by some authority verifying your knowledge and expertise. If you look at it more deeply, additional value lies in the individuals desire to actually go trough it. This shows determination and love towards an area of interest. It’s hard to imagine someone would go trough all the hassle of certification as specific as this one if he does not love his work. This goes for certification in general, not just Magento. This is something hiring managers will know how to recognise and value.</p>
<p>From a personal point of view, as a developer you gain recognition of your Magento skills. Which is not to be ignored, as Magento still is one of the most robust eCommerce platforms our there. This can help differentiate yourself from the competition, possibly increase your value on the market. Not to mention there is a good chance you gain some additional knowledge through preparation and training. Face it as much as you know Magento chances are you skipped a “chapter or two” in your everyday real job (I know I did).</p>
<p>On a company level, there is also a bonus of having some or all of your developers certified. This usually goes in favour of your overall quality.</p>
<p>I’m sure most of you in Magento business will agree that Magento certification was more than welcome and I would even say needed. With all their great products and services now available time is right for quality certification program. I’m only hoping that over the next few months, year or two, most of us in everyday Magento development will earn ourselves a certificate <img src='http://inchoo.net/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>So what’s it all about? <a href="http://www.magentocommerce.com/blog/comments/innovate-magento-beta-certification-exam/">Magento Developer Certification landing page</a> has a nice little PDF file called <a href="http://info.magento.com/rs/magentocommerce/images/Magento%20Certified%20Developer%20Exam%20Self-Assessment%20Checklist%20%28Beta%29.pdf">Certified Developer Exam: Self Assessment Checklist</a>. It outlines the certification program which comprises of two levels:</p>
<ul>
<li><strong>Magento Certified Developer (MCD)</strong></li>
<li><strong>Magento Certified Developer Plus (MCD Plus)</strong>, has an additional 20 questions</li>
</ul>
<p>Questions are based on Magento Community Edition Magento Enterprise Edition. Surprisingly there is a detailed outline of 10 specific areas that exam covers, all you need to do is to study it.</p>
<ol>
<li>Basics (5%)</li>
<li>Request Flow (7%)</li>
<li>Rendering (8%)</li>
<li>Working with Database in Magento (12%)</li>
<li>Entity-Attribute-Value (EAV) Model (10%)</li>
<li>Adminhtml (8%)</li>
<li>Catalog (10%)</li>
<li>Checkout (15%)</li>
<li>Sales and Customers (12%)</li>
<li>Advanced features (13%)</li>
</ol>
<p><a href="http://info.magento.com/rs/magentocommerce/images/Magento%20Certified%20Developer%20Exam%20Self-Assessment%20Checklist%20%28Beta%29.pdf">Check the document</a> for details. </p>
<p>In a nutshell that’s it for now. Grab your Magento installations, some coffee, and run trough outlined exam areas if you are interested in taking exam. I must say, it does not look like it’s going to be easy. Regardless of your experience, Magento is still a large platform, meaning lot of terrain to cover so get prepared:)</p>
<p>Cheers.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/magento-certification-why-bother/feed/</wfw:commentRss>
		<slash:comments>17</slash:comments>
		</item>
		<item>
		<title>Creating your own web shop &#8211; Part 1: Where to start</title>
		<link>http://inchoo.net/ecommerce/creating-your-own-web-shop-part-1-where-to-start/</link>
		<comments>http://inchoo.net/ecommerce/creating-your-own-web-shop-part-1-where-to-start/#comments</comments>
		<pubDate>Sun, 07 Aug 2011 09:56:48 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[E-Commerce]]></category>
		<category><![CDATA[custom]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[Magento]]></category>
		<category><![CDATA[web shop]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=10576</guid>
		<description><![CDATA[Creating your own web shop can be a tedious task, both technically and time consuming. Not to mention there are tens of quality PHP based web shop systems out there &#8230;]]></description>
			<content:encoded><![CDATA[<p>Creating your own web shop can be a tedious task, both technically and time consuming. Not to mention there are tens of quality PHP based web shop systems out there available for free. One of the best if you ask me is Magento, which exceeds the term web shop system by even bigger term &#8220;platform&#8221;. However, regardless of how some much a given system/platform is feature rich there are always those special clients with special requests which exceed the possibilities of even the mightiest eCommerce systems/platforms out there.<span id="more-10576"></span></p>
<p>In my experience, usually these type of clients are either big enterprise level clients or &#8220;small first time running a web shop&#8221; client. Big enterprise level clients usually know very well what they want as they outgrown the features even the most feature rich platforms can give them out of the box.</p>
<p>Before I go any further, I would just like to clarify one thing. This article is by no means advice or suggestion you should consider building your own web shop solution over choosing Magento CE, PE, or EE or possibly some other professional/enterprise web shop platform. In my opinion doing so would only be justified in situations where desired feature list massively exceeds the features offered by any of Magento versions, or if your budget and delivery date are of no or little importance.</p>
<p>Seems like building your own web shop system these days comes down to (1) satisfying your personal needs as a developer (meaning you got some time on your hand and you wish to play with building something out of the scratch just so see if you can do it), (2) You really have a special feature list you need to implement in your own web shop while at the same time you do not need all or any of the features offered by powerful web shops that are already there. Everything else seems just like reinventing a wheel, because all it takes is to develop extensions/modules for existing systems/platforms and save yourself both money and time.</p>
<p>OK, enough for the introduction. Let&#8221;s get down to it. Where to start when creating your own shop? I would say there are five major topic that need to be outlined: </p>
<ul>
<li>Feature list (<i>in a broader term this should come with proper software requirements, as noted by <a href="http://inchoo.net/ecommerce/creating-your-own-web-shop-part-1-where-to-start/#comment-22233">beeplogic</a> below in comments</i>),</li>
<li>Development platform selection,</li>
<li>Overall budget</li>
<li>Available resources</li>
<li>Expected date of delivery</li>
</ul>
<p><strong>Feature list</strong> should cover a full list of requested features, like: Customer Single Sign On (Facebook, Twitter), One Step Checkout, Implementation of XYZ payment gateway, Product Review/Comments System, Multi-language, Multi-currency, Email Notifications System, Product types (standard, downloadable, bundle), etc.</p>
<p><strong>Development platform selection</strong> should evaluate possibly several different platforms that might be interested for our project. For example, usage of Zend Framework over Symfony over CodeIgniter over cakePHP etc., if we are talking about PHP platforms.</p>
<p><strong>Overall budget</strong> is pretty clear. Probably the most important thing here to note is that you should never plan your project on &#8220;how to spent entire budget&#8221; but more like &#8220;how to deliver all or most of the features by expected delivery date not exceeding the overall budget&#8221; concept. Sometimes clients need reality check, and they need to be forced to either prioritize or dismiss certain features if the budget is low.</p>
<p><strong>Available resources</strong> are all those people (developers, designers, project manager) in your team which you know will be working on a given project.</p>
<p><strong>Expected date of delivery</strong> is extremely important in the big picture as it represents the link between feature list, overall budget and available resources. For example, imagine someone gives you a budget of $500K, in order to deliver a system with 50 outlined features with a team of 7 experienced people working on it within 3 months. Let&#8221;s assume you take $120/hour for development services. It comes down to $120/hour * 8h/day * 5days/week * 4weeks/month = $ 19.200,00 per month per developer. Which is around $134.400,00 per month for entire 7 people team. In 3 months time this turns out to be $403.200,00 meaning you are not able to spent all of the budget available. Biggest mistake for a project and people on it you can do here is to throw in a few newbies just to fill in the gap for reaching the budget limit. This disrupts the workflow of the rest of the team for two reasons: (a) newbie developers need significant time to get to the level of senior developers thus not being nearly productive as other team members, (b) each team expansion requires extra management input or even change in management philosophy.</p>
<p>What I outlined above usually makes sense in business environment where entire team is working on a new system/platform. Things are significantly different if you alone are trying to create something in your own time (usually as a hobby). In that case we are usually focused on first two topics: (1) Feature list and (2) Development platform selection. Meaning we have no budged to look at, no expected delivery date to worry about.</p>
<p>In my next articles under this topic I will focus strictly on development platform selection and the actual development of web shop from ground up. Until then, I&#8221;ll give you a hint&#8230; Symfony + Zend Framework + Ext JS <img src='http://inchoo.net/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Cheers.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/creating-your-own-web-shop-part-1-where-to-start/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Magento&#8217;s &#8220;Quote/Order/Invoice&#8221; workflow</title>
		<link>http://inchoo.net/ecommerce/magento/magento-quote-order-invoice-workflow/</link>
		<comments>http://inchoo.net/ecommerce/magento/magento-quote-order-invoice-workflow/#comments</comments>
		<pubDate>Wed, 27 Jul 2011 08:06:06 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Magento]]></category>
		<category><![CDATA[Orders]]></category>
		<category><![CDATA[Starting up]]></category>
		<category><![CDATA[invoice]]></category>
		<category><![CDATA[order]]></category>
		<category><![CDATA[quote]]></category>
		<category><![CDATA[workflow]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=10516</guid>
		<description><![CDATA[The purpose of this article is to shed some light onto the flow of things that happen behind the scene in the process of creating and later invoicing order in &#8230;]]></description>
			<content:encoded><![CDATA[<p>The purpose of this article is to shed some light onto the flow of things that happen behind the scene in the process of creating and later invoicing order in Magento. In order to properly code new functionality surrounding order creation process, one must have a solid understanding of that process otherwise you may impose some serious issues on to the system via custom coding around orders.<span id="more-10516"></span></p>
<p>If you would like to follow up, please set up your own test Magento store. In my example I am using Magento Community Edition, 1.5.</p>
<p>So where do we start. I suggest we start from empty sales tables, meaning we run the following query on database:</p>
<pre class="brush: sql; title: ; notranslate">
TRUNCATE `sales_bestsellers_aggregated_daily`;
TRUNCATE `sales_bestsellers_aggregated_monthly`;
TRUNCATE `sales_bestsellers_aggregated_yearly`;
TRUNCATE `sales_billing_agreement`;
TRUNCATE `sales_billing_agreement_order`;
TRUNCATE `sales_flat_creditmemo`;
TRUNCATE `sales_flat_creditmemo_comment`;
TRUNCATE `sales_flat_creditmemo_grid`;
TRUNCATE `sales_flat_creditmemo_item`;
TRUNCATE `sales_flat_invoice`;
TRUNCATE `sales_flat_invoice_comment`;
TRUNCATE `sales_flat_invoice_grid`;
TRUNCATE `sales_flat_invoice_item`;
TRUNCATE `sales_flat_order`;
TRUNCATE `sales_flat_order_address`;
TRUNCATE `sales_flat_order_grid`;
TRUNCATE `sales_flat_order_item`;
TRUNCATE `sales_flat_order_payment`;
TRUNCATE `sales_flat_order_status_history`;
TRUNCATE `sales_flat_quote`;
TRUNCATE `sales_flat_quote_address`;
TRUNCATE `sales_flat_quote_address_item`;
TRUNCATE `sales_flat_quote_item`;
TRUNCATE `sales_flat_quote_item_option`;
TRUNCATE `sales_flat_quote_payment`;
TRUNCATE `sales_flat_quote_shipping_rate`;
TRUNCATE `sales_flat_shipment`;
TRUNCATE `sales_flat_shipment_comment`;
TRUNCATE `sales_flat_shipment_grid`;
TRUNCATE `sales_flat_shipment_item`;
TRUNCATE `sales_flat_shipment_track`;
TRUNCATE `sales_invoiced_aggregated`;
TRUNCATE `sales_invoiced_aggregated_order`;
TRUNCATE `sales_order_aggregated_created`;
TRUNCATE `sales_order_tax`;
TRUNCATE `sales_payment_transaction`;
TRUNCATE `sales_recurring_profile`;
TRUNCATE `sales_recurring_profile_order`;
TRUNCATE `sales_refunded_aggregated`;
TRUNCATE `sales_refunded_aggregated_order`;
TRUNCATE `sales_shipping_aggregated`;
TRUNCATE `sales_shipping_aggregated_order`;
</pre>
<p>Now we will go to the frontend or our store and add 3 different items to our cart. You can check out the screenshot of the added items.</p>
<p>This simple action of adding 3 items made following entries into the tables:</p>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_quote
-- ----------------------------
INSERT INTO `sales_flat_quote` VALUES ('1', '1', '2011-07-26 11:20:34', '2011-07-26 11:21:23', null, '1', '0', '0', '3', '10.0000', '0', '1.0000', '1.0000', '1.0000', '1.0000', 'USD', 'USD', 'USD', 'USD', '6196.9900', '6196.9900', null, null, '3', '0', null, null, null, null, null, null, null, null, '1', '0', null, '127.0.0.1', null, null, null, null, '6196.9900', '6196.9900', '6196.9900', '6196.9900', null, '1', '0', null, null, '0');
</pre>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_quote_address
-- ----------------------------
INSERT INTO `sales_flat_quote_address` VALUES ('1', '1', '2011-07-26 11:20:34', '2011-07-26 11:21:23', null, '0', null, 'billing', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, '0', '0', '0', null, null, '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', null, 'a:0:{}', null, null, null, '0.0000', null, null, null, null, null, null, '0.0000', '0.0000');
INSERT INTO `sales_flat_quote_address` VALUES ('2', '1', '2011-07-26 11:20:34', '2011-07-26 11:21:23', null, '0', null, 'shipping', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, '1', '0', '0', null, null, '29.0000', '6196.9900', '6196.9900', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '6196.9900', '6196.9900', null, 'a:0:{}', null, '0.0000', '0.0000', '6196.9900', null, null, '0.0000', '0.0000', '0.0000', null, '0.0000', '0.0000');
</pre>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_quote_item
-- ----------------------------
INSERT INTO `sales_flat_quote_item` VALUES ('1', '1', '2011-07-26 11:20:34', '2011-07-26 11:20:34', '17', '1', null, '0', 'bb8100', 'BlackBerry 8100 Pearl', null, null, null, '0', '0', '0', '15.2000', '1.0000', '349.9900', '349.9900', null, '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '349.9900', '349.9900', '0.0000', '15.2000', 'simple', null, null, null, null, 'a:0:{}', '0.0000', '0.0000', '0.0000', null, '0.0000', '0.0000', '0.0000', '0.0000', null, '29.9900', '349.9900', '349.9900', '349.9900', '349.9900', null, null);
INSERT INTO `sales_flat_quote_item` VALUES ('2', '1', '2011-07-26 11:20:49', '2011-07-26 11:21:23', '166', '1', null, '0', 'HTC Touch Diamond', 'HTC Touch Diamond', null, null, null, '0', '0', '0', '0.3000', '6.0000', '750.0000', '750.0000', null, '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '4500.0000', '4500.0000', '0.0000', '1.8000', 'simple', null, null, null, null, 'a:0:{}', '0.0000', '0.0000', '0.0000', null, '0.0000', '0.0000', '0.0000', '0.0000', null, null, '750.0000', '750.0000', '4500.0000', '4500.0000', null, null);
INSERT INTO `sales_flat_quote_item` VALUES ('3', '1', '2011-07-26 11:21:12', '2011-07-26 11:21:12', '44', '1', null, '0', 'Rebel XT', 'Canon Digital Rebel XT 8MP Digital SLR Camera', null, null, null, '0', '0', '0', '4.0000', '3.0000', '449.0000', '449.0000', null, '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '1347.0000', '1347.0000', '0.0000', '12.0000', 'simple', null, null, null, null, 'a:0:{}', '0.0000', '0.0000', '0.0000', null, '0.0000', '0.0000', '0.0000', '0.0000', null, '200.0000', '449.0000', '449.0000', '1347.0000', '1347.0000', null, null);
</pre>
<pre class="brush: plain; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_quote_item_option
-- ----------------------------
INSERT INTO `sales_flat_quote_item_option` VALUES ('1', '1', '17', 'info_buyRequest', 'a:4:{s:4:\&quot;uenc\&quot;;s:124:\&quot;aHR0cDovL21hZ2VudG8tMS42LjAuMC1yYzIubG9jYWwvaW5kZXgucGhwL2VsZWN0cm9uaWNzL2NlbGwtcGhvbmVzL2JsYWNrYmVycnktODEwMC1wZWFybC5odG1s\&quot;;s:7:\&quot;product\&quot;;s:2:\&quot;17\&quot;;s:15:\&quot;related_product\&quot;;s:0:\&quot;\&quot;;s:3:\&quot;qty\&quot;;s:1:\&quot;1\&quot;;}');
INSERT INTO `sales_flat_quote_item_option` VALUES ('2', '2', '166', 'info_buyRequest', 'a:4:{s:4:\&quot;uenc\&quot;;s:120:\&quot;aHR0cDovL21hZ2VudG8tMS42LjAuMC1yYzIubG9jYWwvaW5kZXgucGhwL2VsZWN0cm9uaWNzL2NlbGwtcGhvbmVzL2h0Yy10b3VjaC1kaWFtb25kLmh0bWw,\&quot;;s:7:\&quot;product\&quot;;s:3:\&quot;166\&quot;;s:15:\&quot;related_product\&quot;;s:0:\&quot;\&quot;;s:3:\&quot;qty\&quot;;s:1:\&quot;6\&quot;;}');
INSERT INTO `sales_flat_quote_item_option` VALUES ('3', '3', '44', 'info_buyRequest', 'a:4:{s:4:\&quot;uenc\&quot;;s:224:\&quot;aHR0cDovL21hZ2VudG8tMS42LjAuMC1yYzIubG9jYWwvaW5kZXgucGhwL2VsZWN0cm9uaWNzL2NhbWVyYXMvZGlnaXRhbC1jYW1lcmFzL2Nhbm9uLWRpZ2l0YWwtcmViZWwteHQtOG1wLWRpZ2l0YWwtc2xyLWNhbWVyYS13aXRoLWVmLXMtMTgtNTVtbS1mMy01LTUtNi1sZW5zLWJsYWNrLmh0bWw,\&quot;;s:7:\&quot;product\&quot;;s:2:\&quot;44\&quot;;s:15:\&quot;related_product\&quot;;s:0:\&quot;\&quot;;s:3:\&quot;qty\&quot;;s:1:\&quot;3\&quot;;}');
</pre>
<p>Based on this simple example of 3 &#8220;simple&#8221; product types we have all those entires in the database. As you can see, all of the entries are done on only &#8220;sales_flat_quote_&#8221; prefixed tables, some of them, not all. Since we tested against very simple case.</p>
<p>Let us proceed now and do the checkout based on the items we have in the cart. In order to keep the process with minimum data in the system, I will go trough the checkout as &#8220;guest&#8221; user using &#8220;Flat Rate&#8221; shipping and &#8220;Check / Money order&#8221; payment.</p>
<p>Right before we click the &#8220;Place Order&#8221; button on our one page checkout, additional quote tables get filled. Below is the example that follows the previous state.</p>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_quote_address
-- ----------------------------
INSERT INTO `sales_flat_quote_address` VALUES ('1', '1', '2011-07-26 11:20:34', '2011-07-26 11:39:27', null, '1', null, 'billing', 'email_address@gmail.com', null, 'Branko', null, 'Ajzele', null, 'Inchoo', 'Sample address line1\nAddress line 2', 'Osijek', null, null, '31431', 'HR', '0038531888777', 'admin', '0', '0', '0', null, null, '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', null, 'a:0:{}', null, null, null, '0.0000', null, null, null, null, null, null, '0.0000', '0.0000');
INSERT INTO `sales_flat_quote_address` VALUES ('2', '1', '2011-07-26 11:20:34', '2011-07-26 11:39:27', null, '0', null, 'shipping', 'email_address@gmail.com', null, 'Branko', null, 'Ajzele', null, 'Inchoo', 'Sample address line1\nAddress line 2', 'Osijek', null, null, '31431', 'HR', '0038531888777', 'admin', '1', '0', '0', 'flatrate_flatrate', 'Flat Rate - Fixed', '29.0000', '6196.9900', '6196.9900', '0.0000', '0.0000', '0.0000', '0.0000', '50.0000', '50.0000', '0.0000', '0.0000', '0.0000', '0.0000', '6246.9900', '6246.9900', null, 'a:0:{}', null, '0.0000', '0.0000', '6196.9900', null, null, '0.0000', '0.0000', '0.0000', null, '50.0000', '50.0000');
</pre>
<p>As you can see, our sales_flat_quote_address table got it’s records updated with new values. In previous (first) one we had no values for &#8220;email&#8221; or other columns, but now we do once we are passed the checkout steps all the way to final &#8220;Place Order&#8221; button.</p>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_quote_payment
-- ----------------------------
INSERT INTO `sales_flat_quote_payment` VALUES ('1', '1', '2011-07-26 11:29:36', '2011-07-26 11:39:27', 'checkmo', null, null, null, null, null, '0', '0', null, '0', '0', null, null, null, null, null, null, null, null, null, null);
</pre>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_quote_shipping_rate
-- ----------------------------
INSERT INTO `sales_flat_quote_shipping_rate` VALUES ('2', '2', '2011-07-26 11:39:01', '2011-07-26 11:39:27', 'flatrate', 'Flat Rate', 'flatrate_flatrate', 'flatrate', null, '50.0000', 'Fixed', null);
</pre>
<p>So basically, all the way prior to finally clicking the &#8220;Place Order&#8221; button we have our data stored in &#8220;quote&#8221; tables. Meaning, &#8220;Quote&#8221; model is &#8220;data storage&#8221; from which the &#8220;Order&#8221; model is actually created. Or by more fancy definition, &#8220;Quote&#8221; is a formal offer for products or services proposed at specific prices and related payment terms tied to a given customer.</p>
<p>After we hit the &#8220;Place Order&#8221; button we get our order created. In that regards to previously said about the quote, &#8220;Order&#8221; is a quote that has been accepted.</p>
<p>So after the order has been successfully created, there are several new entries in the &#8220;sales_flat_order_&#8221; prefixed tables. Let’s take a look at those.</p>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_order
-- ----------------------------
INSERT INTO `sales_flat_order` VALUES ('1', 'new', 'pending', null, 'b71db3', 'Flat Rate - Fixed', '0', '1', null, '0.0000', null, null, null, '6246.9900', '50.0000', null, null, null, '0.0000', null, '6196.9900', null, null, null, '0.0000', null, null, null, '1.0000', '1.0000', null, null, null, null, null, null, null, null, '0.0000', null, null, null, '6246.9900', '50.0000', null, null, null, '0.0000', null, '1.0000', '1.0000', '6196.9900', null, null, null, '0.0000', null, null, null, null, null, null, null, null, '10.0000', null, null, null, '1', '1', '1', '0', null, '1', null, null, null, null, null, '1', '2', null, null, null, null, '0.0000', '6196.9900', null, null, '0.0000', '6196.9900', null, '29.0000', null, '100000001', null, 'USD', 'email_address@gmail.com', 'Branko', 'Ajzele', null, null, null, null, null, null, null, 'USD', null, null, 'USD', null, null, null, null, null, '127.0.0.1', 'flatrate_flatrate', 'USD', 'Main Website\nMain Store\nEnglish', null, null, '2011-07-26 11:39:27', '2011-07-26 11:39:31', '3', null, null, null, null, null, null, null, null, null, null, null, '0.0000', '0.0000', '0.0000', '0.0000', null, null, null, null, '50.0000', '50.0000');
</pre>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_order_address
-- ----------------------------
INSERT INTO `sales_flat_order_address` VALUES ('1', '1', null, null, null, null, 'admin', null, '31431', 'Ajzele', 'Sample address line1\nAddress line 2', 'Osijek', 'email_address@gmail.com', '0038531888777', 'HR', 'Branko', 'billing', null, null, null, 'Inchoo', null, null, null);
INSERT INTO `sales_flat_order_address` VALUES ('2', '1', null, null, null, null, 'admin', null, '31431', 'Ajzele', 'Sample address line1\nAddress line 2', 'Osijek', 'email_address@gmail.com', '0038531888777', 'HR', 'Branko', 'shipping', null, null, null, 'Inchoo', null, null, null);
</pre>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_order_grid
-- ----------------------------
INSERT INTO `sales_flat_order_grid` VALUES ('1', 'pending', '1', 'Main Website\nMain Store\nEnglish', null, '6246.9900', null, '6246.9900', null, '100000001', 'USD', 'USD', 'Branko Ajzele', 'Branko Ajzele', '2011-07-26 11:39:27', '2011-07-26 11:39:31');
</pre>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_order_item
-- ----------------------------
INSERT INTO `sales_flat_order_item` VALUES ('1', '1', null, '1', '1', '2011-07-26 11:39:27', '2011-07-26 11:39:27', '17', 'simple', 'a:1:{s:15:\&quot;info_buyRequest\&quot;;a:4:{s:4:\&quot;uenc\&quot;;s:124:\&quot;aHR0cDovL21hZ2VudG8tMS42LjAuMC1yYzIubG9jYWwvaW5kZXgucGhwL2VsZWN0cm9uaWNzL2NlbGwtcGhvbmVzL2JsYWNrYmVycnktODEwMC1wZWFybC5odG1s\&quot;;s:7:\&quot;product\&quot;;s:2:\&quot;17\&quot;;s:15:\&quot;related_product\&quot;;s:0:\&quot;\&quot;;s:3:\&quot;qty\&quot;;s:1:\&quot;1\&quot;;}}', '15.2000', '0', 'bb8100', 'BlackBerry 8100 Pearl', null, null, null, '0', '0', '0', null, '0.0000', '0.0000', '1.0000', '0.0000', '0.0000', '29.9900', '349.9900', '349.9900', '349.9900', '349.9900', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '349.9900', '349.9900', '0.0000', '0.0000', '15.2000', null, null, null, null, 'a:0:{}', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', null, null, null, '349.9900', '349.9900', '349.9900', '349.9900', null, null, null, null, null, null, '0', null, null, null);
INSERT INTO `sales_flat_order_item` VALUES ('2', '1', null, '2', '1', '2011-07-26 11:39:29', '2011-07-26 11:39:29', '166', 'simple', 'a:1:{s:15:\&quot;info_buyRequest\&quot;;a:4:{s:4:\&quot;uenc\&quot;;s:120:\&quot;aHR0cDovL21hZ2VudG8tMS42LjAuMC1yYzIubG9jYWwvaW5kZXgucGhwL2VsZWN0cm9uaWNzL2NlbGwtcGhvbmVzL2h0Yy10b3VjaC1kaWFtb25kLmh0bWw,\&quot;;s:7:\&quot;product\&quot;;s:3:\&quot;166\&quot;;s:15:\&quot;related_product\&quot;;s:0:\&quot;\&quot;;s:3:\&quot;qty\&quot;;s:1:\&quot;6\&quot;;}}', '0.3000', '0', 'HTC Touch Diamond', 'HTC Touch Diamond', null, null, null, '0', '0', '0', null, '0.0000', '0.0000', '6.0000', '0.0000', '0.0000', null, '750.0000', '750.0000', '750.0000', '750.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '4500.0000', '4500.0000', '0.0000', '0.0000', '1.8000', null, null, null, null, 'a:0:{}', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', null, null, null, '750.0000', '750.0000', '4500.0000', '4500.0000', null, null, null, null, null, null, '0', null, null, null);
INSERT INTO `sales_flat_order_item` VALUES ('3', '1', null, '3', '1', '2011-07-26 11:39:30', '2011-07-26 11:39:30', '44', 'simple', 'a:1:{s:15:\&quot;info_buyRequest\&quot;;a:4:{s:4:\&quot;uenc\&quot;;s:224:\&quot;aHR0cDovL21hZ2VudG8tMS42LjAuMC1yYzIubG9jYWwvaW5kZXgucGhwL2VsZWN0cm9uaWNzL2NhbWVyYXMvZGlnaXRhbC1jYW1lcmFzL2Nhbm9uLWRpZ2l0YWwtcmViZWwteHQtOG1wLWRpZ2l0YWwtc2xyLWNhbWVyYS13aXRoLWVmLXMtMTgtNTVtbS1mMy01LTUtNi1sZW5zLWJsYWNrLmh0bWw,\&quot;;s:7:\&quot;product\&quot;;s:2:\&quot;44\&quot;;s:15:\&quot;related_product\&quot;;s:0:\&quot;\&quot;;s:3:\&quot;qty\&quot;;s:1:\&quot;3\&quot;;}}', '4.0000', '0', 'Rebel XT', 'Canon Digital Rebel XT 8MP Digital SLR Camera', null, null, null, '0', '0', '0', null, '0.0000', '0.0000', '3.0000', '0.0000', '0.0000', '200.0000', '449.0000', '449.0000', '449.0000', '449.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '1347.0000', '1347.0000', '0.0000', '0.0000', '12.0000', null, null, null, null, 'a:0:{}', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', null, null, null, '449.0000', '449.0000', '1347.0000', '1347.0000', null, null, null, null, null, null, '0', null, null, null);
</pre>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_order_payment
-- ----------------------------
INSERT INTO `sales_flat_order_payment` VALUES ('1', '1', null, null, null, null, null, null, null, null, '50.0000', '50.0000', null, null, '6246.9900', null, null, null, '6246.9900', null, null, null, null, '0', '0', null, 'checkmo', null, null, null, null, null, null, null, null, null, null, null, '0', null, null, null, null, null, null, null, '0', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
</pre>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_order_status_history
-- ----------------------------
INSERT INTO `sales_flat_order_status_history` VALUES ('1', '1', '1', '0', null, 'pending', '2011-07-26 11:39:31', 'order');
</pre>
<p>Now that we have our real order it is logical to make the final step and bill it / charge it. Usually if you are using PayPal or Authorize.net or some other fancy payment gateway this process of charging is done automatically. Once charged order changes it’s state from &#8220;new&#8221; or &#8220;pending&#8221; to &#8220;processing&#8221;. Which leads us to the next model in this workflow called &#8220;Invoice&#8221;. It’s easy to conclude that &#8220;Invoice&#8221; is by definition an order that has been billed. This &#8220;billed&#8221; data is then saved in invoice related/named tables. It’s easy to find those in Magento database as table names start with the &#8220;sales_flat_invoice&#8221; prefix.</p>
<p>Once we click the &#8220;Invoice&#8221; button on the Magento order view page in the admin, these invoice tables get populated with relevant data. Before I dump the SQL queries that filled my invoice table, its important to note that I invoiced entire order, meaning all items at once. General concept of Magento allows you to create several invoices for one order, meaning you can invoice partial quantities of ordered items or individual items only per single invoice.</p>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_invoice
-- ----------------------------
INSERT INTO `sales_flat_invoice` VALUES ('1', '1', '6246.9900', '0.0000', '0.0000', '0.0000', '1.0000', '0.0000', '0.0000', '1.0000', '6246.9900', '50.0000', '6196.9900', '6196.9900', '1.0000', '50.0000', '10.0000', '1.0000', '6196.9900', '6196.9900', '0.0000', '1', null, '1', null, '0', '2', '2', null, 'USD', null, 'USD', 'USD', 'USD', '100000001', '2011-07-26 12:09:56', '2011-07-26 12:09:56', null, null, null, null, null, null, null, '0.0000', '0.0000', '0.0000', null, '50.0000', '50.0000', null);
</pre>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_invoice_grid
-- ----------------------------
INSERT INTO `sales_flat_invoice_grid` VALUES ('1', '1', '6246.9900', '6246.9900', '1', '2', 'USD', 'USD', 'USD', 'USD', '100000001', '100000001', '2011-07-26 12:09:56', '2011-07-26 11:39:27', 'Branko Ajzele');
</pre>
<pre class="brush: sql; title: ; notranslate">
-- ----------------------------
-- Records of sales_flat_invoice_item
-- ----------------------------
INSERT INTO `sales_flat_invoice_item` VALUES ('1', '1', '349.9900', '0.0000', '0.0000', '0.0000', '0.0000', '349.9900', null, '349.9900', '0.0000', null, '0.0000', '349.9900', '0.0000', '0.0000', '349.9900', '1.0000', '0.0000', '29.9900', '0.0000', '349.9900', '349.9900', '349.9900', '17', '1', null, null, 'a:0:{}', 'bb8100', 'BlackBerry 8100 Pearl', null, '0.0000', '0.0000');
INSERT INTO `sales_flat_invoice_item` VALUES ('2', '1', '750.0000', '0.0000', '0.0000', '0.0000', '0.0000', '4500.0000', null, '4500.0000', '0.0000', null, '0.0000', '750.0000', '0.0000', '0.0000', '750.0000', '6.0000', '0.0000', null, '0.0000', '750.0000', '4500.0000', '4500.0000', '166', '2', null, null, 'a:0:{}', 'HTC Touch Diamond', 'HTC Touch Diamond', null, '0.0000', '0.0000');
INSERT INTO `sales_flat_invoice_item` VALUES ('3', '1', '449.0000', '0.0000', '0.0000', '0.0000', '0.0000', '1347.0000', null, '1347.0000', '0.0000', null, '0.0000', '449.0000', '0.0000', '0.0000', '449.0000', '3.0000', '0.0000', '200.0000', '0.0000', '449.0000', '1347.0000', '1347.0000', '44', '3', null, null, 'a:0:{}', 'Rebel XT', 'Canon Digital Rebel XT 8MP Digital SLR Camera', null, '0.0000', '0.0000');
</pre>
<p>Basically this circles the simplest story around the order creation process. Meaning there are 3 concepts/terms to remember here: Quote, Order, Invoice.</p>
<p>There is also a Shipment, but we will leave this for another time <img src='http://inchoo.net/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>As a developer you should have a pretty good understanding of &#8220;Quote, Order, Invoice&#8221; relation in Magento, especcially if you are doing any custom coding around it. Coding such as &#8220;custom order statuses&#8221;, &#8220;change of order states based on some info retrieved from external web service&#8221;, etc.</p>
<p>So now that all this &#8220;theory&#8221; is behind us, lets try to programmatically create order order and invoice it. Below is the code that does just that for the simplest possible case of using just &#8220;simple&#8221; product types.</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php

require_once 'app/Mage.php';

Mage::app('default');

$store = Mage::app()-&gt;getStore('default');

$customer = Mage::getModel('customer/customer');
$customer-&gt;setStore($store);
$customer-&gt;loadByEmail('email_address@gmail.com');

$quote = Mage::getModel('sales/quote');
$quote-&gt;setStore($store);
$quote-&gt;assignCustomer($customer);

$product1 = Mage::getModel('catalog/product')-&gt;load(166); /* HTC Touch Diamond */
$buyInfo1 = array('qty' =&gt; 1);

$product2 = Mage::getModel('catalog/product')-&gt;load(18); /* Sony Ericsson W810i */
$buyInfo2 = array('qty' =&gt; 3);

$quote-&gt;addProduct($product1, new Varien_Object($buyInfo1));
$quote-&gt;addProduct($product2, new Varien_Object($buyInfo2));

$billingAddress = $quote-&gt;getBillingAddress()-&gt;addData($customer-&gt;getPrimaryBillingAddress());
$shippingAddress = $quote-&gt;getShippingAddress()-&gt;addData($customer-&gt;getPrimaryShippingAddress());

$shippingAddress-&gt;setCollectShippingRates(true)-&gt;collectShippingRates()
                -&gt;setShippingMethod('flatrate_flatrate')
                -&gt;setPaymentMethod('checkmo');

$quote-&gt;getPayment()-&gt;importData(array('method' =&gt; 'checkmo'));

$quote-&gt;collectTotals()-&gt;save();

$service = Mage::getModel('sales/service_quote', $quote);
$service-&gt;submitAll();
$order = $service-&gt;getOrder();

$invoice = Mage::getModel('sales/service_order', $order)-&gt;prepareInvoice();
$invoice-&gt;setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_ONLINE);
$invoice-&gt;register();

$transaction = Mage::getModel('core/resource_transaction')
                    -&gt;addObject($invoice)
                    -&gt;addObject($invoice-&gt;getOrder());

$transaction-&gt;save();
</pre>
<p>I realize there are lot of things left unexplained in the code above, such as &#8220;where does the $service come from&#8221;, &#8220;why are we using $transaction resource for invoice creation&#8221;, etc. For example, if in the above code I where to code a order creation process for &#8220;grouped&#8221; or &#8220;configurable&#8221; product type, possibly for product with custom options, my code would be far more complex. Not to mention custom/new product types created by you or some other third party extension. There is no unique formula to cover all, especially custom product types. All I can say, is that in order to get a true understanding of the process in the background you really need to trace the code thoroughly for specific case.</p>
<p>Hope this article was, to some extent, useful.</p>
<p>Cheers.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/magento-quote-order-invoice-workflow/feed/</wfw:commentRss>
		<slash:comments>17</slash:comments>
		</item>
		<item>
		<title>HowTo &#8211; &#8220;Google +1&#8243; extension for Magento</title>
		<link>http://inchoo.net/ecommerce/magento/howto-google-1-extension-for-magento/</link>
		<comments>http://inchoo.net/ecommerce/magento/howto-google-1-extension-for-magento/#comments</comments>
		<pubDate>Mon, 25 Jul 2011 19:26:31 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Extensions]]></category>
		<category><![CDATA[Magento]]></category>
		<category><![CDATA[Marketing]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[plus]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=10501</guid>
		<description><![CDATA[Embedding Google&#8217;s +1 button is pretty easy and straightforward. All it takes is to include 2 code snippets in your web page. One goes in the header of the page, &#8230;]]></description>
			<content:encoded><![CDATA[<p>Embedding Google&#8217;s +1 button is pretty easy and straightforward. All it takes is to include 2 code snippets in your web page. One goes in the header of the page, and the other one goes to wherever where you want the +1 button to render. To do so all you need is to follow the official Google guideline outlined <a href="http://www.google.com/intl/en/webmasters/+1/button/index.html">here</a>.</p>
<p>With that in mind, let&#8217;s build us Magento extension, preferably the <a href="http://inchoo.net/ecommerce/magento/developing-extensions-that-react-unobtrusively-with-magento/">non-obtrusive</a> one.<span id="more-10501"></span></p>
<p>We will call our extension &#8220;GPlusOne&#8221; and place it under the &#8220;Inchoo&#8221; namespace. Let&#8217;s start by creating the /app/etc/modules/Inchoo_GPlusOne.xml file with the following content:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot;?&gt;

&lt;config&gt;
    &lt;modules&gt;
        &lt;Inchoo_GPlusOne&gt;
            &lt;active&gt;true&lt;/active&gt;
            &lt;codePool&gt;community&lt;/codePool&gt;
        &lt;/Inchoo_GPlusOne&gt;
    &lt;/modules&gt;
&lt;/config&gt;
</pre>
<p>Now we need to create second file, app/code/community/Inchoo/GPlusOne/etc/<strong>config.xml</strong> with the following content:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot;?&gt;

&lt;config&gt;
    &lt;modules&gt;
        &lt;Inchoo_GPlusOne&gt;
            &lt;version&gt;1.0.0.0&lt;/version&gt;
        &lt;/Inchoo_GPlusOne&gt;
    &lt;/modules&gt;
    &lt;global&gt;
        &lt;blocks&gt;
            &lt;inchoo_gplusone&gt;
                &lt;class&gt;Inchoo_GPlusOne_Block&lt;/class&gt;
            &lt;/inchoo_gplusone&gt;
        &lt;/blocks&gt;
    &lt;/global&gt;

    &lt;frontend&gt;
        &lt;layout&gt;
            &lt;updates&gt;
                &lt;inchoo_gplusone&gt;
                    &lt;file&gt;inchoo/gplusone.xml&lt;/file&gt;
                &lt;/inchoo_gplusone&gt;
            &lt;/updates&gt;
        &lt;/layout&gt;
    &lt;/frontend&gt;
&lt;/config&gt;
</pre>
<p>As you can see we are declaring a new layout file here, called gplusone.xml which we will place under the app/design/frontend/default/default/layout/inchoo/<strong>gplusone.xml</strong>, with the following content:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot;?&gt;

&lt;layout version=&quot;1.0.0&quot;&gt;
    &lt;catalog_product_view&gt;
        &lt;reference name=&quot;head&quot;&gt;
            &lt;block type=&quot;core/template&quot; name=&quot;inchoo_gplusone_head&quot; template=&quot;inchoo/gplusone_head.phtml&quot; /&gt;
        &lt;/reference&gt;
        &lt;reference name=&quot;alert.urls&quot;&gt;
            &lt;block type=&quot;inchoo_gplusone/PlusButton&quot; name=&quot;inchoo_gplusone&quot; /&gt;
        &lt;/reference&gt;
    &lt;/catalog_product_view&gt;
&lt;/layout&gt;
</pre>
<p>Carefully observing the gplusone.xml layout update shows that my layout is to &#8220;kick in&#8221; only on the product view page. Since embedding the Google +1 Button reguires two scripts to be loaded on page, I&#8217;m splitting them into two view files, app/design/frontend/default/default/template/inchoo/<strong>gplusone_head.phtml</strong> and app/design/frontend/default/default/template/inchoo/<strong>gplusone_button.phtml</strong>.</p>
<p>File app/design/frontend/default/default/template/inchoo/gplusone_head.phtml is loaded into the head area of HTML via ‘reference name=&#8221;head&#8221;&#8216; layout update, having the following content:</p>
<pre class="brush: php; title: ; notranslate">
&lt;script type=&quot;text/javascript&quot; src=&quot;https://apis.google.com/js/plusone.js&quot;&gt;&lt;/script&gt;
</pre>
<p>While file app/design/frontend/default/default/template/inchoo/gplusone_button.phtml is loaded into the content area of the page, more specifically &#8220;alert.urls&#8221; block via ‘reference name=&#8221;alert.urls&#8221;. For more detailed understanding one should study the app/design/frontend/base/default/layout/catalog.xml file and its ‘<catalog_product_view translate="label">&#8216; section. Content of gplusone_button.phtml  is as follows:</p>
<pre class="brush: php; title: ; notranslate">
&lt;g:plusone &lt;?php if($size = $this-&gt;getSize()) { echo ' size=&quot;'.$size.'&quot;'; } ?&gt;&gt;&lt;/g:plusone&gt;
</pre>
<p>Which bring&#8217;s us to the final file needed to get this running, app/code/community/Inchoo/GPlusOne/Block/<strong>PlusButton.php</strong> with the following content:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php

class Inchoo_GPlusOne_Block_PlusButton extends Mage_Core_Block_Template
{
    /**
     * Constructor. Set template.
     */
    protected function _construct()
    {
        parent::_construct();
        $this-&gt;setTemplate('inchoo/gplusone_button.phtml');
    }

    public function getSize()
    {
        //Here we can implement the code to read the config values for sizes
        return '';
    }
}
</pre>
<p>If you look at the gplusone_button.phtml file you will see it has a call towards getSize() method from Inchoo_GPlusOne_Block_PlusButton class. At the moment, this method does nothing. It&#8217;s merely here for possible extension of this simple module. Given that Google +1 button supports various sizes (&#8220;small&#8221;, &#8220;medium&#8221;, &#8220;tall&#8221;, or no size definition for standard size) you can easily add some extra logic to this extension to have it read some config values that could be set from Magento admin.</p>
<p>The point of this article was not to give you a full blown extension ready to be used in instance, rather to show you the amount of effort needed to implement something as simple as Google +1 button in Magento the right way (not to say that this is the best possible way).</p>
<p>And here is the screenshot of the final result.</p>
<p><a href="http://inchoo.net/wp-content/uploads/2011/07/gplus.png"><img src="http://inchoo.net/wp-content/uploads/2011/07/gplus-300x150.png" alt="" title="gplus" width="300" height="150" class="alignnone size-thumbnail wp-image-10505" /></a></p>
<p>Hope someone finds it useful. </p>
<p>Cheers.</catalog_product_view></p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/ecommerce/magento/howto-google-1-extension-for-magento/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
		<item>
		<title>PHP to (Java) Android &#8211; Array vs HashMap</title>
		<link>http://inchoo.net/mobile-development/android-development/php-to-java-android-array-vs-hashmap/</link>
		<comments>http://inchoo.net/mobile-development/android-development/php-to-java-android-array-vs-hashmap/#comments</comments>
		<pubDate>Sat, 11 Jun 2011 01:21:51 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Android development]]></category>
		<category><![CDATA[Mobile development]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[arrays]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=10013</guid>
		<description><![CDATA[One of the coolest and most powerful things in PHP are arrays. Why? Simply put, you can place almost anything in them. Recently I was playing with the JSON API &#8230;]]></description>
			<content:encoded><![CDATA[<p>One of the coolest and most powerful things in PHP are arrays. Why? Simply put, you can place almost anything in them. Recently I was playing with the JSON API calling various API methods and passing parameters. For a moment I got stuck on a pretty simple step, arrays.<span id="more-10013"></span></p>
<p>For example, imagine that in PHP you need to construct a JSON string like shown below:</p>
<p>HTTPPayloadLine: {&quot;id&quot;:148,&quot;method&quot;:&quot;customerRegistration&quot;,&quot;params&quot;:[&quot;v5unacb9rntvc10fh7k5l1ops3&quot;,{&quot;passwd&quot;:&quot;MyPassHere&quot;,&quot;email&quot;:&quot;devtest1@gmail.com&quot;,&quot;street&quot;:&quot;Sample Street 1&quot;,&quot;locale&quot;:&quot;en_US&quot;,&quot;lastname&quot;:&quot;LTest1&quot;,&quot;gender&quot;:&quot;0&quot;,&quot;firstname&quot;:&quot;FTest1&quot;}]}</p>
<p>More specifically the &#8220;params&#8221; part. In PHP I would most likely do something like:</p>
<pre class="brush: php; title: ; notranslate">
$params = array(
    &quot;v5unacb9rntvc10fh7k5l1ops3&quot;,
    array(
        &quot;passwd&quot; =&gt; &quot;MyPassHere&quot;,
        &quot;email&quot; =&gt; &quot;devtest1@gmail.com&quot;,
        &quot;street&quot; =&gt; &quot;Sample Street 1&quot;,
        &quot;locale&quot; =&gt; &quot;en_US&quot;,
        &quot;lastname&quot; =&gt; &quot;LTest1&quot;,
        &quot;gender&quot; =&gt; &quot;0&quot;,
        &quot;firstname&quot; =&gt; &quot;FTest1&quot;
    )
);
</pre>
<p>Then simply use json_encode($params) and I&#8217;m done.</p>
<p>Now, here is how I did it in my Java/Android example:</p>
<pre class="brush: java; title: ; notranslate">
HashMap&lt;string , Object&gt; customerMap = new HashMap&lt;/string&gt;&lt;string , Object&gt;();

customerMap.put(&quot;email&quot;, &quot;devtest1@gmail.com&quot;);
customerMap.put(&quot;passwd&quot;, &quot;MyPassHere&quot;);
customerMap.put(&quot;firstname&quot;, &quot;FTest1&quot;);
customerMap.put(&quot;lastname&quot;, &quot;LTest1&quot;);
customerMap.put(&quot;street&quot;, &quot;Sample Street 1&quot;);
customerMap.put(&quot;postcode&quot;, &quot;789991&quot;);
customerMap.put(&quot;city&quot;, &quot;City1&quot;);
customerMap.put(&quot;locale&quot;, &quot;en_US&quot;);
customerMap.put(&quot;gender&quot;, &quot;0&quot;);

JSONObject customerMapParams = new JSONObject();
Iterator&lt;entry &lt;String, Object&gt;&gt; itr = customerMap.entrySet().iterator();

while(itr.hasNext()) {
    Entry&lt;string , Object&gt; entry = itr.next();
    customerMapParams.put(entry.getKey(), entry.getValue());
}
</pre>
<p>Please note, this is just one way of doing things. Hope it helps someone.</p>
<p>Cheers.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/mobile-development/android-development/php-to-java-android-array-vs-hashmap/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Debugging Web Service API communication in Android</title>
		<link>http://inchoo.net/mobile-development/android-development/debugging-web-service-api-communication-in-android/</link>
		<comments>http://inchoo.net/mobile-development/android-development/debugging-web-service-api-communication-in-android/#comments</comments>
		<pubDate>Fri, 10 Jun 2011 08:08:31 +0000</pubDate>
		<dc:creator>Branko Ajzele</dc:creator>
				<category><![CDATA[Android development]]></category>
		<category><![CDATA[Mobile development]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[debugging]]></category>

		<guid isPermaLink="false">http://inchoo.net/?p=10021</guid>
		<description><![CDATA[Proper and fast debugging is a key to any application development. Unlike my great experience with PHP where I usually debug whatever I need in a breeze, I found my &#8230;]]></description>
			<content:encoded><![CDATA[<p>Proper and fast debugging is a key to any application development. Unlike my great experience with PHP where I usually debug whatever I need in a breeze, I found my self against the wall when working with Android and web service API calls. Problem is simple, you cannot that easily dump entire $_SESSION array like in PHP and see it&#8217;s content. Surely there are other ways as Java language has full set of it&#8217;s own debugging ways.<span id="more-10021"></span> </p>
<p>Here I&#8217;ll give you some practical guide on how you can really, down to the great detail test your API calls in Android. Get ready because we are going down to the network level. First of all, I&#8217;m using Windows 7 machine here, but fear not, all that is demonstrated can be used on the Linux machine as well, just with different tools.</p>
<p>So, the general idea is to set your PC/Laptop WiFi connection to serve as a HotSpot on which you will connect trough your Android phone. This way all network traffic from your phone goes trough your computer. And then, all you need on your computer is software like Windows Network Monitor to monitor all your HTTP traffic and you can see all of the requests and responses in great details. For me, this approach was a savior as it enabled me more fast debugging of web service API calls trough my Android app.</p>
<p>So here it is in a few simple steps:</p>
<ol>
<li>Download and install Microsoft Network Monitor (or WireShark, which is also great tool).</li>
<li>Download and install <a href="www.connectify.me">Connectify</a>.</li>
<li>Using Connectify setup your PC to act as a HotSpot.</li>
<li>Connect your phone to the HotSpot on your PC.</li>
<li>Start your Microsoft Network Monitor (or WireShark, or some XYZ tool).</li>
<li>Start your application on Android phone and monitor the HTTP traffic.</li>
</ol>
<p>Additionally, in my case where I used MS Network Monitor I setup the filters like:</p>
<p><i><br />
HTTP AND ((Source == &#8220;192.168.2.101&#8243; AND Destination == &#8220;some-api.domain&#8221;) OR (Destination == &#8220;192.168.2.101&#8243; AND Source == &#8220;some-api.domain&#8221;))<br />
</i></p>
<p>This enabled me to specifically filter out the traffic between my Android app and the Webservice API I was calling.</p>
<p>Having an insight into the raw HTTP request response is far more informative then debugging individual Java objects like HttpPost, HttpResponse, HttpEntity (at least it was for me).</p>
<p>As an example, here is a sample of request:</p>
<p><i><br />
  Frame: Number = 283, Captured Frame Length = 199, MediaType = WiFi<br />
+ WiFi: [Unencrypted Data] .T&#8230;.., (I) RSSI = -40 dBm, Rate = 54.0 Mbps<br />
+ LLC: Unnumbered(U) Frame, Command Frame, SSAP = SNAP(Sub-Network Access Protocol), DSAP = SNAP(Sub-Network Access Protocol)<br />
+ Snap: EtherType = Internet IP (IPv4), OrgCode = XEROX CORPORATION<br />
+ Ipv4: Src = 192.168.2.101, Dest = 46.137.*.*, Next Protocol = TCP, Packet ID = 12297, Total IP Length = 135<br />
+ Tcp: Flags=&#8230;AP&#8230;, SrcPort=32886, DstPort=HTTP(80), PayloadLen=83, Seq=499775385 &#8211; 499775468, Ack=2775527642, Win=32044<br />
- Http: HTTP Payload, URL:<br />
  &#8211; payload: HttpContentType =<br />
     HTTPPayloadLine: {&#8220;id&#8221;:0,&#8221;method&#8221;:&#8221;getActiveCities&#8221;,&#8221;params&#8221;:["tlrg9507bl9tm6lu899vg90115","de_DE"]}<br />
</i> </p>
<p>And here is a sample of response captured from network monitor:</p>
<p><i><br />
  Frame: Number = 285, Captured Frame Length = 805, MediaType = WiFi<br />
+ WiFi: [Unencrypted Data] F&#8230;&#8230;, (I)<br />
+ LLC: Unnumbered(U) Frame, Command Frame, SSAP = SNAP(Sub-Network Access Protocol), DSAP = SNAP(Sub-Network Access Protocol)<br />
+ Snap: EtherType = Internet IP (IPv4), OrgCode = XEROX CORPORATION<br />
+ Ipv4: Src = 46.137.*.*, Dest = 192.168.2.101, Next Protocol = TCP, Packet ID = 14634, Total IP Length = 741<br />
+ Tcp: Flags=&#8230;AP&#8230;, SrcPort=HTTP(80), DstPort=32886, PayloadLen=689, Seq=2775527642 &#8211; 2775528331, Ack=499775468, Win=31<br />
- Http: Response, HTTP/1.1, Status: Ok, URL:<br />
    ProtocolVersion: HTTP/1.1<br />
    StatusCode: 200, Ok<br />
    Reason: OK<br />
    Cache-Control:  no-store, no-cache, must-revalidate, post-check=0, pre-check=0<br />
  + ContentType:  application/json<br />
    Date:  Fri, 10 Jun 2011 15:14:37 GMT<br />
    Expires:  Thu, 19 Nov 1981 08:52:00 GMT<br />
    Pragma:  no-cache<br />
    Server:  Apache<br />
    Set-Cookie:  PHPSESSID=tlrg9507bl9tm6lu899vg90115; path=/<br />
    ContentLength:  345<br />
    Connection:  keep-alive<br />
    HeaderEnd: CRLF<br />
  &#8211; payload: HttpContentType =  application/json<br />
     HTTPPayloadLine: {&#8220;result&#8221;:[{&#8220;cityId&#8221;:&#8221;239&#8243;,&#8221;name&#8221;:&#8221;Sofia&#8221;},{&#8220;cityId&#8221;:&#8221;266&#8243;,&#8221;name&#8221;:&#8221;Blagoevgrad&#8221;},{&#8220;cityId&#8221;:&#8221;296&#8243;,&#8221;name&#8221;:&#8221;Burgas&#8221;},{&#8220;cityId&#8221;:&#8221;294&#8243;,&#8221;name&#8221;:&#8221;Varna&#8221;},{&#8220;cityId&#8221;:&#8221;299&#8243;,&#8221;name&#8221;:&#8221;Veliko Tarnovo&#8221;},{&#8220;cityId&#8221;:&#8221;295&#8243;,&#8221;name&#8221;:&#8221;Plovdiv&#8221;},{&#8220;cityId&#8221;:&#8221;298&#8243;,&#8221;name<br />
</i></p>
<p>As you can see, in my case I was playing with JSON web service, calling it from Android app.</p>
<p>Hope this article will give you some ideas on debugging your Android apps.</p>
]]></content:encoded>
			<wfw:commentRss>http://inchoo.net/mobile-development/android-development/debugging-web-service-api-communication-in-android/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

