Import contacts using Google’s API

Maybe the most important thing about this post is how to use HTTP based APIs in a simple way -using a wrapper for cURL PHP extension. Most of the examples available on the Web are kind of out-of-date, based on the idea that there is no a better abstraction layer.

Requirements / Example environment configuration.

This example was originally built for CakePHP, but as you can see, it can be used or migrated to several frameworks – the key is to use the PEAR component. Note: I believe that this example will make most of the example out there crappy – and I hope it, so other developers could use it.

 
// PEAR Package HTTP_Request2
// http://pear.php.net/manual/en/package.http.http-request2.php
require_once('HTTP' . DS . 'Request2.php');
 
class GoogleImportComponent extends Object {
 
	private $username = null;
	private $password = null;
	private $token = null;
 
	/**
	 * Authenticate user credential against provider services
	 *
	 * @param string $username User's login
	 * @param string $password User's account password
	 * @see http://code.google.com/apis/contacts/docs/3.0/developers_guide_protocol.html
	 */
	function authenticate($username, $password) {
		$this->username = $username;
		$this->password = $password;
 
		$hrc = new HTTP_Request2('https://www.google.com/accounts/ClientLogin', HTTP_Request2::METHOD_POST);
		$hrc->setAdapter('HTTP_Request2_Adapter_Curl');
		$hrc->setConfig('ssl_verify_peer', false);
		$hrc->addPostParameter('accountType', 'HOSTED_OR_GOOGLE');
		$hrc->addPostParameter('service', 'cp');
		$hrc->addPostParameter('source', 'MY_APP_ID');
		$hrc->addPostParameter('Email', $this->username);
		$hrc->addPostParameter('Passwd', $this->password);
 
		$response = $hrc->send();
		$body = $response->getBody();
		if ($response->getStatus() == 200) {
			$this->token = substr($body, strpos($body, 'Auth=') + 5);
		}
		else {
			throw new Exception($response->getReasonPhrase(), $response->getStatus());
		}
	}
 
	/**
	 * Return the contacts' email list
	 *
	 * @return array
	 * @see http://code.google.com/apis/contacts/docs/3.0/developers_guide_protocol.html
	 */
	function get() {
		$addresses = array();
 
		$hrc = new HTTP_Request2("http://www.google.com/m8/feeds/contacts/{$this->username}/full", HTTP_Request2::METHOD_GET);
		$hrc->setHeader('Authorization', "GoogleLogin auth={$this->token}");
		$response = $hrc->send();
		if ($response->getStatus() == 200) {
			$xml = simplexml_load_string(utf8_encode($response->getBody()));
			$xmladd =  $xml->xpath("//gd:email/@address");
			foreach ($xmladd as $value) {
				$data = get_object_vars($value);
				$addrs = $data['@attributes']['address'];
				if ($addrs != $this->username) {
					$addresses[] = $addrs;
				}
			}
		}
		else {
			throw new Exception($response->getReasonPhrase(), $response->getStatus());
		}
 
		return $addresses;
	}
}

That’s it. It is not a huge amount of code with HTTP protocol related instructions – like the ones you can get for free. It relies on HTTP_Request2 PEAR module, that even if it’s on beta, works like a charm.


Search Portlet

Few months ago I was developing a project on top of JBoss Portal. Unfortunately, JBoss Portal 2.7.x is a nice Portlet Container but a crappy CMS/Search engine -even when it is supported on Apache’s JackRabbit. However, I was forced to develop a search portlet. It wasn’t easy, and you will probably find that it is not a perfect solution. However I really hope you find this Search Portlet really useful.

SearchPortlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package portal.portlets;
 
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
 
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.PortletSecurityException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.UnavailableException;
import javax.portlet.WindowState;
 
import org.apache.log4j.Logger;
import org.jboss.portal.cms.CMS;
import org.jboss.portal.cms.CMSException;
import org.jboss.portal.cms.Command;
import org.jboss.portal.cms.model.File;
import org.jboss.portal.core.cms.ui.CMSPortlet;
import org.jboss.portal.search.FederatedQuery;
import org.jboss.portal.search.QueryConversionException;
import org.jboss.portal.search.federation.SearchFederation;
import org.jboss.portal.search.impl.jcr.JCRQuery;
import org.jboss.portal.search.impl.jcr.JCRQueryConverter;
 
public class SearchPortlet extends CMSPortlet {
 
  private static Logger log = Logger.getLogger(SearchPortlet.class);
  private SearchFederation SearchService;
  private CMS CMSService;
 
