Monday, July 04, 2016

How to add Custom Fields to Contact Form in Magento

Magento comes with a nice Contact Us form which helps customers to keep in touch with store owners. But in this article we'll see how we can easily add more fields to this Magento Contact form.

Let's check out the various settings in the Admin Panel first.

Below is the Store Email Address section. Any one of them can be used with the Contact Forms Email sender. We'll use "Customer Support" as our sender.



Next, let's take a look at Contact Form settings.



Here, we have the Target email address where the Contact Email will be sent to. Email template can also be selected here.

So, now we need to make changes to 3 more files... 

1. app/design/frontend/default/YOUR_THEME/template/contacts/form.phtml
2. app/code/core/Mage/Contacts/controllers/IndexController.php
3. app/locale/en_US/template/email/contact_form.html

The first file is the contact form template where we would include our new fields.
The IndexController.php is the controller where we can add server side validation to the field.
The third file is the Email Template.

Let's start with the Contact Form template. We are going to add a "Subject" field to our form. So, we just create an <input> element with a name "subject". If we want to make it a required entry, let's add "required-entry" to its class. This will add JavaScript validation to it. Check out the HTML below.

<li>
  <div class="input-box">
    <input name="subject" id="subject" title="Subject" value="" class="input-text required-entry" type="text" placeholder="Subject" />
  </div>
</li>

Now, as it is a required field, we need to add a server side validation as well. Here the controller IndexController.php comes in help. We can add our validation within postAction() method as shown below. Here we have checked if the value is an empty one.

if (!Zend_Validate::is(trim($post['subject']) , 'NotEmpty')) 
{
    $error = true;
}

If Server side validation fails, an error message "Unable to submit your request. Please, try again later" is shown.

Now, let's modify the Email template to include the new field "Subject". Open the email template file app/locale/en_US/template/email/contact_form.html. 



See how we have included the field "subject" in our email template by writing ::

Subject: {{var data.subject}}

Another thing, the first line of this template says "<!--@subject Our CUSTOM Contact Form@-->". This will be the Contact Email Subject line. Here I have modified it to "Our CUSTOM Contact Form".

Final result is, one email with subject line "Our CUSTOM Contact Form" is sent from "Customer Support" profile to "storeowner@store.com" as entered in "System > Configuration > Contacts" shown above.

This way we can add any number of fields to the Contact Form, add validations to it, and include them in the final email.

Thursday, June 30, 2016

Parsing Complex XML with SimpleXML in PHP

Let's parse a very complex XML Data using SimpleXML methods in PHP. Let's take a complex XML data as shown below.

<NodeLevel1>
 <NodeLevel2>
  <NodeLevel3>
<RaceDay RaceDayDate="2016-06-29" >
   <Meeting MeetingCode="BR" MtgId="1299709952" VenueName="Doomben" >
<Pool PoolType="DD" DisplayStatus="SELLING"></Pool>
<Pool PoolType="XD" DisplayStatus="PAYING"></Pool>
<Pool PoolType="TT" DisplayStatus="CLOSED"></Pool>
<Pool PoolType="QD" DisplayStatus="CLOSED"></Pool>
<MultiPool PoolType="XD" DisplayStatus="PAYING"></MultiPool>
<Race RaceNo="1" RaceTime="12:53" RaceName="2YO HANDICAP" />
<Race RaceNo="2" RaceTime="13:23" RaceName="BM 75 HANDICAP" />
<Race RaceNo="3" RaceTime="13:53" RaceName="MAIDEN PLATE" >
     <TipsterTip TipsterId="0" Tips="4"/>
     <TipsterTip TipsterId="5" Tips="1-9-4-8"/>
     <Pool PoolType="A2" Available="Y" Abandoned="N" />
     <Pool PoolType="EX" Available="Y" Abandoned="N" />
     <Pool PoolType="F4" Available="Y" Abandoned="N" />
     <Runner RunnerNo="1" RunnerName="ALL TROOPS" />
     <Runner RunnerNo="2" RunnerName="SEQ THE STAR" />
     <Runner RunnerNo="3" RunnerName="SHADOW LAWN"/>
     <Runner RunnerNo="4" RunnerName="FREQUENDLY" />
</Race>
<Tipster TipsterId="0" TipsterName="LATE MAIL"/>
<Tipster TipsterId="1" TipsterName="RADIO TAB"/>
<Tipster TipsterId="2" TipsterName="TRACKMAN"/>
 </Meeting>
