Monday, June 3, 2013

how to integrate paypal adaptive chained delay payment in php

create a paypalplatform.php
paste below code :
--------------------------------------------------------------------------------------------------------------

<?php
 /********************************************
 PayPal Adaptive Payments API Module
 
 Defines all the global variables and the wrapper functions
 ********************************************/
 define('PROXY_HOST', '127.0.0.1');
 define('PROXY_PORT' , '808');

 define('Env', 'sandbox');

 //------------------------------------
 // PayPal API Credentials
 // Replace <API_USERNAME> with your API Username
 // Replace <API_PASSWORD> with your API Password
 // Replace <API_SIGNATURE> with your Signature
 //------------------------------------
 define('API_UserName','tesssstneeraj_api1.techwave.com');
 define('API_Password','LAPBXMsssFLSBJE6YNA');
 define('API_Signature','AFcWxV21Css7fd0v3bYYYRCpSSRl31Ad8Viro.7-xWcAo2Kkq0koTD7rFO');
 // AppID is preset for sandbox use
 //   If your application goes live, you will be assigned a value for the live
 //   environment by PayPal as part of the live onboarding process.
 define('API_AppID','APP-80W284485P519543T');

 if (Env == "sandbox")
 {

  define('API_Endpoint','https://svcs.sandbox.paypal.com/AdaptivePayments');
 }
 else
 {
  define('API_Endpoint','https://svcs.paypal.com/AdaptivePayments');
}

 define('USE_PROXY',false);

 if (session_id() == "")
  session_start();

 function generateCharacter () {
  $possible = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
  return $char;
 }

 function generateTrackingID () {
 $GUID = generateCharacter().generateCharacter().generateCharacter();
 $GUID .=generateCharacter().generateCharacter().generateCharacter();
 $GUID .=generateCharacter().generateCharacter().generateCharacter();
 return $GUID;
 }

 /*
 '-------------------------------------------------------------------------------
 ' Purpose: Prepares the parameters for the Refund API Call.
 '   The API credentials used in a Pay call can make the Refund call
 '   against a payKey, or a tracking id, or to specific receivers of a payKey or
 '   a tracking id that resulted from the Pay call.
 '
 '   A receiver itself with its own API credentials can make a Refund call against
 '   the transactionId corresponding to their transaction.
 '   The API credentials used in a Pay call cannot use transactionId to issue a refund
 '   for a transaction for which they themselves were not the receiver.
 '
 '   If you do specify specific receivers, you must provide the amounts as well.
 '   If you specify a transactionId, then only the receiver of that transactionId
 '   is affected. Therefore the receiverEmailArray and receiverAmountArray should
 '   have 1 entry each if you do want to give a partial refund.
 ' Inputs:
 '
 ' Conditionally Required:
 '  One of the following:  payKey or trackingId or trasactionId or
 '                         (payKey and receiverEmailArray and receiverAmountArray) or
 '                         (trackingId and receiverEmailArray and receiverAmountArray)
 '                         or (transactionId and receiverEmailArray
 '                         and receiverAmountArray)
 ' Returns:
 '  The NVP Collection object of the Refund call response.
 '------------------------------------------------------------------------------------
 */
 function CallRefund( $payKey, $transactionId, $trackingId,
          $receiverEmailArray, $receiverAmountArray )
 {
  /* Gather the information to make the Refund call.
   The variable nvpstr holds the name-value pairs.
  */

  $nvpstr = "";

  // conditionally required fields
  if ("" != $payKey)
  {
   $nvpstr = "payKey=" . urlencode($payKey);
   if (0 != count($receiverEmailArray))
   {
    reset($receiverEmailArray);
    while (list($key, $value) = each($receiverEmailArray))
    {
     if ("" != $value)
     {
      $nvpstr .= "&receiverList.receiver(" . $key . ").email=" . urlencode($value);
     }
    }
   }
   if (0 != count($receiverAmountArray))
   {
    reset($receiverAmountArray);
    while (list($key, $value) = each($receiverAmountArray))
    {
     if ("" != $value)
     {
      $nvpstr .= "&receiverList.receiver(" . $key . ").amount=" . urlencode($value);
     }
    }
   }
  }
  elseif ("" != $trackingId)
  {
   $nvpstr = "trackingId=" . urlencode($trackingId);
   if (0 != count($receiverEmailArray))
   {
    reset($receiverEmailArray);
    while (list($key, $value) = each($receiverEmailArray))
    {
     if ("" != $value)
     {
      $nvpstr .= "&receiverList.receiver(" . $key . ").email=" . urlencode($value);
     }
    }
   }
   if (0 != count($receiverAmountArray))
   {
    reset($receiverAmountArray);
    while (list($key, $value) = each($receiverAmountArray))
    {
     if ("" != $value)
     {
      $nvpstr .= "&receiverList.receiver(" . $key . ").amount=" . urlencode($value);
     }
    }
   }
  }
  elseif ("" != $transactionId)
  {
   $nvpstr = "transactionId=" . urlencode($transactionId);
   // the caller should only have 1 entry in the email and amount arrays
   if (0 != count($receiverEmailArray))
   {
    reset($receiverEmailArray);
    while (list($key, $value) = each($receiverEmailArray))
    {
     if ("" != $value)
     {
      $nvpstr .= "&receiverList.receiver(" . $key . ").email=" . urlencode($value);
     }
    }
   }
   if (0 != count($receiverAmountArray))
   {
    reset($receiverAmountArray);
    while (list($key, $value) = each($receiverAmountArray))
    {
     if ("" != $value)
     {
      $nvpstr .= "&receiverList.receiver(" . $key . ").amount=" . urlencode($value);
     }
    }
   }
  }

  /* Make the Refund call to PayPal */
  $resArray = hash_call("Refund", $nvpstr);

  /* Return the response array */
  return $resArray;
 }

 /*
 '------------------------------------------------------------------------------------
 ' Purpose: Prepares the parameters for the PaymentDetails API Call.
 '   The PaymentDetails call can be made with either
 '   a payKey, a trackingId, or a transactionId of a previously successful Pay call.
 ' Inputs:
 '
 ' Conditionally Required:
 '  One of the following:  payKey or transactionId or trackingId
 ' Returns:
 '  The NVP Collection object of the PaymentDetails call response.
 '------------------------------------------------------------------------------------
 */
 function CallPaymentDetails( $payKey, $transactionId, $trackingId )
 {
  /* Gather the information to make the PaymentDetails call.
   The variable nvpstr holds the name-value pairs.
  */

  $nvpstr = "";

  // conditionally required fields
  if ("" != $payKey)
  {
   $nvpstr = "payKey=" . urlencode($payKey);
  }
  elseif ("" != $transactionId)
  {
   $nvpstr = "transactionId=" . urlencode($transactionId);
  }
  elseif ("" != $trackingId)
  {
   $nvpstr = "trackingId=" . urlencode($trackingId);
  }

  /* Make the PaymentDetails call to PayPal */
  $resArray = hash_call("PaymentDetails", $nvpstr);

  /* Return the response array */
  return $resArray;
 }

 /*
 '------------------------------------------------------------------------------------
 ' Purpose: Prepares the parameters for the Pay API Call.
 ' Inputs:
 '
 ' Required:
 '
 ' Optional:
 '
 ' Returns:
 '  The NVP Collection object of the Pay call response.
 '------------------------------------------------------------------------------------
 */
 function CallPay( $actionType, $cancelUrl, $returnUrl, $currencyCode,
     $receiverEmailArray, $receiverAmountArray, $receiverPrimaryArray,
     $receiverInvoiceIdArray, $feesPayer, $ipnNotificationUrl, $memo,
     $pin, $preapprovalKey, $reverseAllParallelPaymentsOnError,
     $senderEmail, $trackingId )
 {

  /* Gather the information to make the Pay call.
   The variable nvpstr holds the name-value pairs.
  */



  // required fields
  $nvpstr = "actionType=" . urlencode($actionType) . "&currencyCode=";
  $nvpstr .= urlencode($currencyCode) . "&returnUrl=";
  $nvpstr .= urlencode($returnUrl) . "&cancelUrl=" . urlencode($cancelUrl);

  if (0 != count($receiverAmountArray))
  {

   reset($receiverAmountArray);

   while (list($key, $value) = each($receiverAmountArray))
   {
    if ("" != $value)
    {
     $nvpstr .= "&receiverList.receiver(" . $key . ").amount=" . urlencode($value);
    }
   }
  }

  if (0 != count($receiverEmailArray))
  {
   reset($receiverEmailArray);
   while (list($key, $value) = each($receiverEmailArray))
   {
    if ("" != $value)
    {
     $nvpstr .= "&receiverList.receiver(" . $key . ").email=" . urlencode($value);
    }
   }
  }

  if (0 != count($receiverPrimaryArray))
  {
   reset($receiverPrimaryArray);
   while (list($key, $value) = each($receiverPrimaryArray))
   {
    if ("" != $value)
    {
     $nvpstr = $nvpstr . "&receiverList.receiver(" . $key . ").primary=" .
             urlencode($value);
    }
   }
  }

  if (0 != count($receiverInvoiceIdArray))
  {
   reset($receiverInvoiceIdArray);
   while (list($key, $value) = each($receiverInvoiceIdArray))
   {
    if ("" != $value)
    {
     $nvpstr = $nvpstr . "&receiverList.receiver(" . $key . ").invoiceId=" .
               urlencode($value);
    }
   }
  }

  // optional fields
  if ("" != $feesPayer)
  {
   $nvpstr .= "&feesPayer=" . urlencode($feesPayer);
  }

  if ("" != $ipnNotificationUrl)
  {
   $nvpstr .= "&ipnNotificationUrl=" . urlencode($ipnNotificationUrl);
  }

  if ("" != $memo)
  {
   $nvpstr .= "&memo=" . urlencode($memo);
  }

  if ("" != $pin)
  {
   $nvpstr .= "&pin=" . urlencode($pin);
  }

  if ("" != $preapprovalKey)
  {
   $nvpstr .= "&preapprovalKey=" . urlencode($preapprovalKey);
  }

  if ("" != $reverseAllParallelPaymentsOnError)
  {
   $nvpstr .= "&reverseAllParallelPaymentsOnError=";
   $nvpstr .= urlencode($reverseAllParallelPaymentsOnError);
  }

  if ("" != $senderEmail)
  {
   $nvpstr .= "&senderEmail=" . urlencode($senderEmail);
  }

  if ("" != $trackingId)
  {
   $nvpstr .= "&trackingId=" . urlencode($trackingId);
  }

  /* Make the Pay call to PayPal */
$resArray = hash_call("Pay", $nvpstr);

  /* Return the response array */
  return $resArray;
 }

 /*
 '---------------------------------------------------------------------------
 ' Purpose: Prepares the parameters for the PreapprovalDetails API Call.
 ' Inputs:
 '

 ' Required:
 '  preapprovalKey:A preapproval key that identifies the agreement
 '                 resulting from a previously successful Preapproval call.
 ' Returns:
 '  The NVP Collection object of the PreapprovalDetails call response.
 '---------------------------------------------------------------------------
 */
 function CallPreapprovalDetails( $preapprovalKey )
 {
  /* Gather the information to make the PreapprovalDetails call.
   The variable nvpstr holds the name-value pairs.
  */

  // required fields
  $nvpstr = "preapprovalKey=" . urlencode($preapprovalKey);

  /* Make the PreapprovalDetails call to PayPal */
  $resArray = hash_call("PreapprovalDetails", $nvpstr);

  /* Return the response array */
  return $resArray;
 }

 /*
 '---------------------------------------------------------------------------
 ' Purpose: Prepares the parameters for the Preapproval API Call.
 ' Inputs:
 '
 ' Required:
 '
 ' Optional:
 '
 ' Returns:
 '  The NVP Collection object of the Preapproval call response.
 '---------------------------------------------------------------------------
 */
 function CallPreapproval( $returnUrl, $cancelUrl, $currencyCode,
        $startingDate, $endingDate, $maxTotalAmountOfAllPayments,
        $senderEmail, $maxNumberOfPayments, $paymentPeriod, $dateOfMonth,
        $dayOfWeek, $maxAmountPerPayment, $maxNumberOfPaymentsPerPeriod, $pinType )
 {
  /* Gather the information to make the Preapproval call.
   The variable nvpstr holds the name-value pairs.
  */

  // required fields
  $nvpstr = "returnUrl=" . urlencode($returnUrl) . "&cancelUrl=" . urlencode($cancelUrl);
  $nvpstr .= "&currencyCode=" . urlencode($currencyCode) . "&startingDate=";
  $nvpstr .= urlencode($startingDate) . "&endingDate=" . urlencode($endingDate);
  $nvpstr .= "&maxTotalAmountOfAllPayments=" . urlencode($maxTotalAmountOfAllPayments);

  // optional fields
  if ("" != $senderEmail)
  {
   $nvpstr .= "&senderEmail=" . urlencode($senderEmail);
  }

  if ("" != $maxNumberOfPayments)
  {
   $nvpstr .= "&maxNumberOfPayments=" . urlencode($maxNumberOfPayments);
  }

  if ("" != $paymentPeriod)
  {
   $nvpstr .= "&paymentPeriod=" . urlencode($paymentPeriod);
  }

  if ("" != $dateOfMonth)
  {
   $nvpstr .= "&dateOfMonth=" . urlencode($dateOfMonth);
  }

  if ("" != $dayOfWeek)
  {
   $nvpstr .= "&dayOfWeek=" . urlencode($dayOfWeek);
  }

  if ("" != $maxAmountPerPayment)
  {
   $nvpstr .= "&maxAmountPerPayment=" . urlencode($maxAmountPerPayment);
  }

  if ("" != $maxNumberOfPaymentsPerPeriod)
  {
   $nvpstr .= "&maxNumberOfPaymentsPerPeriod=" . urlencode($maxNumberOfPaymentsPerPeriod);
  }

  if ("" != $pinType)
  {
   $nvpstr .= "&pinType=" . urlencode($pinType);
  }

  /* Make the Preapproval call to PayPal */
  $resArray = hash_call("Preapproval", $nvpstr);

  /* Return the response array */
  return $resArray;
 }

 /**
   '----------------------------------------------------------------------------------
   * hash_call: Function to perform the API call to PayPal using API signature
   * @methodName is name of API method.
   * @nvpStr is nvp string.
   * returns an associative array containing the response from the server.
   '----------------------------------------------------------------------------------
 */
 function hash_call($methodName, $nvpStr)
 {
  //declaring of global variables
  global $API_Endpoint, $API_UserName, $API_Password, $API_Signature, $API_AppID;
  global $USE_PROXY, $PROXY_HOST, $PROXY_PORT;

  $API_Endpoint .= API_Endpoint."/" .$methodName;

  //setting the curl parameters.
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL,$API_Endpoint);
  curl_setopt($ch, CURLOPT_VERBOSE, 1);

  //turning off the server and peer verification(TrustManager Concept).
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);

  curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
  curl_setopt($ch, CURLOPT_POST, 1);

  // Set the HTTP Headers
  curl_setopt($ch, CURLOPT_HTTPHEADER,  array(
  'X-PAYPAL-REQUEST-DATA-FORMAT: NV',
  'X-PAYPAL-RESPONSE-DATA-FORMAT: NV',
  'X-PAYPAL-SECURITY-USERID: ' . API_UserName,
  'X-PAYPAL-SECURITY-PASSWORD: ' .API_Password,
  'X-PAYPAL-SECURITY-SIGNATURE: ' . API_Signature,
  'X-PAYPAL-SERVICE-VERSION: 1.3.0',
  'X-PAYPAL-APPLICATION-ID: ' . API_AppID
 
  ));

    //if USE_PROXY constant set to TRUE in Constants.php,
    //then only proxy will be enabled.
  //Set proxy name to PROXY_HOST and port number to PROXY_PORT in constants.php
  if($USE_PROXY)
   curl_setopt ($ch, CURLOPT_PROXY, $PROXY_HOST. ":" . $PROXY_PORT);

  // RequestEnvelope fields
  $detailLevel = urlencode("ReturnAll"); // See DetailLevelCode in the WSDL
                                         // for valid enumerations
  $errorLanguage = urlencode("en_US");  // This should be the standard RFC
                                        // 3066 language identification tag,
                                        // e.g., en_US

 // NVPRequest for submitting to server
 $nvpreq = "requestEnvelope.errorLanguage=$errorLanguage&requestEnvelope";
 $nvpreq .= "detailLevel=$detailLevel&$nvpStr";

  //setting the nvpreq as POST FIELD to curl
  curl_setopt($ch, CURLOPT_POSTFIELDS, $nvpreq);

  //getting response from server
  $response = curl_exec($ch);

  //converting NVPResponse to an Associative Array
  $nvpResArray=deformatNVP($response);

  $nvpReqArray=deformatNVP($nvpreq);

  $_SESSION['nvpReqArray']=$nvpReqArray;

  if (curl_errno($ch))
  {
   // moving to display page to display curl errors
     $_SESSION['curl_error_no']=curl_errno($ch) ;
     $_SESSION['curl_error_msg']=curl_error($ch);

     //Execute the Error handling module to display errors.
  }
  else
  {
    //closing the curl
    curl_close($ch);
  }

  return $nvpResArray;
 }

 /*'----------------------------------------------------------------------------
  Purpose: Redirects to PayPal.com site.
  Inputs:  $cmd is the querystring
  Returns:
 -------------------------------------------------------------------------------
 */
 function RedirectToPayPal ( $cmd )
 {
  // Redirect to paypal.com here
  global $Env;

  $payPalURL = "";

  if (Env == "sandbox")
  {
   $payPalURL = "https://www.sandbox.paypal.com/webscr?" . $cmd;
  }
  else
  {
   $payPalURL = "https://www.paypal.com/webscr?" . $cmd;
  }

  header("Location: ".$payPalURL);
 }


 /*'----------------------------------------------------------------------------
   * This function will take NVPString and convert it to an Associative Array
   * and then will decode the response.
   * It is useful to search for a particular key and display arrays.
   * @nvpstr is NVPString.
   * @nvpArray is Associative Array.
    ----------------------------------------------------------------------------
   */



 function deformatNVP($nvpstr)
 {
  $intial=0;
  $nvpArray = array();

  while(strlen($nvpstr))
  {
   //postion of Key
   $keypos= strpos($nvpstr,'=');
   //position of value
   $valuepos = strpos($nvpstr,'&') ? strpos($nvpstr,'&'): strlen($nvpstr);

   /*getting the Key and Value values and storing in a Associative Array*/
   $keyval=substr($nvpstr,$intial,$keypos);
   $valval=substr($nvpstr,$keypos+1,$valuepos-$keypos-1);
   //decoding the respose
   $nvpArray[urldecode($keyval)] =urldecode( $valval);
   $nvpstr=substr($nvpstr,$valuepos+1,strlen($nvpstr));
      }
  return $nvpArray;
 }