  public void init(PortletConfig config) throws PortletException {
    super.init(config);
  }
 
  public void destroy() {
    super.destroy();
  }
 
  /*
   * (non-Javadoc)
   *
   * @see javax.portlet.Portlet#init()
   */
  @Override
  public void init() throws PortletException {
    this.SearchService = (SearchFederation) getPortletContext().getAttribute("SearchFederationService");
    this.CMSService = (CMS) getPortletContext().getAttribute("CMS");
 
    if (this.SearchService == null) {
      throw new PortletException("Unable to start search service");
    }
 
    if (this.CMSService == null) {
      throw new PortletException("CMS services not available");
    }
 
    super.init();
  }
 
  /*
   * (non-Javadoc)
   *
   * @see javax.portlet.GenericPortlet#doView(javax.portlet.RenderRequest,
   * javax.portlet.RenderResponse)
   */
  @Override
  protected void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException, UnavailableException {
 
    String portalInstance = request.getWindowID().substring(0, request.getWindowID().indexOf('/', 1));
    PortletRequestDispatcher prd = null;
    String operation = (String) request.getParameter("op");
 
    if (operation != null && operation.equals("search")) {
      String queryString = (String) request.getParameter("query");
      if (queryString != null && queryString.length() > 0) {
        request.setAttribute("files", this.getFiles(queryString, portalInstance));
        prd = this.getPortletContext().getRequestDispatcher("/WEB-INF/jsp/results.jsp");
      } else {
        prd = getPortletContext().getRequestDispatcher("/WEB-INF/jsp/search.jsp");
      }
      prd.include(request, response);
    } else if (operation != null && operation.equals("display")) {
      super.doView(request, response);
    } else {
      prd = getPortletContext().getRequestDispatcher("/WEB-INF/jsp/search.jsp");
      prd.include(request, response);
    }
  }
 
  /*
   * (non-Javadoc)
   *
   * @see javax.portlet.Portlet#processAction(javax.portlet.ActionRequest,
   * javax.portlet.ActionResponse)
   */
  @Override
  public void processAction(ActionRequest request, ActionResponse response) throws PortletException, PortletSecurityException, IOException {
    String profile;
 
    String operation = (String) request.getParameter("op");
    if (operation != null && operation.length() > 0) {
      if (operation.equals("ask")) {
        response.setWindowState(WindowState.NORMAL);
      } else {
        response.setWindowState(WindowState.MAXIMIZED);
      }
      response.setRenderParameter("op", operation);
    } else {
      response.setRenderParameter("op", "ask");
      response.setWindowState(WindowState.NORMAL);
    }
 
    if (operation.equals("search")) {
      String queryString = (String) request.getParameter("query");
      if (queryString != null && queryString.length() > 0) {
        response.setRenderParameter("query", queryString);
      }
    }
  }
 
  private List getFiles(String queryString, String portal) {
    List files = null;
    List filteredFiles = new ArrayList();
 
    if (queryString != null && queryString.length() > 0) {
      FederatedQuery query = new FederatedQuery(queryString);
      JCRQueryConverter converter = new JCRQueryConverter();
 
      try {
        Command searchCommand = CMSService.getCommandFactory().createSearchCommand((JCRQuery) converter.convert(query));
        files = (List) CMSService.execute(searchCommand);
 
        // You can filter files if needed by reviewing its path under the CMS
        // structure, by name, or any other criteria that fits your needs.
 
      } catch (CMSException e) {
        log.error("CMS Error", e);
      } catch (QueryConversionException e) {
        log.warn("Conversion Error", e);
      }
    }
 
    return files;
  }
}

I used two JSP pages to support this portlet.

  • search.jsp – Search form – the standard portlet view
  • results.jsp – Results list page – appears with the portlet MAXIMIZED and displays CMS files that matches the criteria

search.jsp – (WP-Syntax does not support JSP)

1
2
3
4
5
6
7
8
<h2>Search</h2>
<form action="<%=contactActionURL%>" method="POST">
  <fieldset style="border: none;">
    <input name="op" type="hidden" value="search" />
    <input name="query" type="text" />
    <input type="submit" value="Search" />
  </fieldset>
</form>