</RaceDay>
<RaceDay RaceDayDate="2016-06-30" >
 <Meeting MeetingCode="MR" MtgId="2299719559" VenueName="Lucas" >
<Pool PoolType="CC" DisplayStatus="SELLING"></Pool>
<Pool PoolType="YD" DisplayStatus="PAYING"></Pool>
<Pool PoolType="VT"  DisplayStatus="CLOSED"></Pool>
<Pool PoolType="MD" DisplayStatus="CLOSED"></Pool>
<MultiPool PoolType="VD" PoolDisplayStatus="PAYING"></MultiPool>
<Race RaceNo="1" RaceTime="12:53" RaceName="R2YO BHANDI" />
<Race RaceNo="2" RaceTime="13:23" RaceName="XX 75 ZINDA" />
<Race RaceNo="3" RaceTime="13:53" RaceName="PLATE RAIDEN" >
    <TipsterTip TipsterId="0" Tips="5"/>
    <TipsterTip TipsterId="5" Tips="2-1-4-8-4"/>
    <Pool PoolType="A2" Available="Y" Abandoned="N" />
    <Pool PoolType="EX" Available="Y" Abandoned="N" />
    <Pool PoolType="F4" Available="Y" Abandoned="N" />
    <Runner RunnerNo="1" RunnerName="ALL BROOKS" />
    <Runner RunnerNo="2" RunnerName="MIDDLE STAR" />
    <Runner RunnerNo="3" RunnerName="LONELY LAWN"/>
    <Runner RunnerNo="4" RunnerName="OBLIV" />
</Race>
<Tipster TipsterId="0" TipsterName="EARLY MAIL"/>
<Tipster TipsterId="1" TipsterName="RADIO CAB"/>
<Tipster TipsterId="2" TipsterName="JACKMAN"/>
 </Meeting>
  </RaceDay>
 </NodeLevel3>
</NodeLevel2>
</NodeLevel1>  

See that <NodeLevel3> node has two <RaceDay> nodes in it. And each <RaceDay> has its own <Meeting> node. Again each <Meeting> node  has various nodes like <Pool>, <MultiPool>, <Race>, <Tipster> as its children. Finally each <Race> node has <TipsterTip>, <Pool> and <Runner> nodes under it.

Here, Most of the nodes have attributes and some have descendants under it. We would traverse through all the <RaceDay> nodes and finds all its attributes and children. Let's start it.

<?php
$xml_source = <<<EOD
<NodeLevel1>
 <NodeLevel2>
  <NodeLevel3>
   <RaceDay RaceDayDate="2016-06-29" >
     <Meeting MeetingCode="BR" MtgId="1299709952" VenueName="Doomben" >
      <Pool PoolType="DD" DisplayStatus="SELLING"></Pool>
      <Pool PoolType="XD" DisplayStatus="PAYING"></Pool>
      <Pool PoolType="TT" DisplayStatus="CLOSED"></Pool>
      <Pool PoolType="QD" DisplayStatus="CLOSED"></Pool>
      <MultiPool PoolType="XD" DisplayStatus="PAYING">
      </MultiPool>
      <Race RaceNo="1" RaceTime="12:53" RaceName="2YO HANDICAP"/>
      <Race RaceNo="2" RaceTime="13:23" RaceName="BM7 HANDICAP"/>
      <Race RaceNo="3" RaceTime="13:53" RaceName="MAIDEN PLATE">
       <TipsterTip TipsterId="0" Tips="4"/>
       <TipsterTip TipsterId="5" Tips="1-9-4-8"/>
       <Pool PoolType="A2" Available="Y" Abandoned="N" />
       <Pool PoolType="EX" Available="Y" Abandoned="N" />
       <Pool PoolType="F4" Available="Y" Abandoned="N" />
       <Runner RunnerNo="1" RunnerName="ALL TROOPS" />
       <Runner RunnerNo="2" RunnerName="SEQ THE STAR" />
       <Runner RunnerNo="3" RunnerName="SHADOW LAWN"/>
       <Runner RunnerNo="4" RunnerName="FREQUENDLY" />
     </Race>
     <Tipster TipsterId="0" TipsterName="LATE MAIL"/>
     <Tipster TipsterId="1" TipsterName="RADIO TAB"/>
     <Tipster TipsterId="2" TipsterName="TRACKMAN"/>
   </Meeting>
  </RaceDay>
  <RaceDay RaceDayDate="2016-06-30" >
   <Meeting MeetingCode="MR" MtgId="2299719559" VenueName="Las" >
    <Pool PoolType="CC" PoolDisplayStatus="SELLING"></Pool>
    <Pool PoolType="YD" PoolDisplayStatus="PAYING"></Pool>
    <Pool PoolType="VT" PoolDisplayStatus="CLOSED"></Pool>
    <Pool PoolType="MD" PoolDisplayStatus="CLOSED"></Pool>
    <MultiPool PoolType="VD" DisplayStatus="PAYING">
    </MultiPool>
    <Race RaceNo="1" RaceTime="12:53" RaceName="R2YO BHANDI" />
    <Race RaceNo="2" RaceTime="13:23" RaceName="XX 75 ZINDA" />
    <Race RaceNo="3" RaceTime="13:53" RaceName="PLATE RAIDEN" >
     <TipsterTip TipsterId="0" Tips="5"/>
     <TipsterTip TipsterId="5" Tips="2-1-4-8-4"/>
     <Pool PoolType="A2" Available="Y" Abandoned="N" />
     <Pool PoolType="EX" Available="Y" Abandoned="N" />
     <Pool PoolType="F4" Available="Y" Abandoned="N" />
     <Runner RunnerNo="1" RunnerName="ALL BROOKS" />
     <Runner RunnerNo="2" RunnerName="MIDDLE STAR" />
     <Runner RunnerNo="3" RunnerName="LONELY LAWN"/>
     <Runner RunnerNo="4" RunnerName="OBLIV" />
    </Race>
    <Tipster TipsterId="0" TipsterName="EARLY MAIL"/>
    <Tipster TipsterId="1" TipsterName="RADIO CAB"/>
    <Tipster TipsterId="2" TipsterName="JACKMAN"/>
  </Meeting>
 </RaceDay>
