June 9, 2011

Minify or Compress HTML Output with CodeIgniter

Recently i worked on improving page load using Google's page speed tool. One of the suggestion includes minifying HTML , page speed indicated around 10 to 30 % improvement using minifyer.

So i decided to include it in our project which is built on CodeIgniter framework.

In CodeIgniter we can use hooks so that before HTML output is sent to browser it can be minifyed. here's how-

1) Enable hooks if not done previously

- Set $config['enable_hooks'] = TRUE; in /system/application/config/config.php.

2)Configure the hook function in /system/application/config/hooks.php. as below-


$hook['display_override'] = array(
                                'class'    => 'Minifyhtml',
                                'function' => 'minify',
                                'filename' => 'Minifyhtml.php',
                                'filepath' => 'hooks'
                                );

3) Create Minifyhtml.php in /system/application/hooks/ folder with following content. Following HTML minifying code is extracted from minifyer http://code.google.com/p/minify/ which is better than all available other as it wont break your existing HTML.

class Minifyhtml {
   
    /**
     * "Minify" an HTML page
     *
     * @param string $html
     *
     * @param array $options
     *
     * 'cssMinifier' : (optional) callback function to process content of STYLE  elements.
     *
     * 'jsMinifier' : (optional) callback function to process content of SCRIPT elements. Note: the type attribute
     *                    is ignored.
     * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If unset, minify will sniff for an
     *                  XHTML doctype.
     *
     * @return string
     */
    function minify() {
       
        $CI =& get_instance();
        $buffer = $CI->output->get_output();
   
        $options = array();
        $min = new Minifyhtml();
        $min->pre_process($buffer, $options);
        return $min->process();
    }
   
   
    /**
     * Create a minifier object
     *
     * @param string $html
     *
     * @param array $options
     *
     * 'cssMinifier' : (optional) callback function to process content of STYLE elements.
     *
     *
     * 'jsMinifier' : (optional) callback function to process content of SCRIPT elements. Note: the type attribute
     *                       is ignored.
     *
     * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If unset, minify will sniff for an
     *                      XHTML doctype.
     * @return null
     */
    public function pre_process($html, $options = array())
    {
        $this->_html = str_replace("\r\n", "\n", trim($html));
        if (isset($options['xhtml'])) {
            $this->_isXhtml = (bool)$options['xhtml'];
        }
        if (isset($options['cssMinifier'])) {
            $this->_cssMinifier = $options['cssMinifier'];
        }
        if (isset($options['jsMinifier'])) {
            $this->_jsMinifier = $options['jsMinifier'];
        }
    }
   
   
    /**
     * Minify the markeup
     *
     * @return string
     */
    public function process()
    {       
        if ($this->_isXhtml === null) {
            $this->_isXhtml = (false !== strpos($this->_html, '
        }
       
        $this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
        $this->_placeholders = array();
       
        // replace SCRIPTs (and minify) with placeholders
        $this->_html = preg_replace_callback(
                       '/(\\s*)(]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'             
                       ,array($this, '_removeScriptCB')             
                      ,$this->_html);                
       
           // replace STYLEs (and minify) with placeholders         
          $this->_html = preg_replace_callback(            
                      '/\\s*(]*?>)([\\s\\S]*?)<\\/style>\\s*/i'            
                      ,array($this, '_removeStyleCB')             
                      ,$this->_html);                 

          // remove HTML comments (not containing IE conditional comments).        
          $this->_html = preg_replace_callback(            
                       '//'             
                      ,array($this, '_commentCB')             
                     ,$this->_html);                
         
          // replace PREs with placeholders         

          $this->_html = preg_replace_callback(
                       '/\\s*(]*?>[\\s\\S]*?<\\/pre>)\\s*/i'             
                       ,array($this, '_removePreCB')            
                       ,$this->_html);                

          // replace TEXTAREAs with placeholders         

         $this->_html = preg_replace_callback(            
                       '/\\s*(]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'             
                       ,array($this, '_removeTextareaCB')             
                       ,$this->_html);                 

          // trim each line.         
          // @todo take into account attribute values that span multiple lines.         

          $this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);                
          // remove ws around block/undisplayed elements        
          $this->_html = preg_replace(
                          '/\\s+(<\\?(?:area|base(?:font)?|blockquote|body'           
                         .'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
                        .'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
                        .'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'    
                        .'|ul)\\b[^>]*>)/i', '$1', $this->_html);   
             
           // remove ws outside of all elements         
            $this->_html = preg_replace_callback('/>([^<]+),array($this, '_outsideTagCB')              
                                                 ,$this->_html);                

            // use newlines before 1st attribute in open tags (to limit line lengths)         

            $this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);                 
           // fill placeholders        
            $this->_html = str_replace(array_keys($this->_placeholders)          
                                                           ,array_values($this->_placeholders) ,$this->_html);                         $CI =& get_instance();    
            $CI->output->set_output($this->_html);      
            $CI->output->_display();     
}         

protected function _commentCB($m)     {
        return (0 === strpos($m[1], '[') || false !== strpos($m[1], '            : '';     }         

protected function _reservePlace($content)     {         
      $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';         $this->_placeholders[$placeholder] = $content;         return $placeholder;     }    

protected $_isXhtml = null;     
protected $_replacementHash = null;     
protected $_placeholders = array();     
protected $_cssMinifier = null;    
protected $_jsMinifier = null;    

protected function _outsideTagCB($m)     {  
       return '>' . preg_replace('/^\\s+|\\s+$/', ' ', $m[1]) . '<';     }     
protected function _removePreCB($m)     {      return $this->_reservePlace($m[1]);     }        
protected function _removeTextareaCB($m)     {     return $this->_reservePlace($m[1]);     }    
protected function _removeStyleCB($m)     {      
               $openStyle = $m[1];        
               $css = $m[2];        
               // remove HTML comments       
              $css = preg_replace('/(?:^\\s*\\s*$)/', '', $css);                 
              // remove CDATA section markers         
              $css = $this->_removeCdata($css);                
              // minify         
              $minifier = $this->_cssMinifier ? $this->_cssMinifier             : 'trim';         
              $css = call_user_func($minifier, $css);                
              
               return $this->_reservePlace($this->_needsCdata($css)  
                           ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/&lgt;/style>
                            : "{$openStyle}{$css}</style>" );    

}    

protected function _removeScriptCB($m)     {         
               $openScript = $m[2];         
               $js = $m[3];                
                // whitespace surrounding? preserve at least one space        
               $ws1 = ($m[1] === '') ? '' : ' ';         
               $ws2 = ($m[4] === '') ? '' : ' ';        

                // remove HTML comments (and ending "//" if present)         

                $js = preg_replace('/(?:^\\s*\\s*$)/', '', $js);                     

                // remove CDATA section markers        
                $js = $this->_removeCdata($js);                
               // minify         
               $minifier = $this->_jsMinifier ? $this->_jsMinifier : 'trim';        
               $js = call_user_func($minifier, $js);                

               return $this->_reservePlace($this->_needsCdata($js)             
                                  ? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
                                  : "{$ws1}{$openScript}{$js}{$ws2}");  
}    

protected function _removeCdata($str)     {         return (false !== strpos($str, ''), '', $str)             : $str;     }
  protected function _needsCdata($str)     {   
                   return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));     } 
}








December 24, 2010

Installing solr with tomcat on ubuntu

This article explains steps to install Solr -Lucene search engine under Tomcat on ubuntu. Also important checks to avoid spending a lot of time in debugging are mentioned where-ever needed.


1) JAVA
    First make sure you have latest java SDK installed.
    Using -  
                     dpkg –get-selections | grep sun-java
This may give you following result if java is already installed
sun-java6-bin install
              sun-java6-jdk         install
              sun-java6-jre          install

If not, Install it using - 
                      apt-get install sun-java6-jdk
Make sure:- java was installed properly. check version of java installed with command - 
 java version Or java showversion
Add below line in your ".bashrc" to set JAVA_HOME variable tomcat needs it.
export JAVA_HOME=/usr/lib/jvm/java-6-sun
  
2) TOMCAT
   Install Tomcat 6 with-
                        apt-get install tomcat6 tomcat6-admin tomcat6-common tomcat6-user tomcat6-docs tomcat6-examples