results.jsp – (WP-Syntax does not support JSP)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@ page import="org.jboss.portal.cms.model.File"%>
<%@ page import="java.util.List"%>
<%@ page language="java" extends="org.jboss.portal.core.servlet.jsp.PortalJsp"%>
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>
<%
  List<File> files = (List<File>) request.getAttribute("files");
  if (files != null && files.size() > 0) { %>
<h1>RESULTADOS DE B&Uacute;QUEDA</h1>
  <%
    for (int i = 0; i < files.size(); i++) {
        File file = files.get(i);
  %>
  <h2><%= file.getName() %></h2>
  <p><% if (file.getDescription() != null && file.getDescription().length() > 0) { %><%= file.getDescription() %><% } %><a href="<portlet:actionURL><portlet:param name="op" value="display" /><portlet:param name="file" value="<%= file.getBasePath() %>" /></portlet:actionURL>">Ver m&aacute;s...</a></p>
  <hr />
  <%
    }
  %>
<% } else { %>
<h1>NO HAY RESULTADOS</h1>
<% } %>
<p align="center"><a href="<portlet:actionURL><portlet:param name="op" value="ask" /></portlet:actionURL>">Volver</a></p>

Include the following lines on your jboss-portlet.xml file.

1
2
3
4
5
6
7
8
9
10
11
<service>
  <service-name>CMS</service-name>
  <service-class>org.jboss.portal.cms.CMS</service-class>
  <service-ref>:service=CMS</service-ref>
</service>
 
<service>
  <service-name>SearchFederationService</service-name>
  <service-class>org.jboss.portal.search.federation.SearchFederation</service-class>
  <service-ref>:service=SearchFederationService</service-ref>
</service>

And finally add the following lines on your portlet.xml file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<portlet>
  <description>DESC</description>
  <portlet-name>SearchPortlet</portlet-name>
  <display-name>NAME</display-name>
  <portlet-class>portal.portlets.SearchPortlet</portlet-class>
  <supports>
    <mime-type>text/html</mime-type>
    <portlet-mode>VIEW</portlet-mode>
  </supports>
  <resource-bundle>Resource</resource-bundle>
  <portlet-info>
    <title>SEARCH</title>
  </portlet-info>
  <portlet-preferences>
    <preference>
      <name>indexpage</name>
      <value>/default/index.html</value>
      </preference>
    <preference>
      <name>setBrowserTitle</name>
      <value>false</value>
    </preference>
  </portlet-preferences>
</portlet>
 
<filter>
  <filter-name>JBoss Portlet Filter</filter-name>
  <filter-class>org.jboss.portlet.filter.JBossPortletFilter</filter-class>
  <lifecycle>ACTION_PHASE</lifecycle>
  <lifecycle>RENDER_PHASE</lifecycle>
</filter>
 
<filter-mapping>
  <filter-name>JBoss Portlet Filter</filter-name>
  <portlet-name>SearchPortlet</portlet-name>
</filter-mapping>

Team Building vs. Labor Climate

Few days ago I had a conversation with one of the most active Bloggers I had ever known: Bas de Baar about project management in Colombia.I’m not the most experienced project manager here in Colombia, but probably I had worked for few companies that provide me with a very well understanding of what is going on with project management in Colombia.

Project Management isn’t new in Colombia. At least for IT and software related companies Project Managers have been there forever (due to the nature of the IT and software development processes). But, there is something missing about PM here in Colombia: Team Building.

I do believe that Labor Climate and Team Building aren’t the same. As I said to Bas, almost all Colombians -good, not so good, and bad ones- will try to be your friend. Colombian people is really appreciated because of their willingness and attitude of service and camaraderie (did I write it right?). However those are two different things: try to be your friend, and be a good boss, project manager or director.

And that’s why Labor Climate seems to be amazing on Colombian SW development related companies even when “team building” is not a priority. And it could be good, because of its impact on the organizational behavior, improve the labor climate will probably  boost the team efficiency and collaboration. But, not to perform team building activities will be bad for long-term employee & employers relationship, because “personal” friendship isn’t necessarily based on the idea of personal and professional growth and career development.

Furthermore,  SW developers, designers, and architects, and IT project managers, prefers to have project-based relationships with their employers. And for those who know, contractors, even if they are good doing what they do, increase the probability of certain risks to occur -example: dependency on people because there is no knowledge spread opportunity.

To conclude. there is a big difference between “having a great time with amazing people at the office” and “having a great work experience”. It should be a balance between labor climate and team building to ensure a short-term motivation, but also a long-term relationship between employers and employees.


Automagic Platforms

Few years a go, Ruby on Rails (RoR) start shinning because it was only framework able to speed up the Web development with a set of all-in-one tools. As Java experienced programmer and architect, I’ve to recognize that the RoR team made their point. I still remember that video with a guy with a lot of jars on top of a table (check it here) that made me LoL for a while -an awful truth. Now we have a lot of solutions that offers similar out-of-the-box scripts and frameworks that build code as RoR does -the fancy but power/useful scaffolding, the code generators, and others. Few of them:

