Token Handling - Zend Framework - OAuth 2.0 - Google

Sample code to get a request token from Google through OAuth 2.0. These are snippets of code to show the request and response interaction.

This is the link to allow a user to authorize application access.

        $auth_uri='https://accounts.google.com/o/oauth2/auth?'.
                'client_id='.$this->configs->contacts->client->id.'&'.
                'redirect_uri='.$this->configs->contacts->redirect_uri.'&'.
                'scope='.$this->configs->contacts->scope.'&'.
                'response_type=code';

If the user authorizes access, Google gives them a token, which is referred to as an auth_code in the following code. They paste the token in the auth_code input and click a button to initiate this action.

            if ($form->getElement('auth_code')->isValid($data['auth_code']))
            {
                $client = new Zend_Http_Client($this->configs->oauth_uri,
                    array( 'maxredirects' => 0, 'timeout'      => 30));
                $client->setMethod(Zend_Http_Client::POST);
                $client->setHeaders('Content-Type: application/x-www-form-urlencoded');
                $client->setParameterPost(array(
                    'code' => $data['auth_code'],
                    'client_id' => $this->configs->contacts->client->id,
                    'client_secret' => $this->configs->contacts->client->secret,
                    'redirect_uri' => $this->configs->contacts->redirect_uri,
                    'grant_type' => 'authorization_code'));
                $response = $client->request();
                $this->googlecontacts_data->last_status = $response->getStatus();
                if ($response->isSuccessful())
                {
                    $response_data = Zend_Json::decode($response->getBody());
                    $this->googlecontacts_data->last_auth = new Doctrine_Expression('NOW()');
                    $this->googlecontacts_data->access_token = $response_data['access_token'];
                    $this->googlecontacts_data->expires_in = $response_data['expires_in'];
                    $this->googlecontacts_data->token_type = $response_data['token_type'];
                    $this->googlecontacts_data->refresh_token = $response_data['refresh_token'];
                    $this->googlecontacts_data->status = 'authorized';
                }
                else
                {
                    $this->googlecontacts_data->access_token =
                    $this->googlecontacts_data->expires_in =
                    $this->googlecontacts_data->token_type =  
                    $this->googlecontacts_data->refresh_token = null;
                    $this->googlecontacts_data->status = 'not_authorized';
                }
                $this->save_googlecontacts_data(); 

This code uses the access or refresh token to retrieve the contacts.

        if ($this->googlecontacts_data->status == 'authorized')
        {
            $client = new Zend_Http_Client($this->configs->contacts->userinfo,
                array( 'maxredirects' => 0, 'timeout' => 30));
            $client->setMethod(Zend_Http_Client::GET);
            $client->setHeaders('Authorization: Bearer '.$this->googlecontacts_data->access_token);
            $response = $client->request();
            if (!$response->isSuccessful())
            {
                $client = new Zend_Http_Client($this->configs->oauth_uri,
                    array( 'maxredirects' => 0, 'timeout'      => 30));
                $client->setMethod(Zend_Http_Client::POST);
                $client->setHeaders('Content-Type: application/x-www-form-urlencoded');
                $client->setParameterPost(array(
                    'client_id' => $this->configs->contacts->client->id,
                    'client_secret' => $this->configs->contacts->client->secret,
                    'refresh_token' => $this->googlecontacts_data->refresh_token,
                    'grant_type' => 'refresh_token'));
                $response = $client->request();
            }
            $this->googlecontacts_data->last_status = $response->getStatus();
            if ($response->isSuccessful())
            {
                $response_data = Zend_Json::decode($response->getBody());
                $this->googlecontacts_data->last_auth = new Doctrine_Expression('NOW()');
                $this->googlecontacts_data->access_token = $response_data['access_token'];
                $this->googlecontacts_data->expires_in = $response_data['expires_in'];
                $this->googlecontacts_data->token_type = $response_data['token_type'];
                $this->googlecontacts_data->auto = null;
                $this->googlecontacts_data->deleted_at = null;
                $this->googlecontacts_data->status = 'authorized';
            }
            else
            {
                $this->googlecontacts_data->access_token =
                $this->googlecontacts_data->expires_in =
                $this->googlecontacts_data->token_type =
                $this->googlecontacts_data->refresh_token =
                $this->googlecontacts_data->auto = null;
                $this->googlecontacts_data->status = 'not_authorized';
            }
            $return = $this->save_googlecontacts_data();
            if (!isset($return['error']))
            {
                $this->view->results = $this->get_contacts();
                $this->return['success'] = true;
            }
        }
        else
            $this->return['error'] = $this->status();

Some of the config values (other omitted for security):

oauth_uri = “https://accounts.google.com/o/oauth2/token”
contacts.uri = “https://www.google.com/m8/feeds/contacts/default/full”
contacts.scope = “https://www.google.com/m8/feeds/”
contacts.userinfo = “https://www.googleapis.com/oauth2/v1/userinfo”

.ini file settings for auth_code input. This application forces the user to cut and paste the token into the browser.

[production]
action="/contacts/google”
method="post”

disableTranslator = 0
; code element
elements.auth_code.type = “ValidationTextBox”
elements.auth_code.options.label = “Authorization Code”
elements.auth_code.options.required = true
elements.auth_code.options.trim = “true”
elements.auth_code.options.class = “long”
elements.auth_code.options.validators.strlen.validator = “StringLength”
elements.auth_code.options.validators.strlen.options.min = “8″
elements.auth_code.options.validators.strlen.options.max = “100″
elements.auth_code.options.validators.regex.validator = “regex”
elements.auth_code.options.validators.regex.options.pattern = “/^[\w\/\-]{8,100}$/”
elements.auth_code.options.validators.regex.options.messages.regexInvalid = “Invalid code”
elements.auth_code.options.filters[] = “StringTrim”
elements.auth_code.options.filters[] = “StripTags”
elements.auth_code.options.filters[] = “StripNewlines”

displayGroups.gcode.options.order = 10
displayGroups.gcode.options.class = “auth_code”
displayGroups.gcode.elements[] = “auth_code”

Zend Framework 1.11 - Rename Form Elements

Workaround

In this case, the method accepts the name of the element and applies a prefix.

The code was derived from Zend_Form::removeElement.

private function prefixElement($name)
        {
            $name = (string) $name;
            if (isset($this->_elements[$name])) {
                $this->_elements[$name]->setName($this->_prefix.$name);
                $this->_elements[$this->_prefix.$name]=$this->_elements[$name];
                unset($this->_elements[$name]);
                if (array_key_exists($name, $this->_order)) {
                    $this->_order[$this->_prefix.$name]=$this->_order[$name];
                    unset($this->_order[$name]);
                    $this->_orderUpdated = true;
                } else {
                    foreach ($this->_displayGroups as $group) {
                        if (null !== $group->getElement($name)) {
                            $group->addElement($this->getElement($this->_prefix.$name));
                            $group->removeElement($name);
                        }
                    }
                }
                return true;
            }

            return false;
        }

dojo 1.6.1 Form.reset()

Code to clear a form without firing the onChange events on the inputs.

 /* frmDijit.reset() */
                        dojo.query('#frmForm input[class^=dijit]').forEach(
                        function(item)
                        {
                                if ((d=dijit.byId(item.id))!=null)
                                        switch(d.type)
                                        {
                                        case 'checkbox':
                                        case 'radio':
                                                d.set('checked',false);
                                                break;
                                        case 'hidden':
                                        case 'text': 
                                                d.set('value','',false);
                                                break;
                                        }
                        });

dojo 1.1.1 to 1.6.1 Declarative Tag Update Script

This script performs the bulk of the upgrade translation to translate declarative tags from older versions of dojo up to the 1.6 format.

The script was written to work with tags in Smarty templates, so it includes a little extra logic to avoid confusion with Smarty syntax within the tags. If you’re not using Smarty, the script should still run fine, but you will need to change {ldelim} to { and {rdelim} to }.

Example of a simple translation performed by the tool.

Original

<label for="iDisplayMaxOffset">{#max_offset#}</label>
<input type="text" class="medium"
  name="iDisplayMaxOffset" id="iDisplayMaxOffset"
  dojoType="dijit.form.ValidationTextBox"
  readonly="true"
  value="{$iMaxOffset+1}" />

<label for="iDisplayMaxOffset">{#max_offset#}</label>
<input data-dojo-type="dijit.form.ValidationTextBox" data-dojo-props="'type':'text','class':'medium','name':'iDisplayMaxOffset','id':'iDisp
layMaxOffset','readonly':'true','value':'{$iMaxOffset+1}'">

Note that the attribute names are enclosed in single quotes. If there is a class attribute, for a custom CSS override, Internet Explorer (version 7, possibly others) requires the class attribute name to be quoted, or it may cause errors.

If a tag is too complex for this tool, it will output text recommending manual review, as well as the original tag, so you can fix any errors. In the translation below, the tool removed some of the message text.


{**** MANUAL REVIEW RECOMMENDED ****}
{**** 
<input type="text"
  name="sText" id="sText"
  regExp="{$sExtLngRegExp}"
  dojoType="dijit.form.ValidationTextBox"
  promptMessage="{#C_enter#} {#text#}"
  invalidMessage="{#C_invalid#} {#text#}"
  errorMessage="{#C_invalid#} {#text#} {#C_refer_to_documentation#}"
  value="{$sText}"
  onChange="setDataChanged()" />
****}
<input data-dojo-type="dijit.form.ValidationTextBox" data-dojo-props="'type':'text','name':'sText','id':'sText','regExp':'{$sExtLngRegExp}','promptMe
ssage':'{#C_enter#}','invalidMessage':'{#C_invalid#}','errorMessage':'{#C_invalid#}','value':'{$sText}','onChange':function(e){ldelim}setDataChanged(){rdelim}">

This is a command line script, run it with php dojo-tag.php old.file.html

<?php

$aEvents=array
('onblur'=>'onBlur',
'onchange'=>'onChange',
'onclick'=>'onClick',
'onclose'=>'onClose',
'ondblclick'=>'onDblClick',
'onfocus'=>'onFocus',
'onkeydown'=>'onKeyDown',
'onkeypress'=>'onKeyPress',
'onmousedown'=>'onMouseDown',
'onmouseenter'=>'onMouseEnter',
'onmouseleave'=>'onMouseLeave',
'onmousemove'=>'onMouseMove',
'onmouseout'=>'onMouseOut',
'onmouseover'=>'onMouseOver',
'onmouseup'=>'onMouseUp');

$aDeprecated=array('dijit.layout.AccordionPane'=>'dijit.layout.ContentPane');

$aDojoRequires=array();

$sFileText=file_get_contents($argv[1]);

$sNewText=preg_replace_callback('/(^\s*dojo\.require\s*\([\'"]([^\'^"]*)[\'"]\);[\r\n\s]*$)/m','requires',$sFileText);

$sUpdatedText=preg_replace_callback('/(<)(\w+) +([^>]*)(\/?>)/im','tags',$sNewText);

echo $sUpdatedText.PHP_EOL;

file_put_contents('/tmp/NEW',$sUpdatedText);

function requires($aMatches)
{
        global $aDeprecated,$aDojoRequires;
        $sDijit=trim($aMatches[2]);
        if (array_key_exists($sDijit,$aDeprecated))
                $sDijit=$aDeprecated[$sDijit];
        if (!in_array($sDijit,$aDojoRequires))
        {
                $aDojoRequires[]=$sDijit;
                return 'dojo.require("'.$sDijit.'");';
        }
        return '';
}

function tags($aMatches)
{
        global $aDeprecated;
        $sText=$aMatches[0];
        if (strpos($sText,'dojoType')!==false)
        {
                $sDataDojoType='';$aDojoText=array();$sDojoText='data-dojo-props=';$bFlagForReview=false;
                $sDataDojoId='';
                $aText=array();

                $sAttributeText=$aMatches[3];
                if (($iLeft=strpos($sAttributeText,'{'))!==false) 
                {
                        $iRight=strpos($sAttributeText,'}');
                        $iCountLeft=substr_count($sAttributeText,'{');
                        $iCountRight=substr_count($sAttributeText,'}');
                        if (($iLeft>$iRight) || ($iCountLeft>1) || ($iCountRight>1))
                                $bFlagForReview=true;
                }

                preg_match_all('/([a-zA-Z]+=[^>\s]+)/m',$sAttributeText,$aText);
                foreach ($aText[0] as $k => $v)
                {
                        if (stripos($v,'dojoType=')!==false)
                        {
                                $sDataDojoType=substr(str_replace(array('\'','"'),'',trim($v)),9);
                                $bDeprecated=array_key_exists($sDataDojoType,$aDeprecated);
                                if ($bDeprecated!==false)
                                {
                                        $bFlagForReview=true;
                                        $sDataDojoType=$aDeprecated[$sDataDojoType];
                                }
                                $sDataDojoType='"'.$sDataDojoType.'"';
                        }
                        else
                                if (stripos($v,'jsId=')!==false)
                                        $sDataDojoId='data-dojo-id='.substr($v,5);
                                else
                                        $aDojoText[]=pair($v,$bFlagForReview);
                }
                if (count($aDojoText)>0)
                        $sDojoText.='"'.implode(',',$aDojoText).'"';
                $sFlag=($bFlagForReview)?'{**** MANUAL REVIEW RECOMMENDED ****}'.PHP_EOL.
                        '{**** '.PHP_EOL.$sText.PHP_EOL.'****}'.PHP_EOL:'';
                $sClose=(strpos($sText,'/>')===false)?'>':' />';
                return $sFlag.$aMatches[1].$aMatches[2].' data-dojo-type='.$sDataDojoType.' '.$sDojoText.$sClose;
        }
        else
                return $sText;
}


function pair($value,&$bFlagForReview)
{
        global $aEvents;
        $iEqual=strpos($value,'=');
        $sProperty=substr($value,0,$iEqual);
        $sValue=substr($value,$iEqual+1);
        $sProperty=str_replace(array('\'','"'),'',trim($sProperty));
        $sValue=str_replace(array('\'','"'),'',trim($sValue));
        $bEvent=array_key_exists($sPropertyIndex=strtolower($sProperty),$aEvents);
        if ($bEvent===false)
                        $sRetVal='\''.$sProperty.'\':\''.$sValue.'\'';
        else
        {
                $bFlagForReview=true;
                $sRetVal='\''.$aEvents[$sPropertyIndex].'\':function(e){ldelim}'.$sValue.'{rdelim}';
        }

        return $sRetVal;
}

This is a simple tool, it is intended to be run on one file at a time, with the output reviewed carefully.

This script will upgrade most of the javascript, be sure to check the code carefully after it runs.

#!/bin/bash

# dojo 1.1.1 to dojo 1.6.1 javascript upgrade script

if [ $# -lt 1 ]
then
        echo 'Usage: dojo-js.sh <filename>'
        exit;
fi

NEW='/tmp/NEW'
cp $1 $NEW

sed --in-place "/attr([^\,^)]*)/s/attr/get/g" $NEW

# Must run AFTER the line above
sed --in-place "s/attr/set/g" $NEW
sed --in-place "s/\(\.set('value',[^\)]*\))/\1,false)/g" $NEW

sed --in-place "s/\.setValue\ *( *\([^\)]* *\))/.set('value',\1,false)/g" $NEW

sed --in-place "s/\.getValue\ *( *\([^\)]* *)\)/.get('value')/g" $NEW

sed --in-place "s/json-comment-filtered/json/" $NEW

sed --in-place "s/getDescendants\(.*\)$/getChildren\1 \/\/**** MANUAL REVIEW RECOMMENDED/" $NEW

sed --in-place "s/setHref(/set('href',/g" $NEW

sed --in-place "s/setAttribute/set/g" $NEW

sed --in-place "s/\.checked\s*\=\s*\(true\|false\)/.set('checked',\1)/g" $NEW
sed --in-place "s/\.checked/.get('checked')/g" $NEW

sed --in-place "s/getDisplayedValue()/get('displayedValue')/g" $NEW

sed --in-place "s/setLabel(/set('label',/g" $NEW

*** Make a backup before using these tools ***

dojo - Distinguishing between onChange events

During an upgrade from dojo 1.1.1 to 1.6.1, I found that dijit.byId('someWidget').set('value','someNewValue'), was causing the onChange event to fire for the input.

To prevent onChange from firing when the .set method is used, a third parameter priorityChange is used. Setting priorityChange to false prevents the onChange event from firing.

The updated code is: dijit.byId('someWidget').set('value','someNewValue',false)

A sed command to update the code. In this case the setValue or attr(’value’) calls had already been translated to set:

sed --in-place "s/\.set\ *([\"|']value[\"|']\,\ *\([^\)]*\))/.set('value',\1,false)/g" js/code.js