?>

--------------------------------------------------------------------------------------------------------------
create paymment redirect page

<?php
require_once ("paypalplatform.php");

// Request specific required fields
$actionType = "PAY_PRIMARY";
$cancelUrl = 'your cancel url';
$returnUrl = 'your return url';
$currencyCode = "USD";

// A chained payment can be made with 1 primary receiver and between 1 and 5 secondary
// receivers
// TODO - Specify the receiver emails
//        Remove or set to an empty string the array entries for receivers that you
//        do not have
//echo   $this->Salar_Sum_Price ;

$receiverEmailArray = array(
 'primary email id',
  'secondary email-1',
  'secondary email-1'
  );

// TODO - Specify the receiver amounts as the amount of money, for example, '5' or '5.55'
//        Remove or set to an empty string the array entries for receivers that you
//        do not have
$receiverAmountArray = array(
  'primaary amount',
  'sendary amout1',
  'sendary amout2'
  );

// TODO - Set ONLY 1 receiver in the array to 'true' as the primary receiver, and set the
//        other receivers corresponding to those indicated in receiverEmailArray to
//        'false'. Make sure that you do NOT specify more values in this array than
//        in the receiverEmailArray.
$receiverPrimaryArray = array(
  ''
  );

// TODO - Set invoiceId to uniquely identify the transaction associated with each receiver
//        Set the array entries with value for receivers that you have each of the array
//        Values must be unique across all Pay calls made by the caller's API credentials
$receiverInvoiceIdArray = array(
  ''
  );

