PHP xcache performance tuning tutorial on Lighttpd

A tutorial on php + xcache for serving webpages directly from server RAM

A typical PHP application and most of the frameworks uses “The Loop” method to code the website. Whenever request comes to website, It’s typically sent to index page to handle every parameters.

// The world's simplest index page, also called The Loop
get_header();
if (have_action()) :
	if(found_action()):
		include(action);
	else:
		header("404");
else :
	  include("homepage");
endif;
get_footer();

Above code describes typical PHP application routing for template based websites. This style of coding can be converted into enterprise PHP framework with use of design patterns (i.e interfaces like pear, Template engine like smarty, use of MVC).

Xcache and Lighttpd

Xcache optimize PHP performance by keeping most frequently used code into server RAM. i.e. Once code is cached, repeated requests are directly served without recompiling. There are several PHP accelerators are available that goes well on different environments i.e. APC, memcache, xcache, static cache etc.

Accelerator on Lighttpd No of Requests Concurrent connections Serving Time
static File Cache 20000 5 43 seconds
memcache 20000 5 19 seconds
xcache 20000 5 13 seconds

I made some ab (apache benchmark) test on lighttpd with xcache, memcache and static file cache & I have to conclude that Lighttpd and Xcache combination gives best php performance.

Best Pairs of Accelerator + Web Server

  • APC + Apache = Best performance
  • Memcached + Nginx = Best performance
  • Xcache + Lighttpd = Best performance

The Loop Framework with Xcache

We can use xcache variables to store entire web page and serve it from RAM.

Xcache Functions

  • xcache_isset($key) – Checks for $key exist in xcache
  • xcache_set(“key”,”value”,$timeout) – Sets variable in xcache for $timeout seconds
  • xcache_get(“key”) – Gets variable from xcache
  • xcache_unset(“key”) – Removes variable from xcache

Using xcache with php to optimize website.

// Init Caching
include("xcache.php"); // Including Xcache Library... (find code below)
define('HTML_CACHE',true); // set this to false when you need to turn off xcache feature
$cacheConfig['cachepage'] = true; // for mobile useragent you may turn it off for fetching and displaying ads.
init_caching();

get_header();
if (have_action()) :
	if(found_action()):
		include(action);
	else:
		header("404");
else :
	  include("homepage");
endif;
get_footer();  

// Finish Caching
finish_caching();

xcache.php

