Accessing an External API with a Sugar Logic Action
Let us say we were building a new Action called "SetZipCodeAction" that uses the Yahoo geocoding API to get the zip code for a given street + city + state address.
Since the Yahoo geocoding API requires JSON requests and returns XML data, we will have to write both PHP and JavaScript code to make and interpret the requests. Because accessing external APIs in a browser is considered cross-site scripting, a local proxy will have to be used. We will also allow the street, city, state parameters to be passed in as formulas so the action could be used in more complex Dependencies.
First, we should add a new action that acts as the proxy for retrieving data from the Yahoo API. The easiest place to add that would be a custom action in the "Home" module. The file that will act as the proxy will be .custom/modules/Home/geocode.php. It will take in the parameters via a REST call, make the call to the Yahoo API, and return the result in JSON format.
geocode.php contents:
<?php
function getZipCode($street, $city, $state)
{
$appID = "6ruuUKjV34Fydi4TE.ca.I02rWh.9LTMPqQnSQo4QsCnjF5wIvyYRSXPIzqlDbI.jfE-";
$street = urlencode($street);
$city = urlencode($city);
$state = urlencode($state);
$base_url = "http://local.yahooapis.com/MapsService/V1/geocode?";
$params = "appid={$appID}&street={$street}&city={$city}&state={$state}";
//use file_get_contents to easily make the request
$response = file_get_contents($base_url . $params);
//The PHP XML parser is going to be overkill in this case, so just pull the zipcode with a regex.
preg_match('/\([\d-]*)\/', $response, $matches);
return $matches[1];
}
if (!empty($_REQUEST['execute']))
{
if (empty($_REQUEST['street']) || empty($_REQUEST['city']) || empty($_REQUEST['state']))
{
echo("Bad Request");
}
else
{
echo json_encode(array('zip' => getZipCode($_REQUEST['street'], $_REQUEST['city'], $_REQUEST['state'])));
}
}
?>
Next, we will need to map the geocode action to the geocode.php file. This is done by adding an action map to the Home Module. Create the file ./custom/modules/Home/action_file_map.php and add the following line of code:
<?php
$action_file_map['geocode'] = 'custom/modules/Home/geocode.php';
We are now ready to write our Action. Start by creating the file ./custom/include/Expressions/Actions/SetZipCodeAction.php. This file will use the proxy function directly from the PHP side and make an asynchronous call on the javascript side to the proxy.
SetZipCodeAction.php contents:
<?php
require_once "include/Expressions/Actions/AbstractAction.php";
class SetZipCodeAction extends AbstractAction
{
protected $target = "";
protected $streetExp = "";
protected $cityExp = "";
protected $stateExp = "";
function SetZipCodeAction($params)
{
$this->target = empty($params['target']) ? " " : $params['target'];
$this->streetExp = empty($params['street']) ? " " : $params['street'];
$this->cityExp = empty($params['city']) ? " " : $params['city'];
$this->stateExp = empty($params['state']) ? " " : $params['state'];
}
static function getJavascriptClass()
{
return "'($this->target}', '{$this->streetExp}', '{$this->cityExp}', '{$this->stateExp}')";
}
function fire(&$bean)
{
require_once("custom/modules/Home/geocode.php");
$vars = array(
'street' => 'streetExp',
'city' => 'cityExp',
'state' => 'stateExp'
);
foreach($vars as $var => $exp)
{
$toEval = Parser::replaceVariables($this->$exp, $bean);
$var = Parser::evaluate($toEval)->evaluate();
}
$target = $this->target;
$bean->$target = getZipCode($street, $city, $state);
}
function getDefinition()
{
return array(
"action" => $this->getActionName(),
"target" => $this->target,
);
}
static function getActionName()
{
return "SetZipCode";
}
}
?>
Once you have the action written, you need to call it somewhere in the code. Currently, this must be done as shown above using custom views, logic hooks, or custom modules.