The list could be huge, but the main point is not to check them all but to provide few tips to start a Web-based project and choose the right tool.

The .NET and Java mistakes
I do love Java, I’m a fan-boy of JEE technology and portability but, I’ve to say, even if it hurts, Java is a huge rock. It means you need to have a huge infrastructure, a huge processing capacity and a lot of tools to start a JEE project. Why? Let’s face it, you need more than your imagination to start a project and it isn’t easy nor cheap to find a great hosting solution, to properly setup an environment for development (with sandbox and preproduction/test environments). Even if your project is really small it is a good idea not to have the test environment on the developer computer -I hope you don’t think this is a joke, it happens ALL the time.

So, instead of being easy to start with a basis, you have to deal with complex configuration problems -it is good to have all those options if you are on a banking/financial project with a couple millions as budget, but in a real economic-crisis world you should look for “point-n-shoot” solutions -if you know what I meant.

The Apache Friends
So you have a lot of alternatives on the Apache HTTPD side, with PHP (the popular), Perl (the bad cousin), Python (the genius), RoR (the cocky brother) and all others.

I’m not a scripter guy, the compiling hell is for me but, the simplicity shows us that you don’t have to deal with complexity all the time for Web projects. So, give non-corporate-supported technologies a chance. It was hard to me, but they worked faster and cheaper than expected -there is not measure to say better. It is cheaper to create a top-class development and testing environment and work with remote teams if you use open solutions -not necessarily open source. It is not all about the language you love, the server you own, the database that rules the world. It is about cost, simplicity and efficiency. It is easier to get/hire great developers on these tools than for Java – .NET is for chickens and everybody knows it (check this article if you do not believe me). Software development is not about coding, is about solving, that’s what Microsoft doesn’t understand. They code a lot but they do not solve too much, right? You should notice since Windows 95 and the coming of the Blue Screen ERA!

The Automagic
Before I became a senior developer I worked migrating some applications from Oracle Forms and PowerBuilder to J2EE -we used to put a horrible 2 in the middle few years ago. And those tools were great for the task they were created. So it took almost a decade to have those tools on the Web -and they do not work as fine as those mentioned. However, the Automagic isn’t new at all. ActiveRecord exists since, I don’t know, the Z81 maybe? He He He!

So now we have tools that reverse the database and create ORM models, pre-formatted pages and objects that speed up -years- the development. So you can concentrate in other IMPORTANT STUFF like, User Experience (UX), Information Architecture (IA) and the forgotten design.

The generated code is clean as possible (I remember the JBuilder and its generated & encrypted code). And this is thanks to the separation of concerns and excellent pattern implementations – the commonly used for the Web is the MVC supported with other ones like ActiveRecord and IoC, but you can find a whole universe on each platform and framework – I do have an old poster of J2EE patterns as wallpaper for example!

The Verdict
Before fan-boys appears into the scene while you are deciding what platform should use, please take a look to the ones you probably will not look at -don’t waste your time with .NET :-) . During these 7 (should I said 8?) years working on Web development I’ve noticed that only big companies uses robust (slow development) platforms like Java and .NET -by big  companies I meant with more than couple billions of dollars on their budgets. They spent a lot of money in servers and infrastructure and ironically most of the time they use always versions old versions of platforms, servers, databases and all. So even if they spent a lot of money they cannot move as fast as it happens with the technology. Microsoft still produces Adobe Flash banners right, even if they have their own Silverlight. And it also happens with JavaFX. When you develop Flash components some companies still requests Flash 6 or 7, when we all know that we have version 9 and 10 running on most of the computers on the web. But hey, they want to be sure and even those computers that are off since 1995 can see them! – why?

Before you spent more then 6 months working on a project without a single release -could be a partial one, but functional- keep in mind that probably it will take other 6 months before you see the product/result you are expecting. Automagic means fast, so I hope you do not spend more time thinking on if you should or not, and how, than coding… Otherwise it will demostrate my point :-)


Facebook & CodeIgniter – Definitive Guide

I spent more hours than the expected starting a simple development for a new Facebook application that runs on top of CodeIgniter. First I read A LOT, then I checked for solutions and, of course, I read a little bit more. After several hours reading and coding I knew it wasn’t good at all so I decided to start a new integration method from the scratch using only the code I found as guide. Here’s the result:

0. Environment