Also install MYSQL java driver with -
            apt-get install libmysql-java
if you need connecting to RDBMS MYSQL.
Once tomcat is installed make sure its installed properly by doing
                        curl http://localhost:8080
In above command use the port number configured for tomcat connector listens for HTML1.1. Check for it in"server.xml" under "/usr/lib/tomcat6/conf"

curl shall fetch html page showing "It works" heading and some instructions.
 
3) SOLR
    If everything is good till now, its time to install Solr.
a) Download solr with-
                 wget -c http://apache.tradebit.com/pub/lucene/solr/1.4.1/apache-solr-1.4.1.zip
uncompress it with - unzip apache-solr-1.4.1.zip

b) Now copy the .war file from solr folder to tomcat webapps folder
                cp ~/apache-solr-1.4.1/dist/apache-solr-1.4.1.war /var/lib/tomcat6/webapps/solr.war

c) Also copy solr files as below-
                cp -R ~/apache-solr-1.4.1/example/solr/ /var/lib/tomcat6/solr/

This shall copy "bin" and "conf" folder and readme.txt to "/var/lib/tomcat6/solr/"

"conf" folder shall have following files -
                                 dataimport.properties -(make sure this file has write permissions for tomcat)
                                 elevate.xml
                                 mapping-ISOLatin1Accent.txt
                                 protwords.txt
                                 schema.xml
                                 scripts.conf
                                 solrconfig.xm
                                 spellings.tx
                                 stopwords.tx
                                 synonyms.tx
                                 xslt (folder
                                 my-data-config.xml (data config XML for your solr instance.you will need to edit 
                                                                    this file so you can import mysql data in to solr)

d) Create the "solr.xml" file under "/var/lib/tomcat6/conf/Catalina/localhost/" with - 
                 vi /etc/tomcat6/Catalina/localhost/solr.xml