// code for xcache.php
global $cacheConfig;
$cacheConfig = Array();
$cacheConfig['timeout'] = 10800; // 3 hours
$cacheConfig['page'] = md5("http://".$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI']);
$cacheConfig['savepage'] = false;
$cacheConfig['cachepage'] = false;

function cacheEnabled() {
	if (!defined('HTML_CACHE') || (defined('HTML_CACHE') && HTML_CACHE != true)) {
		return false;
	}else{
		return true;
	}
}

function init_caching() {
	if (!cacheEnabled()) {
		return;
	}
	global $cacheConfig;
	if ($cacheConfig['cachepage'] == true) {
	startHTMLCache();
	}
}

function finish_caching() {
	if (!cacheEnabled() ){
		return;
	}
	global $cacheConfig;
	if ($cacheConfig['savepage'] == true) {
	endHTMLCache();
	}
}

function endHTMLCache() {
		global $cacheConfig;
		$content = ob_get_contents();
		ob_end_clean();
		xcache_set($cacheConfig['page'],$content,$cacheConfig['timeout']);
		echo $content;
		exit;
}

function startHTMLCache() {
		global $cacheConfig;
		if (xcache_isset($cacheConfig['page']))	{
			echo xcache_get($cacheConfig['page']);
			exit;
		}
  		else {
    	  $cacheConfig['savepage'] = true;
		   ob_start();
  	 	}
}

StickyTable – jQuery fixed table header plugin

One of my client wanted to display financial reports data with better accessibility and printing usability. He asked me to retain table header fixed on the top while scrolling financial reports.  He also wanted to have MS Access VBA style multicolumn drop-down component for large customer database. In this post I am writing about that fixed table header component which I have coded in jQuery.

By default, If you print any HTML table…

  • the <TH> row get printed on top of every pages that contains the table.
  • <TFOOT> row get printed on every page’s footer.
  • <THEAD> row get printed on every page’s header.

For printing we have chosen fixed width fonts i.e. Courier New.

Demo tutorial page: Demo for jQuery stickyTable plugin

Download: jQuery stickyTable plugin zip file

Initializing jQuery stickyTable plugin

$(document).ready(function(){
	$('#report_table').stickyTable();
});

Above function binds StickyTable plugin to supplied table.

jQuery.stickyTable.js plugin source

(function($){
$.fn.stickyTable = function() {
  if ($(this).length == 0) {
    throw new Error('jQuery.stickyTable ERROR\n\nAttribute id="'+ $(this).selector.replace("#","") +'" NOT FOUND in your webpage.');
    return;
  }

  function equilize_width() {
    var ths = $('#report_table')[0].rows[0].cells;
    var thd = $('#stickyfixed')[0].rows[0].cells;
    for(var j=0; j < ths.length; j++) {
      $(thd[j]).width($(ths[j]).width()-1);
    }
    $('#stickyfixed').width($('#report_table').width()+4);
  }

  $(this).parent().append("<div id='stickyfixed_container' class='hide'><table id='stickyfixed' class='report_table' border='1'></table>&ly;/div>");

  $('#report_table tr:first').clone().appendTo('#stickyfixed');
  equilize_width();
  var cutoffTop      = $('#report_table').offset().top;
  var cutoffBottom  = $('#report_table').height(); + cutoffTop - $($('#report_table')[0].rows[0].cells[0]).height();
  var no_fixed_support = false;
  if ($('#stickyfixed').css('position') == "absolute") {
    // IE 6 hack
    no_fixed_support = true;
  }
  $('#report_table tr').hover(function() {
      $(this).addClass("highlight");
    },function() {
      $(this).removeClass("highlight");
  });

  //Handling windows resize
  $(window).resize(function(){
    equilize_width();
  });

  $(window).scroll(function(){
    var currentPosition = $(document).scrollTop();
      if (currentPosition > cutoffTop && currentPosition < cutoffBottom) {
        $('#stickyfixed_container').show();
          if (no_fixed_support == true) {
            // IE 6 hack
            document.getElementById('stickyfixed').style.top = parseInt(currentPosition) + "px";
          }
      } else {
    $('#stickyfixed_container').hide();
   }
  });
  return $(this);
 };
})(jQuery);

SCREEN.CSS

/* CSS for screen */
body,*{font-family:Tahoma}
h1{padding:0px;margin:5px;font-size:22px}
h1{padding:0px;margin:5px;font-size:22px}
#header{padding:10px;font-size:24pt;font-weight:bold;font-family:Arial,sans-serif;border-bottom:1px solid #DDD;text-align:left;margin:0px}
#report *{font-family:Tahoma;font-size:8pt}
table.report_table{border-collapse:collapse;border-spacing: 2px;border:2px solid #FFF;width:100%;overflow:scroll;background-color:#FFF;font-size:10pt;}
table.report_table th{padding:5px;background-color:#3B5998;color:#FFF}
table.report_table tr td{padding:5px;background-color:#F3F3F3;border:1px solid #FFF;}
table.report_table tr.highlight td{padding:5px;background-color:#B0D4F7;color:#000;}
table#stickyfixed{z-index:10;position:fixed;_position:absolute;top:0;font-size:10pt;}
table#stickyfixed th{}
.hide{display:none;}

PRINT.CSS

/* CSS for print */
body,*{font-family:Courier New;font-size:11px}
h1{display:none;font-family:Times New Roman;padding:0px;margin:5px;font-size:18px}
#report *{font-family:Courier New}
table.report_table tr th{font-size:14pt}
table.report_table tr td h3{font-size:12pt}
table.report_table td{overflow:hidden;white-space:nowrap;}
#member_links{display:none}
table#report_form{display:none;}
table#stickyfixed{display:none;}

Upgrading php 5.1 to php 5.3 with xcache rebuild on centos 5

Upgrading PHP 5.3 on CentOS 5

CentOS 5 comes with php 5.1 version. There is no official PHP 5.2+ release for  upgrade since last 3 years. So, It was hard for any php developer to work with new php functions like json_encode, json_decode and powerful frameworks like symfony and cakephp 2

As a result, Developer had to implement alternative functions to integrate twitter, myspace OAuth API. Today wordpress has officially said bye bye to php 4 and mysql 4. So finally, I gathered some courage to mess with my current php installation.

I followed the following steps to upgrade php 5.3 on centos 5.

Adding webtatic repository to yum for php 5.3 upgrade

su
cd ~
rpm -ivh http://repo.webtatic.com/yum/centos/5/`uname  -i`/webtatic-release-5-0.noarch.rpm

Updating PHP with yum

yum --enablerepo=webtatic update php

Up to this point, everything had executed perfectly, including dependency resolving.

After upgrading PHP 5.3, when I tried to browse my site homepage, I got blank page!

I thought now I went into deep trouble, I shouldn’t had taken the risk but then I thought lets check all sites hosted on my server. Fortunately they were working correctly. So as computer engineer, I started digging into problem with basics. Just added 2 lines of error debugging code into my header file.

error_reporting(E_ALL);
ini_set("display_errors",1);

Got xcache error: function xcache_isset() not defined on line blah… So I got it. I checked for php version.

php -v

Everything was there but xcache was missing. Finally I got the clue, I have to rebuild xcache with newer version of php. So I rebuild the xcache.

XCache 1.3.0 rebuild commands for PHP 5.3 on CentOS 5

wget http://xcache.lighttpd.net/pub/Releases/1.3.0/xcache-1.3.0.tar.gz
tar -xzvf xcache-1.3.0.tar.gz
cd xcache-1.3.0/
phpize --clean
phpize
./configure --enable-xcache && make
make install

That’s it. After restarting lighttpd, my homepage started working correctly.

Google App Engine powered python mailer

Third party highly customizable email marketing infrastructure is quiet costly compared to owning dedicate email server. Companies providing low cost email marketing packages has limitation on daily/monthly mail count and bandwidth.  Even there is no guaranty of any hosted mailing server, If internet users mark sent mail/IP as spam, server get black listed. So It’s wise to use trusted mailing infrastructure in budget cost.

Google App Engine allows daily 2000 mail resource usage for free. For more quota one can easily upgrade membership and have reliable and trustful emailing infrastructure.

Google App Engine Mailer Logic Diagram

Features

  1. Django template based mailer
  2. IP address validation/filtering.
  3. Generating PHP API on the fly for remote requests.
  4. Task Queue based mail sending module.

Summary of Application

  • First of all it ask for admin’s google account credentials whenever we point browser to http://appid.appspot.com
  • After successful login, It shows dashboard containing existing created mailer templates.
    Dash Board Screen
  • Admin can add, edit, delete templates. It also allows python django template variables. i.e. Dear {{username}}
    Mailer Edit Screen
  • To get Google App Engine request curl api address, one need to go inside edit mailer.
    Google App Engine Mailer
  • It also allows admin to configure our app for limited IP addresses.
    Mailer IP filter screen

Here is a demo code for Google App Engine Mail task queue

app.yaml

application: your_app
version: 1
runtime: python
api_version: 1

handlers:
- url: /picknsend*
  script: picknsend.py

queue.yaml

queue:
- name: limitedemail
  rate: 2000/d

Setting up task queue

try:
  from google.appengine.api.labs import taskqueue
except ImportError:
  from google.appengine.api import taskqueue # for official inclusion of taskqueue.

 # schedule task queue for sending mail
      q = taskqueue.Queue('limitedemail')
      t = taskqueue.Task(url='/picknsend', method='POST')
      q.add(t)

picknsend.py

#!/usr/bin/env python
# /picknsend task queue job
import os
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext import db
from google.appengine.api import mail

class MailQueue(db.Model):
  mail_to = db.StringProperty(required=True)
  mail_subject = db.StringProperty(required=True)
  mail_body = db.TextProperty(required=True)
  mail_datetime = db.DateTimeProperty(auto_now_add=True)
  mail_sent = db.BooleanProperty(default=False)

class PickAndSendMail(webapp.RequestHandler):
  def post(self):
    self.sendmail()

  def sendmail(self):
    m = MailQueue.gql("WHERE mail_sent=False ORDER BY mail_datetime asc").fetch(1)
    for i in m:
      BODY    = i.mail_body
      TO      = i.mail_to
      SUBJECT = i.mail_subject
    mail.send_mail('your_email@gmail.com',TO,SUBJECT,BODY)
	for i in m:
      i.mail_sent = True  # marking... mail gone
      i.put()      

def main():
  application = webapp.WSGIApplication([('/picknsend*', PickAndSendMail)], debug=True)
  util.run_wsgi_app(application)

if __name__ == '__main__':
  main()

Above code is not our entire application. It’s just one of the sample module that Google App Engine developer can refer for development.

Drupal 6.x clean urls with lighttpd 1.4 + windows 7

While experimenting with drupal module development on my lighttpd server (Windows 7 Ultimate), I encounter some wired things.

  1. It stuck on installation page, after taking mysql host, username and password, nothing happened…

    SOLUTION:

    I changed permission of sites/default/settings.php & supplied mysql credentials manually. So it moved to next steps and completed installation.

  2. Okay… trouble were not just over. After completing installation, when I went to admin”/settings/clean-urls”I realized lighttpd + drupal + windows = Trouble to developer!

    Drupal clean urls

  3. Clean URL option was disabled! I couldn’t change it (although I could do it with firefox developer toolbar). So I started searching the drupal community for lighttpd rules. Bunch of pages had number of answers like mod_magnet, lua, mod_rewrite and blah .. blah.. Windows 7 and centos 5.0 have some trouble with lua and mod_magnet so I had to take  mod_rewrite option.

    SOLUTION:


    It did trick when I took code from other site and paste it into settings.php I could able to enable option with the code.

    if (strpos($_SERVER['SERVER_SOFTWARE'], 'LightTPD') !== false) {
    $_lighty_url = $base_url.$_SERVER['REQUEST_URI'];
    $_lighty_url = @parse_url($_lighty_url);
    if ($_lighty_url['path'] != '/index.php' && $_lighty_url['path'] != '/') {
        $_SERVER['QUERY_STRING'] = $_lighty_url['path'];
        parse_str($_lighty_url['path'], $_lighty_query);
        foreach ($_lighty_query as $key => $val)
            $_GET[$key] = $_REQUEST[$key] = $val;
    	}
        $_GET['q'] = $_REQUEST['q'] = substr($_lighty_url['path'], 1);
    }
    }

    but I didn’t know, blind copy paste will make things much uneasier.

  4. While reading drupal view module documents, I figured out I can save my lots of time in most of the projects. So I installed view module and tried to enable frontpage module by clicking enable link inside admin/build/views. But I got the classic 403 error “You are not authorized to access this page.” I tried to search, for the same but couldn’t find the solution anywhere.Drupal View Build 403 errorSo, I open that core module, and checked for the error function in /modules/views/includes/admin.inc and I found that…
    /**
     * Page callback for the Views enable page.
     */
    function views_ui_enable_page($view) {
    
      if (isset($_GET['token']) && drupal_valid_token($_GET['token'], 'views-enable')) {
        $views_status = variable_get('views_defaults', array());
        $views_status[$view->name] = FALSE; // false is enabled
        variable_set('views_defaults', $views_status);
        views_invalidate_cache();
        menu_rebuild();
        drupal_goto('admin/build/views');
      }
      else {
        return drupal_access_denied();
      }
    }

    was creating the problem. Our copy/pasted code wasn’t handling token and destination parameters, causing failure at above function. So I reopen sites/default/settings.php file. and edit the rewrite handling code as follow.

    SOLUTION:

    if (strpos($_SERVER['SERVER_SOFTWARE'], 'LightTPD') !== false) {
    $_lighty_url = $base_url.$_SERVER['REQUEST_URI'];
    $_lighty_url = @parse_url($_lighty_url);
    if ($_lighty_url['path'] != '/index.php' && $_lighty_url['path'] != '/') {
        $_SERVER['QUERY_STRING'] = $_lighty_url['path'];
        parse_str($_lighty_url['path'], $_lighty_query);
        foreach ($_lighty_query as $key => $val)
            $_GET[$key] = $_REQUEST[$key] = $val;
    
            // Note: Lighttpd won't send the querystring attached with clean url
    
    	if(isset($_lighty_url['query']) && $_lighty_url['query'] != ""){
    		parse_str($_lighty_url['query'],$myGET);
    		foreach($myGET as $k=>$v) {
    			$_GET[$k] = $v;
    		}
    	}
        $_GET['q'] = $_REQUEST['q'] = substr($_lighty_url['path'], 1);
    }
    }

    Above final code made my drupal installation on windows running under lighttpd 1.4 for further trouble and solutions… I’ll keep posting.

Handling Thickbox and Facebox jQuery plugins with LiveQuery for AJAX

I found an interesting JQuery plugin to code painless AJAX modules.

It’s called LiveQuery which binds events/callbacks/plugins to the elements, will be loaded into DOM through AJAX requests.

Where to use LiveQuery?

We know that jQuery has most famous ready(); method that executes after DOM completion. So, what’s the point of LiveQuery?

Okay, let’s take an example.

We need to load some healthy amount of HTML elements using AJAX which includes hyperlinks, table, forms etc. and then we want to bind events to them. How would we achieve that?

Simple solution would be write a callback function.

$.get('ajax_url',serialize_params_map,function(data){
// manipulate data variable;
// execute callback to bind events;
});

We may think, it would be great we don’t have to attach manual callback after each and every ajax call and should be simple as

$(document).ready(
  function(){
  //execute callback to bind events;
  }
);

To understand my point more clearly, take a practical example.

  1. ThickBox Problem
    // thickbox.js (jQuery plugin)
    //on page load call tb_init
    $(document).ready(
      function(){
        //pass where to apply thickbox
        tb_init('a.thickbox, area.thickbox, input.thickbox');
        imgLoader = new Image();// preload image
        imgLoader.src = tb_pathToImage;
      }
    );

    Look at line6.
    It only executes one time when DOM get completed.

    What If we need to bind thickbox to new elements, inserted through AJAX request?

    One solution could be call tb_init(‘..’) as “callback function” to every AJAX request

    $.get('ajax_url',serialize_params_map,function(data){
    // manipulate data
      tb_init('a.thickbox, area.thickbox, input.thickbox');
    //pass where to apply thickbox
    });

    or use LiveQuery ONLY ONCE i.e.

    Thickbox Demo

    $('a.thickbox').livequery(
      function(){
        tb_init('a.thickbox');
      }
    );

    So, what would above code do?
    It simulates $(document).ready(); function. Also observers for any “set of newly matched elements” inserted in DOM. So whenever “AJAX request” insert new elements in DOM, above function will bind thickbox to them.

  2. FaceBox Problem
    The same case with facebox… and could be with many more jquery plugins. To make facebox work properly with post loaded HTML elements, use…

    jQuery(document).ready(function() {
    $('a[rel*=facebox]').livequery(
      function(){
        $(this).facebox();
      });
    });

    instead of

    jQuery(document).ready(
      function($) {
        $('a[rel*=facebox]').facebox();
      }
    );

The conclusion is…

jQuery + painless AJAX modules => efficient use of LiveQuery.

Python coded GoogleMini SAYT (Search As You Type) run on Google App Engine

One of my client uses GoogleMini for search infrastructure. His entire site was coded in classic asp. I was hired for making some XSLT changes in frontend. GSA 6.0 has built in facility for query auto-completing. But googlemini doesn’t has such facility. So, Google Staff introduce SAYT (search as you type autocomplete) for googlemini and my client asked me to integrate SAYT with his googlemini. Google Mini SAYT is nothing but AJAX autocomplete script written in Javascript and PHP.

My client’s dedicated server is always heavily loaded with daily millions of page-views. So, SAYT was a kind of burden on his server by putting extra load as user keep sending request on every key stroke. Luckily I found Google App Engine, cloud computing infrastructure way cheaper as you can consume 1GB daily bandwidth free of cost. So then I converted GoogleMini SAYT php code into python and created a simple app engine code so we can host and run SAYT from Google Cloud Infrastructure.

# original file search-responder.php is Copyright (C) 2006 Google Inc. under Apache License, Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0

import os
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext.webapp import template
from django.utils import simplejson

class RPCHandler(webapp.RequestHandler):

  def get(self):
    self.response.headers["Content-Type"] = "text/html; charset=UTF-8"
    self.response.headers["Cache-Control"] = "no-cache"
    self.response.headers.add_header("Expires", "Thu, 01 Dec 1994 16:00:00 GMT")
    # Get the data and the query
    self.data = self.GetData()
    self.query = self.request.get('query').strip().lower()
    # Build Response
    self.responseout = {}
    self.responseout["query"] = self.query
    self.responseout["results"] = self.GetResults()
    if len(self.responseout["results"]) == 1:
      self.responseout["autocompletedQuery"] = self.responseout['results'][0]['name']
    # Output response
    self.response.out.write("searchAsYouType.handleAjaxResponse(")
    self.response.out.write(simplejson.dumps(self.responseout))
    self.response.out.write(");")

  def GetData(self):
    data = []
    data_index = 1
    filedata = open("test-data.txt")
    for line in filedata:
      record = line.strip().split("|")
        if len(record) == 4:
          data.append(record)
    return data

  def GetResults(self):
    results = []
    data = self.data
    queryLength = len(self.query)
    for record in data:
      if record[0].lower().find(self.query,0,queryLength) != -1:
        result = {}
        result['name'] = record[0]
        result['type'] = record[1]
        result['content'] = record[2]
        result['moreDetailsUrl'] = record[3]
        result['style'] = 'expanded' if self.query == record[0].lower() else 'normal'
        results.append(result)
    return results

def main():
  application = webapp.WSGIApplication([('/rpc*', RPCHandler)], debug=True)
  util.run_wsgi_app(application)

if __name__ == '__main__':
main()

Save this code as search-responder.py file, and rest of work is similar to existing setup. Now to run this code from Google App Engine and modify your googlemini custom xslt frontend by these steps.

Note the step:-

<script src="http://localhost/sayt/search-as-you-type.js"></script>

<script>searchAsYouType.initialize(document.getElementById('sayt'), true);</script>

Modify with them with

<script src="http://your-app-engine-path/static_dir/search-as-you-type.js"></script>

<script>searchAsYouType.initialize(document.getElementById('sayt'), true);</script>

Save the Frontend XSLT and you are done.

Google WebMaster API (PHP) for submitting dynamic sitemaps

My New Photo Site has approximately 475 albums. Each album has manually coded RSS feeds. Now submitting that much RSS feeds, one by one, into Google Webmaster Tool’s interface is quiet painful.

So, I digged into Google’s GDATA API Code provided by Zend Framework. It’s very easy to submit google sitemap using php coding.

Steps:-
(1) Download Zend FrameWork
(2) Download GData API
(3) Write the code written @ end of the post into your php file & save it on path /demos/Zend/Gdata & run it.

Google needs RDF data tobe submitted to  https://www.google.com/webmasters/tools/feeds/www.yoursite.com/sitemap using POST request.

RDF format for submitting sitemaps is,

<atom:entry>
<atom:id>http://www.example.com/sitemap-index.xml</atom:id>
<atom:category scheme='http://schemas.google.com/g/2005#kind'
term='http://schemas.google.com/webmasters/tools/2007#sitemap-regular'/>
<wt:sitemap-type>WEB</wt:sitemap-type>
</atom:entry>

You need to change http://www.yoursite.com/sitemap.xml with your sitemap location dynamically using php.

Above XML was creating some parsing errors before submitting POST request because it has no name-space.

So, I googled some different xml for submitting SiteMap,

<entry xmlns="http://www.w3.org/2005/Atom" xmlns:wt="http://schemas.google.com/webmasters/tools/2007">
<id>http://www.yoursite.com/sitemap.xml</id>
<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/webmasters/tools/2007#sitemap-regular'/>
<wt:sitemap-type>WEB</wt:sitemap-type>
</entry>

So final php code became.

<?php

require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Gdata');
Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
Zend_Loader::loadClass('Zend_Gdata_Gapps');

// Provide Google Account Information
$email = 'youremail@gmail.com';
$passwd = 'xxxxxxxx';
$service      = 'sitemaps';

// Try to connect
try {
$client = Zend_Gdata_ClientLogin::getHttpClient($email, $passwd, $service);
} catch (Zend_Gdata_App_CaptchaRequiredException $cre) {
echo 'URL of CAPTCHA image: ' . $cre->getCaptchaUrl() . "n";
echo 'Token ID: ' . $cre->getCaptchaToken() . "n";
} catch (Zend_Gdata_App_AuthException $ae) {
echo 'Problem authenticating: ' . $ae->exception() . "n";
}

add_sitemap($sitemap-location,$client);

function add_sitemap($sitemap-location,$client){
$xml ='<entry xmlns="http://www.w3.org/2005/Atom" xmlns:wt="http://schemas.google.com/webmasters/tools/2007"><id>'.$sitemap-location.'</id>';
$xml.="<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/webmasters/tools/2007#sitemap-regular'/><wt:sitemap-type>WEB</wt:sitemap-type></entry>";
$fdata = new Zend_Gdata($client);
$result=$fdata->post($xml,"https://www.google.com/webmasters/tools/feeds/http://yoursite%2Ecom%2F/sitemaps/",null,"application/atom+xml");
}

?>

Bollysite Goes Mobile

Bollysite Goes Mobile Now, browse bollysite at your fingertips on your mobile…for bollywood latest news, actor and actressesWe are pleased to enter into the mobile sphere by launching bollysite mobile version. It’s interactive and user friendly for easy navigation.

It’s easy and simple, just type www.bollysite.com into your mobile browser to experience bollysite. It is available on all WAP/GPRS enabled mobile phones.

Bollysite Movies featured on Google Code Blog

Here is a project.

Bollysite has lots of cool backend/frontend stuff  to give latest updated news as well as good User Interaction to the user.

Finally, one of my frontend engineering work Bollysite movies, get featured on Google Code Blog.

For every movies information, I didn’t have any image into my database. So, first I tried Flickr API to get relevant images to movies. But Flickr failed to deliver relevant images for old movies.

So, finally I try for Google Image Search API. First result on Google was relevant to every odd/old movies.

Then, I just needed to tweak API to get only first result. I could find that function google.search.ImageSearch.RawCompletion returns raw output.

So, I override that function with my custom function.

google.search.ImageSearch.RawCompletion = function(arg1,arg2,arg3,arg4){
if(typeof(arg2.results[0].tbUrl) != "undefined"){
movieImage = new Image();
movieImage.src = arg2.results[0].tbUrl; // Here first image will come
// now you can use movieImage.src to anywhere....
}
}
google.setOnLoadCallback(OnLoad);

So, now google powers every movie images to BollySite Movies