Before you get excited just review a few things before spent time reading another useless post :-)

  • PHP Version 5.2.6
  • MySQL 5 (It doesn’t affect Facebook integration at all but it is good to know)
  • HostMonster is my hosting provider (it is not a marketing initiative, just they have a nice environment set up for PHP)
  • Facebook platform 5 – I upload a copy of it to ensure you get the same copy I used.
  • CodeIgniter 1.6.3 – latest version available at the time of this post.

1. Do not hack CodeIgniter

I found a lot of solutions about hacking CodeIgniter by overriding functions or classes. NO, YOU DO NOT HAVE TO DO IT.

2. Install facebook library as plugin

  • Download the facebook platform ZIP
  • Unzip the files wherever you want -outside your Code Igniter application
  • Copy the WHOLE content under /php folder (under means files inside the folder and not with the container folder) to yout ./system/plugins folder inside Code Igniter application -Note: Yes, you have to include the jsonwrapper folder.
  • Rename the facebook.php file to facebook_pi.php

It is done! You already have installed facebook framework as plugin.

3. Autoload facebook plugin [optional]

To avoid the need of including the “load plugin” sentence inside every controller/class you can modify the ./system/config/autoload.php to include facebook plugin as required. It is almost a requirement for Facebook applications. Add to your $autoload['plugin'] array the facebook plugin. It could look like this:

$autoload['plugin'] = array('facebook');

Note: If you skip this step you will have to include the following line every time you need facebook functionality available.

$this-&gt;load-&gt;plugin('facebook');

4. Setup your application [updated]

Update your configuration file and set the ./system/application/config/config.php and change it as follows:

$config['uri_protocol'] = "PATH_INFO";
$config['base_url'] = "APPLICATION_URL";

Note: If you want to test your application locally, you can set base_url to http://127.0.0.1/path_to_your_app/

5. Extend your Controller class

Because I’m building a 100% facebook application, all my controllers require facebook API available. So I decided to extend my main Controller class as follows – BTW: By the date of this post the CodeIgniter documentation WAS WRONG about how to extend the core classes (CodeIgniter User Guide Version 1.6.3 – Creating Core System Classes). It is not true -i.e. it is false- that you have to extend the CI_ClassName. At least it didn’t work for me at all. Extend the ClassName directly.

Facebook Controller class should be placed under ./system/application/libraries folder.

Filename: XX_Controller.php (where “XX_” is the prefix you set on your ./system/application/config/config.php file – subclass_prefix parameter)

class FB_Controller extends Controller {
 
	// Facebook application key
	var $API_KEY = 'YOUR_API_KEY';
 
	var $facebook;
	var $uid;
 
	/*
	 * Custom Controller constructor.
	 * Adds Facebook support.
	 *
	 */
	function FB_Controller() {
 
		parent::Controller();
 
		// Authentication key
		$secret = 'YOUR_SECRET_KEY'; 
 
		// Prevent the 'Undefined index: facebook_config' notice from being thrown.
		$GLOBALS['facebook_config']['debug'] = NULL;
 
		// Create a Facebook client API object.
		$this-&gt;facebook = new Facebook($this-&gt;API_KEY, $secret);
		$this-&gt;uid = $this-&gt;facebook-&gt;require_login();
	}
}

Note: Inside the facebook application I built I always require an authenticated users, so that’s why I do have the require_login() call. However you can also validate if the user already have the application added. You should do this manually inside controller classes because there are a few exceptions where it is needed that user is authenticated but without adding the application.

6. Create your own controllers

Now you can extend your controllers from your Custom Controller. Note: You do not have to add any include or require sentence in your other classes, CodeIgniter will load directly your XX_Controller.php file -if it doesn’t load please check the config.php file and validate the subclass_prefix parameter.

Below you will find an example of a Controller that will validate if the user has the application added in his/her profile.

class Welcome extends XX_Controller {
 
	function Welcome() {
 
		parent::XX_Controller();
 
		// Check if the application has been added by the user
		try {
			if (!$this-&gt;facebook-&gt;api_client-&gt;users_isAppAdded()) {
				$this-&gt;facebook-&gt;redirect($this-&gt;facebook-&gt;get_add_url());
				return;
			}
		}
		catch (Exception $x) {
			// Clear cookies for your application and redirect them to a login prompt
			$this-&gt;facebook-&gt;expire_session();
			$facebook-&gt;redirect($this-&gt;facebook-&gt;get_login_url());
		}
	}
 
	// You should place your Controller's methods below.
 
}

7. Get out and celebrate

CodeIgniter is running as cleaner as you expected and Facebook API was included nicely!

I hope you find this post useful as I said at the beginning.

  PHP Client Library (34.3 KiB, 935 hits)