Note:- XML file name under "/var/lib/tomcat6/conf/Catalina/localhost/" will be the path to access your solr instance like - http://localhost:8080/solr/
Add below contents in "solr.xml"
<Context docBase="/var/lib/tomcat6/webapps/solr.war" debug="0" privileged="true" allowLinking="true" crossContext="true">
<Environment name="solr/home" type="java.lang.String" value="/var/lib/tomcat6/solr" override="true" />
</Context&gt;
in Above set solr home where you copied the Solr files in step (c)

Change directory to "/var/lib/tomcat6/solr/" and create data folder with "mkdir data" 
Make sure the tomcat has write permissions to "data" folder.

This completes solr installation .make sure it works by browing here "http://localhost:8080/solr".
This shall give you html page with meassge "Welcome to Solr" with link to solr admin page.


e) make sure contents in solrconfig.xml in /var/lib/tomcat6/solr/conf is correct and what you want for your solr instance .Check that requestHandler with dataimport handler is defined as here. this is important to import data from mysql.

f) Also make sure that "/var/lib/tomcat6/solr/bin" contains MySQL JAVA connector jar .Else while dataimport it may throw exception that JDBC driver not found.
 


g) If you want another instance of Solrr for development purpose 
        -Copy "solr.xml" to "solrdev.xml"  in "/etc/tomcat6/Catalina/localhost/"
         -Also duplicate "solr" folder to "solrdev" in "/var/lib/tomcat6/".
         -Edit "solrdev.xml" to set solr home to "/var/lib/tomcat6/solrdev".
         -Edit "solrdev.xml" from "/var/lib/tomcat6/solrdev/conf" to use respecive "my-data-config.xml"
This instance can be accessed with "http://localhost:8080/solrdev"

Final step, restart Tomcat and check it here "http://localhost:8080/solr/"
use command "/etc/init.d/tomcat6 restart"




Here after refer to following links to successfully import data from MYSQL
http://wiki.apache.org/solr/DIHQuickStart
http://wiki.apache.org/solr/DataImportHandler
http://wiki.apache.org/solr/SearchHandler
http://wiki.apache.org/solr/SolrRequestHandler

For quick review https://docs.google.com/View?id=ddwrxhb8_86dtz2h9dc&pli=1

December 2, 2010

Upgrading FaceBook Connect to New GRAPH API

This Post is continuation to my previous post <a href="http://pandurangzambare.blogspot.com/2010/03/enable-you-site-with-facebook-connect.html">Enable-you-site-with-facebook-connect for facebook connect</a>. As facebook has recently released New GRAPH API's ,there's need to upgrade to Graph Api's.

Graph API's make it very easy to integrate FaceBook Connect for login, registration  or using FB plugin to your website, as compared to previous Connect Api's.

1) First of all new API' need us to define the name-space like below:-

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml">

2) Once the name-space is defined Include the JS file - http://connect.facebook.net/en_US/all.js - on page where you need to use FaceBook API.