// Request specific optional fields
//   Provide a value for each field that you want to include in the request;
//   if left as an empty string, the field will not be passed in the request
$senderEmail = ""; // TODO - If you are executing the Pay call against a preapprovalKey,
                   // you should set senderEmail
                   // It is not required if the web approval flow immediately
                   // follows this Pay call
$feesPayer = "";
$ipnNotificationUrl = "";
$memo = ""; // maxlength is 1000 characters
$pin = ""; // TODO - If you are executing the Pay call against an existing preapproval
           // that requires a pin, then you must set this
$preapprovalKey = ""; // TODO - If you are executing the Pay call against an existing
                      // preapproval, set the preapprovalKey here
$reverseAllParallelPaymentsOnError = ""; // TODO - Do not specify for chained payment
$trackingId = generateTrackingID(); // generateTrackingID function is found
                                    // in paypalplatform.php


$resArray = CallPay($actionType, $cancelUrl, $returnUrl, $currencyCode,
      $receiverEmailArray, $receiverAmountArray, $receiverPrimaryArray,
      $receiverInvoiceIdArray, $feesPayer, $ipnNotificationUrl, $memo,
      $pin, $preapprovalKey, $reverseAllParallelPaymentsOnError,
      $senderEmail, $trackingId
);


$ack = strtoupper($resArray["responseEnvelope.ack"]);
if($ack=="SUCCESS")
{
 if ("" == $preapprovalKey)
 {
  // redirect for web approval flow

  $cmd = "cmd=_ap-payment&paykey=" . urldecode($resArray["payKey"]);
  //$cmd = "cmd=_ap-preapproval&preapprovalkey=" . urldecode($resArray["preapprovalKey"]);

  RedirectToPayPal ( $cmd );
 }
 else
 {
  // The Pay API call was made for an existing preapproval agreement, so no approval
  // flow follows.
  // payKey is the key that you can use to identify the result from this Pay call.
  $payKey = urldecode($resArray["payKey"]);
  // paymentExecStatus is the status of the payment
  $paymentExecStatus = urldecode($resArray["paymentExecStatus"]);
  // Note that in order to get the exact status of the transactions resulting from
  // a Pay API call, you should make the PaymentDetails API call for the payKey
 }
}
else
{
 //Display a user-friendly Error on the page using any of the following error information
 //returned by PayPal.
 //TODO - There can be more than 1 error, so check for "error(1).errorId",
 //       then "error(2).errorId", and so on until you find no more errors.
 $ErrorCode = urldecode($resArray["error(0).errorId"]);
 $ErrorMsg = urldecode($resArray["error(0).message"]);
 $ErrorDomain = urldecode($resArray["error(0).domain"]);
 $ErrorSeverity = urldecode($resArray["error(0).severity"]);
 $ErrorCategory = urldecode($resArray["error(0).category"]);

 echo "Pay API call failed. ";
 echo "Detailed Error Message: " . $ErrorMsg;
 echo "Error Code: " . $ErrorCode;
 echo "Error Severity: " . $ErrorSeverity;
 echo "Error Domain: " . $ErrorDomain;
 echo "Error Category: " . $ErrorCategory;
}
?>