</NodeLevel3>
</NodeLevel2>
</NodeLevel1>
EOD;
?>

See how I have declared the XML in a string using Heredoc in PHP.

$xml_source = <<<EOD

When using Heredoc, we need to make sure that there is no blankspace after the opening identifier. So, "<<<EOD" must be followed by a newline "\n"; which means in the editor, after typing "<<<EOD" we need to press ENTER to move to the new line.

Heredoc helps us to avoid quote (' or ") usage problems. See, all the node attributes are wrapped in double quote. We have another method to define the XML string as shown below. 

/// We make sure that all single quotes are escaped
$xml_source = '<NodeLevel1><NodeLevel2><NodeLevel3>' .
              '<RaceDay Name="John O\'Neal" > ..... '; 
 
Ok, now let's proceed.

// LOAD the XML Root Object
$all_nodes = new SimpleXMLElement($xml_source);

// BROWSE to Certain PATH/NODE
$all_nodelevel3 = $all_nodes
                  ->xpath('/NodeLevel1/NodeLevel2/NodeLevel3');

// PRINT what we got
print_r($all_nodelevel3);

The above piece of code would load the XML data, create SimpleXMLElement Object with it. Then we are traversing to "/NodeLevel1/NodeLevel2/NodeLevel3" node in the XML tree. xpath method actually searches the SimpleXML node for children matching the XPATH provided as its argument. We don't add the trailing slash ('/') to the end of our XPATH. 

To get all the <NodeLevel1> we need to pass "/NodeLevel1" as argument to xpath() method.

Now, let's print all the <Runner> nodes in the above XML.

<?php
// LOAD the XML Root Object
$all_nodes = new SimpleXMLElement($xml_source);

// BROWSE to all <NodeLevel3>
$all_nodelevel3 = $all_nodes->xpath('/NodeLevel1/NodeLevel2/NodeLevel3');

// LOOP THRU <NodeLevel3> nodes
foreach($all_nodelevel3 as $nodelevel3)
{
  // GEt All <RaceDay> Nodes
  $all_racedays = $nodelevel3->RaceDay;
  
  // LOOP THRU All <RaceDay> Nodes
  foreach($all_racedays as $raceday)  
  {
// GET ALL <Meeting>
$all_meeting = $raceday->Meeting;
 
// Loop Thru <Meeting>
foreach($all_meeting as $meeting)
{
     // GET ALL RACE
     $all_race = $meeting->Race;

     // LOOP Thru <Race> Nodes
     foreach($all_race as $race)
     {
 
       // GET ALL <Runner> nodes
       $all_runners = $race->Runner;
 
       /// Note that some <Race> nodes don't have
       /// <Runner> nodes under it
       /// So, we check if <Runner> nodes exist
       if($all_runners)
       {
/// Loop Thru <Runner> nodes
foreach($all_runners as $runner)
{
          /// GEt <Runner> Node's attributes
          $atts = $runner->attributes();

          // Loop Thru Attributes
          $str = "";
          foreach($atts as $key => $val)
          {
$str .= "$key => $val, ";
          }
         // PRINT  
          echo "RUNNER  :: $str <br>";

}
       }
     }
}
 }
}
?>

Check the Output Below :: 

RUNNER :: RunnerNo => 1, RunnerName => ALL TROOPS, 
RUNNER :: RunnerNo => 2, RunnerName => SEQ THE STAR, 
RUNNER :: RunnerNo => 3, RunnerName => SHADOW LAWN, 
RUNNER :: RunnerNo => 4, RunnerName => FREQUENDLY, 
RUNNER :: RunnerNo => 1, RunnerName => ALL BROOKS, 
RUNNER :: RunnerNo => 2, RunnerName => MIDDLE STAR, 
RUNNER :: RunnerNo => 3, RunnerName => LONELY LAWN, 
RUNNER :: RunnerNo => 4, RunnerName => OBLIV, 

See, how we have used "foreach" loop structure to traverse nodes and get deeper into the XML Tree. foreach construct has been used considering that <RaceDay>, <Meeting>, <Race> and <Runner> nodes may appear in any number within their Parent Node in the XML Tree. 

We even used foreach($all_nodelevel3 as $nodelevel3) to consider that many <NodeLevel3> nodes co-exist within a single <NodeLevel2> node.

Secondly, we have used attributes() function to get all the attributes of a node.

Hope this helps.

Tuesday, May 17, 2016

Determine Page Type in Magento

If we want to know page type ( whether it is a Category, Product, CMS, Cart page or Checkout Success page ) in Magento, the following code may come handy.

<?php

// GET current Category, Product
$category = Mage::registry('current_category');         
$product  = Mage::registry('current_product');

// WE May use the below also
//$product  = Mage::registry('product');

// GET ROUTE information
$route =  Mage::app()->getFrontController()
                     ->getRequest()->getRouteName();

// Return controller name
$controller = Mage::app()->getRequest()
                         ->getControllerName(); 

// Return action name
$action = Mage::app()->getRequest()->getActionName(); 


if($product != null) 
{
   // PRODUCT PAGE
   // WRITE code for PRODUCT PAGE
   
}
else if($category != null) 
{
   // CATEGORY PAGE
   // WRITE code for CATEGORY PAGE
}
else 
{  
   // CMS PAges
   $identifier = Mage::getSingleton('cms/page')->getIdentifier();
   
   // CART PAGE and ORDER SUCCESS Page
   if($route == 'checkout')
   {
      // Success Page
      if($action == 'success')
      {
// GET ORDER TOTAL
$orderId = Mage::getSingleton('checkout/session')
                    ->getLastRealOrderId();
        $order = Mage::getSingleton('sales/order')
                    ->loadByIncrementId($orderId);
        $orderTotal = $order->getGrandTotal(); 
      }
      else 
      // CART PAge
      {

// GET Cart Total
$quote = Mage::getModel('checkout/session')
                               ->getQuote();
$quoteData= $quote->getData();
$cartTotal = $quoteData['grand_total'];
      }
   
   }
   
   // FIRECHECKOUT PAGE
   if($route == 'firecheckout')
   {
                // IF IT is FIRECHECKOUT Page
   }
   
   // Search Result PAGE
   if($route == 'catalogsearch')
   {
       // IF IT is SEARCH RESULT Page
   }
   
   // Website HOME page
   if($identifier == "home")
   {
      // IF it is HOME PAGE  
   }
}
?>


So, In the first section of the program, we are trying to get if it is a CATEGORY or PRODUCT page by calling 

$category = Mage::registry('current_category');         
$product  = Mage::registry('current_product');

We may use  $product = Mage::registry('product'); instead of $product = Mage::registry('current_product'); 

IF it is a Category page, $category would have a non-null value. Similarly, when it is a product page, $product variable would have a non-null value. 

To determine CMS and other pages like Cart, Checkout etc, we need to catch the route, controller name and action name. So we use the following ::

$route =  Mage::app()->getFrontController()
                     ->getRequest()->getRouteName();
$controller = Mage::app()->getRequest()->getControllerName(); 
$action = Mage::app()->getRequest()->getActionName(); 

For CMS pages, we get page ID/Identifier by calling this :: 
$identifier = Mage::getSingleton('cms/page')->getIdentifier();

There is another Magento fundtion called getFullActionName() which returns full action name which comprises of route name, controller name and action name. 

$fullActionName = Mage::app()->getFrontController()
                             ->getAction()
                             ->getFullActionName();

So, on "Contacts" page, full action name is 'contacts_index_index' where 'contacts' is the front name, first 'index' means 'indexController' controller and second 'index' refers to 'indexAction' function/method defined within that controller PHP file.

Some full action name examples given below. From there, we can easily identify the Front/Route name, Controller Name and Action Name.

checkout_cart_index                => Cart Page
checkout_onepage_index         => Checkout Page
checkout_onepage_success    => Checkout Success Page
customer_account_login         => Customer Login
customer_account_logoutSuccess => Customer Logout 
customer_account_create      => Registration Page
customer_account_index        => Customer Dashboard
wishlist_index_index           => My Wishlist
cms_page_view                              => any CMS page
contacts_index_index             => Contacts page 
catalog_seo_sitemap_category  => SiteMap Catalog
catalogsearch_term_popular        => Search Term Page
catalogsearch_advanced_index => Advanced Search

Hope this helps.

Tuesday, April 05, 2016

PHP and MongoDB - I

In our previous Article, we had seen some basic commands of MongoDB in action. Now, we would connect our PHP to MongoDB. 

We already know how to install MongoDB driver for PHP and how to install MongoDB as Windows Services. Next in our PHP file, we start connecting to MongoDB as shown below.

<?php
ini_set('display_errors', 1);

// Let's connect without Authorization
$mongo = new MongoClient();

// GET Connections
$mongoConnections = $mongo->getConnections();

// IF Connection Found
if (false === empty($mongoConnections)) 

   // LIST Databases
   $dbs = $mongo->listDBs();
   
   // Show List of DataBases
   echo "<b>Showing All the DB Names :</b> ";
   foreach($dbs['databases'] as $d)
   {
     $dn = $d['name'];
     echo "<br>Database Name : $dn" ;
 
     // Select the database
     $p = $mongo->{$dn};
 
     // Get all the Collections
     $c = $p->listCollections();
     echo ", Collections are : ";
     foreach($c as $n) 
     {
       echo "[$n] ";
     } 
   }
   
   // Select our Database within the 1
   // position within the $dbs array
   $db = $dbs['databases'][1]['name'];
   $db = $mongo->{$db};
   
   // Select a collection say "myDatabase"
   $col = $db->myDatabase;
   
   // get all records
   $records = $col->find();
   
   // Iterate and show
   foreach($records as $key=>$val)
   {
     echo "<br>First Name : " . $val['fname'] . 
          ", Last Name : " . $val['lname'] . 
          ", Roll : " . $val['roll'] . 
          ", Status : " . $val['status']  ;
   }
   
   // INSERT a new ROW in that Collection
   // Insert a Document to Collection
   $arr =  array('fname' => 'Amiya', 'lname' => 'Sengupta', 
                 'roll' => '33', 'status' => '4');
   $col->insert($arr);
   
   
   // If we want to create a New Collection, JUST
   // use its name, Mongo will automatically create it
   $col = $db->PassedStudents;
   
   // Insert a Document to Collection
   $arr =  array('fname' => 'Adhuna', 'lname' => 'Muland', 
                 'roll' => '10', 'status' => '3');
   $result = $col->insert($arr);
   
   // Check if INSERT was successful
   if($result['ok'] != 1)
    echo " INSERT ERROR";
   
   // CREATE A NEW COLLECTION called 'Publisher'
   $publisher = $db->createCollection(
      "publisher",
       array(
        'capped' => true,
        'size' => 10*1024,
        'max' => 10
      )
   );
   
   // Insert some records in Publisher
   $arr =  array(
           'name' => 'General Publisher', 
           'address' => '10, Peter Road, Zip 2343', 
           'phone' => '9876543210');
   $result = $publisher->insert($arr);   
   

else 
{
   // Not connected
   echo "MongoDB not connected";
}
?>

The above code is quite self-explanatory. We have the MongoClient object available with us. This object helps us to interact with MongoDb database.

Some points which need to be noticed here are : 
1. listDBs() gets all the Database available. This is equivalent to "show dbs" command in Mongo Shell.
2. listCollections() gets all the collections available within the database. 
3. find() is used to find all records within a collection. The find() method returns a MongoCursor object 
4. insert() is used to INSERT record into collection

Check the output below :: 




In our next article, we'll see various ways of querying Mongo.