3) Now we need to initialize the FaceBook JS library as below:-

<div id="fb-root"></div>
FB.init({appId: "APP_ID", status: true, cookie: true, xfbml: true});

This completes the basic requirements for FaceBook Graph API's

Set xfbml to true to use FBML on page of interest.

Using FBML tag as below will enable FaceBook login on your website.

<fb:login-button perms="publish_stream,create_event,rsvp_event,offline_access, publish_checkins,user_birthday,user_events,user_interests,user_likes,user_location, user_relationships,user_relationship_details,user_checkins,friends_checkins,friends_birthday, friends_events,friends_location" v="2" size="xlarge" onlogin="window.reload();">Sign in With Facebook<fb:login-button>

OR

If don't want use FBML initialize FaceBook library as below:-

<div id="fb-root"></div>
FB.init({appId: "APP_ID", status: true, cookie: true, xfbml: false});

and use the following tag to show login button:-


<a onclick="FB.login(function(response) {  if (response.session) { window.realod(); } else { show_error(); }});">Signin with faceBook&lt/a>

Simple three steps to include FaceBook login with New GRAPH API on your website.

March 25, 2010

Adding support for Facebook Connect on subdomians.

If you want subdoamins to share the same facebook session for Facebook connect There are some addings need to be done (I had wasted around 3-4 hrs to find this out) so wanted to share if its useful to others.

First thing you need to do is configure Base domain in faceook application settings page under "Connect"  tab.
  1)   This will make Facebook javascript library to stores the session cookie on base domain there by , allowing all subdomains to share the same cookie.
2)   And also Facebook Server will allow the Connect URL to be available to all subdomains
Secondly set  document.domain  to your base domain in all javascript page on your domain and subdomains.Also set document.domain = base.domain in xd_receiver.htm before the Facebook javascript import.

 Thanks Hope this helps you all to save time and add support for subdomains.

March 22, 2010

Installing java on UBUNTU

Recently wanted to install YUI compresssor which need java runtime for execution.


To install java we need to run following command on terminal -

sudo apt-get install sun-java6-jre sun-java6-plugin sun-java6-fonts

 If during installation you get error like "Err http://us.archive.ubuntu.com hardy-updates/multiverse sun-java6-bin 6-14-0ubuntu1.8.04   404 Not Found [IP: 91.189.88.46 80]"

You are required to Enable universe and multiverse repositories which can be done from command line as below -
Open a terminal and go to /etc/apt/ . backup sources.list file .Then edit it using the following command
vi /etc/apt/sources.list
Add following lines
## MAIN REPOSITORIES
deb http://gb.archive.ubuntu.com/ubuntu/ hardy main restricted universe multiverse
## MAJOR BUG FIX UPDATES produced after the final release
deb http://gb.archive.ubuntu.com/ubuntu/ hardy-updates main restricted universe multiverse
## UBUNTU SECURITY UPDATES
deb http://security.ubuntu.com/ubuntu hardy-security main restricted universe multiverse
If you wish to enable the backports and proposed repositories as well (recommended), then add these lines as well
## BACKPORTS REPOSITORY
deb http://gb.archive.ubuntu.com/ubuntu/ hardy-backports main restricted universe multiverse
## PROPOSED REPOSITORY
deb http://archive.ubuntu.com/ubuntu hardy-proposed main restricted universe multiverse

Once saved execute

sudo apt-get update

If it runs without error you shall be able to install JRE using this command with no problem -

 sudo apt-get install sun-java6-jre sun-java6-plugin sun-java6-fonts

March 3, 2010

Enable your site with Facebook Connect.

Recently i worked on integrating facebook connect on site so visitors can login with their facebook id to give comment on article .Incorporating facebook connect is advatageous to both the commenter and site owner .As commenter gets to publicize his facebook profile and get credit for good comments and site owner gets authenticate commenter and their site becomes more resourcefull and active .

Create an Facebook Application:-
First of all one needs to create an application on facebook so that people can authenticate with it in order to login on third site.
To create an go here http://www.facebook.com/developers/createapp.php. Give an appropriate App name and create the application.You will be presented with application settings page with multiple tabs.

1) In basic tab note down the app key and app secret (you will need it when you download php library for application from here **) fill out the other info as required .
2) In Authentication tab select user / pages as per your need .so who can install the application .
3) In Publihsre tab fill out the info if needed as per requirement.
4) Canvas tab is the imporatant setting for your application to work properly