Wednesday, May 29, 2013

magento Grids and Forms in the Admin Panel example


In my humble opinion, creating new sections in the Admin Panel are a tad bit more complicated than creating new features on the frontend. Hopefully, this post will help get you a few steps closer to being able to understand and create adminhtml grids and forms.
The first thing you need to do is create a menu item to get to your new grid, then you begin the exciting journey into some Adminhtml action.
The Config
How about I just throw the whole stinkin' config.xml file out here right off the bat:
?

































































































<config>
    <modules>
        <Super_Awesome>
            <version>..</version>
        </Super_Awesome>
    </modules>
    <adminhtml>
        <!-- The <layout> updates allow us to define our block layouts in a seperate file so are aren't messin' with the magento layout files.  -->
        <layout>
            <updates>
                <awesome>
                    <file>awesome.xml</file>
                </awesome>
            </updates>
        </layout>
        <!-- The <acl> section is for access control. Here we define the pieces where access can be controlled within a role. -->
        <acl>
            <resources>
                <admin>
                    <children>
                        <awesome>
                            <title>Awesome Menu Item</title>
                            <children>
                                <example translate="title" module="awesome">
                                    <title>Example Menu Item</title>
                                </example>
                            </children>
                        </awesome>
                    </children>
                </admin>
            </resources>
        </acl>
    </adminhtml>
    <admin>
        <!--
            Here we are telling the Magento router to look for the controllers in the Super_Awesome_controllers_Adminhtml before we look in the
            Mage_Adminhtml module for all urls that begin with /admin/controller_name
         -->
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <awesome before="Mage_Adminhtml">Super_Awesome_Adminhtml</awesome>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>

    <global>
        <models>
            <awesome>
                <class>Super_Awesome_Model</class>
                <resourceModel>awesome_mysql</resourceModel>
            </awesome>
             <awesome_mysql>
                <class>Super_Awesome_Model_Mysql</class>
                <entities>
                    <example>
                        <table>Super_Awesome_example</table>
                    </example>
                </entities>
            </awesome_mysql>
        </models>

        <resources>
            <awesome_setup>
                <setup>
                    <module>Super_Awesome</module>
                </setup>
                <connection>
                    <use>core_setup</use>
                </connection>
            </awesome_setup>
            <awesome_write>
                <connection>
                    <use>core_write</use>
                </connection>
            </awesome_write>
            <awesome_read>
                <connection>
                    <use>core_read</use>
                </connection>
            </awesome_read>
        </resources>

        <blocks>
            <awesome>
                <class>Super_Awesome_Block</class>
            </awesome>
        </blocks>
        <helpers>
            <awesome>
                <class>Super_Awesome_Helper</class>
            </awesome>
        </helpers>
    </global>