In Canvas Page URL add appropriate name which will appear in application URL .
In Canvas Callback URL - Add the URL , on your web server where you will be hosting the facebook application. Do not forget to add '/' at the end of URL.
In Post-Authorize Redirect URL - Add the URl for the page where you want facebook users to get redirected to after they authorize you application access to their profile.
In Canvas Settings - choose Render Method as FBML or framesdepending on the type of application you want to host. If you don't have much functionality but informational application use frames else FBML .To learn more on FBML check here http://wiki.developers.facebook.com/index.php/XFBML. Add othetr info as per you likeness.

5) In Connect tab - Add Connect URL. it will be the root of your domain where you must copy xd_receiver.html. This is cross domain channel file used by facebook JavaScript Client Library to establish communication between your Web pages and Facebook pages.T file can be downloaded from here http://www.somethingtoputhere.com/xd_receiver.htm

Other tabs can be filled as per your needs and doesnt affect facebook connect functionality so not discussed here.

Now you application is ready and you have application key and secret noted from Basic tab in application settings page.

Install PHP library at canvas page URL:-

Download the php library from http://svn.facebook.com/svnroot/platform/clients/packages/facebook-platform.tar.gz and copy the folders php and footprints to Canvas Callback URL you set in application settings page under canvas tab.

You may add following code in index.php in your canvas callback URL to check if faceboook application is working properly


require_once 'php/facebook.php';

$appapikey = 'Your App Key';
$appsecret = 'Your App Secret';
$facebook = new Facebook($appapikey, $appsecret);
$user_id = $facebook->require_login();

// Greet the currently logged-in user!
echo '<p>Hello,<fb:name uid=\"$user_id\" useyou=\"false\" />!</p>';

// Print out at most 25 of the logged-in user's friends,
// using the friends.get API method
echo "<p>Friends:";
$friends = $facebook->api_client->friends_get();
$friends = array_slice($friends, 0, 25);
foreach ($friends as $friend) {
echo  '<fb:profile-pic size="square" uid="'.$friend.'" facebook-logo="true"></fb:profile-pic>';
}
echo "</p>";

Adding javascript to web pages :-

Now on the web page from your web site where you want to add facebook connect you shall include this JS-

<script type="text/javascript" src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php"></script>
Adding this JS file tells browser how to parse the facebook html tags used in page.

Now intialize the facebook javascript library.
<script type="text/javascript">
FB.init("Your App Key","http://domain.name/xd_receiver.htm");
</script>

Please add the application key and domain name in above places in javascript code.

Use this FBML code to add facebook connect icon on your web page -

<fb:login-button onlogin="facebook_onlogin_ready();"></fb:login-button>

"facebook_onlogin_ready" is the function which gets called when visitor is logged in using his facebook login.In this function you may reload the page or use ajax to fetch the visitors facebook profile details.

Following sample code fetches user profile with ajax -

var uid = FB.Facebook.apiClient.get_session().uid;
FB.Facebook.apiClient.users_getInfo(uid, ['name','first_name','last_name','email'],function(ret,ex) {


        var nameobj = document.getElementById('txtVName');
        nameobj.value = ret[0].name;
        FB.XFBML.Host.parseDomElement(nameobj);

        var webobj = document.getElementById('txtVWebsite');
        webobj.value = ret[0].profile_url;
        FB.XFBML.Host.parseDomElement(webobj);
});

In above code we use JS function from facebook javascript library "FB.Facebook.apiClient.users_getInfo" to get user details - first argument to it is user Id which we can retrieve from session using the js function "FB.Facebook.apiClient.get_session()" second argument is the profile fields we want to fetch. and third argument is the callback function which gets executed on succesfull profile fetching in which we update DOM with details .

Alternatively we can reload the page and use PHP library to fetch the profile details from facebook and prepopulate the web page with visitors details .

Use following code to fetach profile fields from facebook using php library.

$facebook = new Facebook('Your app Key', 'Your app secret');
$user_id = $facebook->get_loggedin_user();
$userObj = $facebook->api_client->users_getInfo($user_id, array('name','first_name','profile_url','pic_square'));

In order to access email like confidential profile fields you need to take extra permission from visitors which can be done by adding '{permsToRequestOnConnect : "email", }' as third argument to FB.init call .