</config>
Some things of note about the config.xml:
  1. There is a layout file defined (awesome.xml)
  2. There is a change to the adminhtml router that tells the route to look in our module before looking into Mage_Adminhtml
  3. Everything else is pretty straight-forward (models, resource models, collections, setup, blocks, helpers...)
The Layout
Before I forget, here is the contents of the layout file (design/adminhtml/default/default/layout/awesome.xml):
?















<?xml version=“1.0”?>

<layout>
    <adminhtml_example_index>
        <reference name="content">
            <block type="awesome/adminhtml_example" name="example" />
        </reference>
    </adminhtml_example_index>

     <adminhtml_example_edit>
        <reference name="content">
            <block type="awesome/adminhtml_example_edit" name="example_edit" />
        </reference>
    </adminhtml_example_edit>

</layout>
We don't need to define much in here. The reason I think that Admin Panel coding is a little more complicated is because there is a lot of things that happen behind the scenes that are not driven by the layout as we normally see it.
Install Script
For testing/example purposes, I also created an install script to create a table and load up some data. In the sql/awesome_setup/
mysql4-install-0.1.0.php file, I have:

























<?php

$installer = $this;

$installer->startSetup();

$installer->run("

-- DROP TABLE IF EXISTS {$this->getTable('super_awesome_example')};
CREATE TABLE {$this->getTable('super_awesome_example')} (
  `id` int() unsigned NOT NULL auto_increment,
  `name` varchar() NOT NULL,
  `description` varchar() NOT NULL,
  `other` varchar() NOT NULL,
  PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=latin AUTO_INCREMENT= ;

INSERT INTO {$this->getTable('super_awesome_example')} (name, description, other) values ('Example ', 'Example One Description', 'This first example is reall awesome.');
INSERT INTO {$this->getTable('super_awesome_example')} (name, description, other) values ('Example ', 'Example Two Description', 'This second example is reall awesome.');
INSERT INTO {$this->getTable('super_awesome_example')} (name, description, other) values ('Example ', 'Example Three Description', 'This third example is reall awesome.');
INSERT INTO {$this->getTable('super_awesome_example')} (name, description, other) values ('Example ', 'Example Four Description', 'This fourth example is reall awesome.');

");

$installer->endSetup();
The Models
In this example, I am going to assume you know how to create a model, its resource model, and its collection model. I created the following classes:
Super_Awesome_Model_Example
Super_Awesome_Model_Mysql_Example
Super_Awesome_Model_Mysql_Example_Collection
The Controller
Hopefully, we all know what a controller does, so I won't explain that part of the MVC pattern. The adminhtml controllers generally provide actions to do basic CRUD operations on the model. In ours, you will find the following actions:
  • index - Shows the grid.
  • edit - Shows the edit/new form.
  • save - Saves the form data.
  • delete - Deletes the model.
  • new - Forwards on to the edit action
There really isn't anything crazy going on here, so I would just take a few minutes to read through the code and get an understanding of what each function does (and does not do):
?












































































































<?php

class Super_Awesome_Adminhtml_ExampleController extends Mage_Adminhtml_Controller_Action
{

    public function indexAction()
    {

        $this->loadLayout();
        $this->renderLayout();
    }

    public function newAction()
    {
        $this->_forward('edit');
    }

    public function editAction()
    {
        $id = $this->getRequest()->getParam('id', null);
        $model = Mage::getModel('awesome/example');
        if ($id) {
            $model->load((int) $id);
            if ($model->getId()) {
                $data = Mage::getSingleton('adminhtml/session')->getFormData(true);
                if ($data) {
                    $model->setData($data)->setId($id);
                }
            } else {
                Mage::getSingleton('adminhtml/session')->addError(Mage::helper('awesome')->__('Example does not exist'));
                $this->_redirect('*/*/');
            }
        }
        Mage::register('example_data', $model);

        $this->loadLayout();
        $this->getLayout()->getBlock('head')->setCanLoadExtJs(true);
        $this->renderLayout();
    }

    public function saveAction()
    {
        if ($data = $this->getRequest()->getPost())
        {
            $model = Mage::getModel('awesome/example');
            $id = $this->getRequest()->getParam('id');
            if ($id) {
                $model->load($id);
            }
            $model->setData($data);

            Mage::getSingleton('adminhtml/session')->setFormData($data);
            try {
                if ($id) {
                    $model->setId($id);
                }
                $model->save();

                if (!$model->getId()) {
                    Mage::throwException(Mage::helper('awesome')->__('Error saving example'));
                }

                Mage::getSingleton('adminhtml/session')->addSuccess(
Mage::helper('awesome')->__('Example was successfully saved.'));
                Mage::getSingleton('adminhtml/session')->setFormData(false);

                // The following line decides if it is a "save" or "save and 
 continue"
                if ($this->getRequest()->getParam('back')) {
                    $this->_redirect('*/*/edit', array('id' => $model->getId()));
                } else {
                    $this->_redirect('*/*/');
                }

            } catch (Exception $e) {
                Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
                if ($model && $model->getId()) {
                    $this->_redirect('*/*/edit', array('id' => $model->getId()));
                } else {
                    $this->_redirect('*/*/');
                }
            }

            return;
        }
        Mage::getSingleton('adminhtml/session')->addError(Mage::helper('awesome')->__('No data found to save'));
        $this->_redirect('*/*/');
    }

    public function deleteAction()
    {
        if ($id = $this->getRequest()->getParam('id')) {
            try {
                $model = Mage::getModel('awesome/example');
                $model->setId($id);
                $model->delete();
                Mage::getSingleton('adminhtml/session')->addSuccess(
Mage::helper('awesome')->__('The example has been deleted.'));
                $this->_redirect('*/*/');
                return;
            }
            catch (Exception $e) {
                Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
                $this->_redirect('*/*/edit', array('id' => $this->getRequest()->getParam('id')));
                return;
            }
        }
        Mage::getSingleton('adminhtml/session')->addError(Mage::helper('adminhtml')->__('Unable to find the example to delete.'));
        $this->_redirect('*/*/');
    }

}
The Grid Block
Here is where it starts getting a touch tricky. When you originally click on the menu item to see the grid of examples, you are going to the "indexAction" in the controller and simply loading and rendering the layout. This means that you will probably have something halfway useful to see in the awesome.xml layout file. We see that there is only one block defined for that handle, and that block is: 'awesome/adminhtml_example'. This block extends Mage_Adminhtml_Block_Widget_Grid_Container which which tells us that our block (Super_Awesome_Block_Adminhtml_Example) will be be a container for a grid. What does that mean? This container will provide a few buttons at the top and automagically define the grid as a child block of itself. Below I will show you the entire contents of our container, and the piece that builds the name of the grid block (which is in the parent Mage_Adminhtml_Block_Widget_Grid_Container.
Super_Awesome_Block_Adminhtml_Example:
?













<?php

class Super_Awesome_Block_Adminhtml_Example extends Mage_Adminhtml_Block_Widget_Grid_Container
{
    protected $_addButtonLabel = 'Add New Example';

    public function __construct()
    {
        parent::__construct();
        $this->_controller = 'adminhtml_example';
        $this->_blockGroup = 'awesome';
        $this->_headerText = Mage::helper('awesome')->__('Examples');
    }
}
?






protected function _prepareLayout()
   {
       $this->setChild( 'grid',
           $this->getLayout()->createBlock( $this->_blockGroup.'/' . $this->_controller . '_grid',
           $this->_controller . '.grid')->setSaveParametersInSession(true) );
       return parent::_prepareLayout();
   }
Now that we have the container we need to build our grid (Super_Awesome_Block_Adminhtml_Example_Grid):
?






















































<?php

class Super_Awesome_Block_Adminhtml_Example_Grid extends Mage_Adminhtml_Block_Widget_Grid
{
    public function __construct()
    {
        parent::__construct();
        $this->setId('example_grid');
        $this->setDefaultSort('id');
        $this->setDefaultDir('desc');
        $this->setSaveParametersInSession(true);
    }

    protected function _prepareCollection()
    {
        $collection = Mage::getModel('awesome/example')->getCollection();
        $this->setCollection($collection);
        return parent::_prepareCollection();
    }

    protected function _prepareColumns()
    {
        $this->addColumn('id', array(
            'header'    => Mage::helper('awesome')->__('ID'),
            'align'     =>'right',
            'width'     => 'px',
            'index'     => 'id',
        ));

        $this->addColumn('name', array(
            'header'    => Mage::helper('awesome')->__('Name'),
            'align'     =>'left',
            'index'     => 'name',
        ));

        $this->addColumn('description', array(
            'header'    => Mage::helper('awesome')->__('Description'),
            'align'     =>'left',
            'index'     => 'description',
        ));

        $this->addColumn('other', array(
            'header'    => Mage::helper('awesome')->__('Other'),
            'align'     => 'left',
            'index'     => 'other',
        ));

        return parent::_prepareColumns();
    }

    public function getRowUrl($row)
    {
        return $this->getUrl('*/*/edit', array('id' => $row->getId()));
    }
}
The _prepareCollection() function gets the collection of data that will populate our grid, and the _prepareColumns() function maps that data into the specific columns. Keep in mind that the _prepareCollection() and the _prepareColumns() can be much more detailed/complicated than my example here, so don't be afraid to try crazy things.
If you stop here, you should have a working grid.
The Forms
If you notice, the getRowUrl() on the grid returns back a url that maps to the editAction() in our controller. That is where were start the "form fun". The editAction() handles both the "edit" scenario and the "new" scenario for the model. It makes no difference to us since it is the same form for both.
The edit action maps to a handle in the awesome.xml layout file which simply defines the block: awesome/adminhtml_example_edit. If we take a look at that block we will see the following code:
?













































<?php

class Super_Awesome_Block_Adminhtml_Example_Edit extends Mage_Adminhtml_Block_Widget_Form_Container
{
    public function __construct()
    {
        parent::__construct();

        $this->_objectId = 'id';
        $this->_blockGroup = 'awesome';
        $this->_controller = 'adminhtml_example';
        $this->_mode = 'edit';

        $this->_addButton('save_and_continue', array(
                  'label' => Mage::helper('adminhtml')->__('Save And Continue Edit'),
                  'onclick' => 'saveAndContinueEdit()',
                  'class' => 'save',
        ), -);
        $this->_updateButton('save', 'label', Mage::helper('awesome')->__('Save Example'));

        $this->_formScripts[] = "
            function toggleEditor() {
                if (tinyMCE.getInstanceById('form_content') == null) {
                    tinyMCE.execCommand('mceAddControl', false, 'edit_form');
                } else {
                    tinyMCE.execCommand('mceRemoveControl', false, 'edit_form');
                }
            }

            function saveAndContinueEdit(){
                editForm.submit($('edit_form').action+'back/edit/');
            }
        ";
    }

    public function getHeaderText()
    {
        if (Mage::registry('example_data') && Mage::registry('example_data')->getId())
        {
            return Mage::helper('awesome')->__('Edit Example "%s"', $this->htmlEscape(Mage::registry('example_data')->getName()));
        } else {
            return Mage::helper('awesome')->__('New Example');
        }
    }

}
Just like the grid had a container, so does the form. We are just changing some labels on buttons and creating some javascript to handle the save scenarios. Below is the snippet of code in the parent container that builds the name of the block that will be rendered containing the actual form:
?






protected function _prepareLayout()
    {
        if ($this->_blockGroup && $this->_controller && $this->_mode) {
            $this->setChild('form', $this->getLayout()->createBlock($this->_blockGroup . '/' . $this->_controller . '_' . $this->_mode . '_form'));
        }
        return parent::_prepareLayout();
    }
Next/finally, we look at the actual form class; It's so awesome, you might faint when you see it:
?


























































<?php

class Super_Awesome_Block_Adminhtml_Example_Edit_Form extends Mage_Adminhtml_Block_Widget_Form
{
    protected function _prepareForm()
    {
        if (Mage::getSingleton('adminhtml/session')->getExampleData())
        {
            $data = Mage::getSingleton('adminhtml/session')->getExamplelData();
            Mage::getSingleton('adminhtml/session')->getExampleData(null);
        }
        elseif (Mage::registry('example_data'))
        {
            $data = Mage::registry('example_data')->getData();
        }
        else
        {
            $data = array();
        }

        $form = new Varien_Data_Form(array(
                'id' => 'edit_form',
                'action' => $this->getUrl('*/*/save', array('id' => $this->getRequest()->getParam('id'))),
                'method' => 'post',
                'enctype' => 'multipart/form-data',
        ));

        $form->setUseContainer(true);

        $this->setForm($form);

        $fieldset = $form->addFieldset('example_form', array(
             'legend' =>Mage::helper('awesome')->__('Example Information')
        ));

        $fieldset->addField('name', 'text', array(
             'label'     => Mage::helper('awesome')->__('Name'),
             'class'     => 'required-entry',
             'required'  => true,
             'name'      => 'name',
             'note'     => Mage::helper('awesome')->__('The name of the example.'),
        ));

        $fieldset->addField('description', 'text', array(
             'label'     => Mage::helper('awesome')->__('Description'),
             'class'     => 'required-entry',
             'required'  => true,
             'name'      => 'description',
        ));

        $fieldset->addField('other', 'text', array(
             'label'     => Mage::helper('awesome')->__('Other'),
             'class'     => 'required-entry',
             'required'  => true,
             'name'      => 'other',
        ));

        $form->setValues($data);

        return parent::_prepareForm();
    }
}
Did you faint?
The only thing not straight-forward here is the line: $form->setUseContainer(true);. This line is important because it is the line that actually causes the form renderer to output the surrounding <form> tags.