Vacation Learning - PHP and Smarty Templates

I’m on vacation this week and next week. Since I rarely have time to learn anything technical (or blog for that matter anymore), I thought I would take some time during my time off to learn something new around development.

We have a system at work that is essentially a small portal. The core of it was written by me to learn PHP about 8 years ago and has been augmented by me and one other guy at workKeith and I over the years. Over that time, as we added new functionality to it, I used it to experiment with other languages as I was learning it. Other pieces were written in Java out of convenience. In total, we have pieces written in PHP, Java, Python, and PERL.

As I usually use this system to learn new things, I figured it would be a good candidate to use to learn how to use the Smarty templating system for PHP. I became interested in this templating system after working with Eventum over the last few weeks and figured that if I am going to do further work with Eventum, it would be helpful to understand the templating framework it uses.

So I’ve started using the system to take our 8 year old PHP code base and separate some of the presentation logic out. Smarty is pretty flexible and easy to use at a high level (I haven’t gotten into any of the really advanced stuff yet).

Here’s an example of how nicely the use of a templating system simplifies your code. Take this example, which enumerated entries from our internal wiki via an RSS feed into a section on the home page:


function getWikiEntries($url) {
   $theHTML = "";
 
   $rss = fetch_rss($url);
 
   $theHTML .=  "<table cellpadding=\"0\" cellspacing=\"0\" align=\"center\"><tr>";
   $theHTML .=  "<th CLASS=\"header-title\" colspan=\"2\">";
 
   # get the channel title and link properties off of the rss object
   #
   $title = "Recent Wiki Entries";
   $link = $rss->channel['link'];
 
   #$theHTML .= "<a href=$link>$title</a>";
   $theHTML .= "$title&nbsp;&nbsp;&nbsp;<a href=\"$url\"><img border=\"0\" src=\"" . APP_URL . "/images/rss.png\"/></a>";
   $theHTML .=  "</th></tr><tr><td colspan=\"2\" class=\"modifications-sectionheader\">&nbsp;</td></tr>";
  
   # foreach over each item in the array.
   # displaying simple links
 
   $rowCount = 0;
   $className = "modifications-evenrow";
  
   foreach ($rss->items as $item ) {
 
      if (($rowCount % 2) == 0) {
        $theHTML .= "<tr CLASS=\"$className\">";
      }
      $theHTML .= "<td CLASS=\"modifications-data\">";
      $theHTML .= "<a href=\"$item[link]\" title=\"" . $item['title'] . "\">";
 
        # truncate item title to 28 characters
        $myTitle = $item['title'];
 
        if (strlen($myTitle) > 28 ) {
            $myTitle = substr($myTitle, 0, 28) . " ...";
       }
 
      $theHTML .= $myTitle;
      if (($rowCount % 2) == 0) {
          $theHTML .= "</a>&nbsp;&nbsp;</td>";
      } else {
          $theHTML .= "</tr>";
      }
      $rowCount++;
  
      if ($rowCount == 20):
         break;
      endif;
   }
  
   $theHTML .= "</table>";
  
   return($theHTML);
}
 

I’m sure you can appreciate how hard this would be to maintain, and all of the cruft that has accumulated over the years …

Now take the simplified version (sans error checking), written today in about 10 minutes:


function getWikiEntries($url) {
   $rss = fetch_rss($url);
 
   $template = new TemplateEngine();
  
   $firstColumn = array_slice($rss->items, 0, 10);
   $secondColumn = array_slice($rss->items, 10);
  
   $template->assign("firstColumn", $firstColumn);
   $template->assign("secondColumn", $secondColumn);
   $template->assign("link", $rss->channel['link']);
  
   return($template->renderString("wikiEntries.tpl"));  
}

… along with its corresponding Smarty template:


<table width="80%" cellpadding="0" cellspacing="0" align="center">
<tr><th class="header-title" colspan="2">Recent Wiki Entries&nbsp;&nbsp;
       <a href="{$link}"><img border="0"  src="{$applicationURL}/images/rss.png"/></a></th>
</tr>
<tr><td colspan="2" class="modifications-sectionheader">&nbsp;</td></tr>
 
{section name="entries" loop="$firstColumn"}
    <tr>
    <td class="modifications-data">
    <a href="{$firstColumn[entries].link}" title="{$firstColumn[entries].title}">{$firstColumn[entries].title|truncate:28:" ..."}</a>&nbsp;&nbsp;
    </td>
    <td class="modifications-data">
    <a href="{$secondColumn[entries].link}" title="{$secondColumn[entries].title}">{$secondColumn[entries].title}</a>
    </td></tr>
{/section}
</table>

I don’t know about you, but I think thats quite a difference in maintainability. I’d much rather modify the html in the template than in the original function. Not only that, but the code is actually code, not a bunch of code with a lot of simply horrid markup stuck in the middle of everything.

I’m pretty impressed with how much I’ve been able to use in a short amount of time this week. The libraries are obviously thought out and ramp up time for me was really minimal. I like libraries like that. It also addresses something that has annoyed me for a long time. Embedded HTML is a pain to maintain and I’ve dreaded going into this over the years just because of that.

At some point, I’ll investigate what it takes to write custom plugins, a functionality that the libraries also support.

I think I’ve been able to get a really good start at getting something maintainable. My goal over the next few of weeks is to templatize the whole system, then start taking the non-PHP pieces of the system and rewrite them in PHP. I’ll also add the ability to change configuration in one place, so that we can cut some of the pain that we have in keeping things maintained down - and perhaps be able to install the application in other places.

Should be fun. I’m definitely feeling productive over the past few days. I’ve always liked working in PHP over other languages. I definitely have to do work like this more often.

Related posts

Tagged with: , , ,

LDAP Enabling The Eventum Defect Tracking System

Due to a recent reorg, I have the opportunity to replace our defect tracking system, which has quite a bit of really wasteful process baked into the tool, with a new one. I’ve been looking at defect tracking software for a while, and chose Eventum, an open source project by MySQL AB for a number of reasons, some of them including:

  • Its open source
  • Its written in PHP, so I don’t have to worry about messing with fastcgi, mod_perl, or mod_python
  • It is extensible (you can add custom fields, etc)
  • It uses MySQL, rather than SQLLite or something like that, so we can integrate it into the rest of our home-grown build software
  • It supports email integration. While we won’t be using this right away, we’ll be implementing it in a later iteration
  • Its simple to use, with a very simple interface, once you get use to it. Everything is essentially on one screen.
  • It has time tracking, along with some basic reporting built in

One thing it doesn’t have built in is LDAP authentication. I wrote a previous article about all of the work we’ve done to integrate both our home grown applications and a few open source applications in with our LDAP store, to minimize the management of multiple passwords across systems, so this was very important to me. I started with many, many Google searches to see if someone else has done this, only to hit one dead end after another. At first I was being lazy and decided to just forget about it. One system not tied to the LDAP tree isn’t that big of a deal, but then my perfectionism set in. Why would I settle for that when LDAP authentication should be really easy to integrate into an Open Source package?

So I decided to spend a few hours to get it working. Since I had no success finding an implementation, I figured I could do my part and post what I have. There are a couple of caveats that I want to throw out before we actually get to the code though:

  1. It isn’t done “right”. This is all extra work for me, so I got enough done so that it would work. The right way to do this would to refactor the auth stuff out into a workflow like hierarchy that could be pluggable (see this post in the eventum mailing list). I’ll get to that someday, but right now this solution hacks the auth module to get authentication working.
  2. LDAP Settings are not configurable through the interface. I don’t have time for that, so a set of defines at the top of the LDAPAuthenticator class contains all of the configuration information for the LDAP server. Bummer, but like I said, I’m on a schedule.
  3. Users still have to be added to the Eventum database - they are not added automagically when they log in. I want control of who is in the system, so I’ve elected to leave this functionality out and just do authentication.

With these three caveats in place though, given my experience looking around for this stuff, at least this code works and will be able to be used by others. Its a starting point - which is more than is out there today. Anyone is free to use this and take the time to do it right. With that said, I’d love to receive updates if someone actually takes this up. For now though, this works for me.

So, now to the code. I wrote a small PHP class called “class.LDAPAuthenticator.php. There are two functions in it. Because Eventum uses email address as the login, we need a way to get the full user DN from the email address. This is what the email_to_dn function does. Given an email address, it returns the full distinguished name of the user. This is called by the main class function, ldap_authenticate. The ldap_authenticate function takes the same arguments as the class.auth.php function isCorrectPassword, which consist of the email address and the password. It binds to the LDAP authentication tree using the full DN of the user and the password supplied. If authentication is successful, it returns TRUE, otherwise it returns FALSE, just like the isCorrectPassword function used to validate the password from the Eventum database.

The code looks like this:

# Change these values to access another LDAP server.
define("LDAP_PORT", 636);
define("LDAP_HOST", 'ldaps://ldapserver.example.com:' . LDAP_PORT);
define("LDAP_BIND_DN", 'PUT THE BIND DN HERE');
define("LDAP_BIND_PASSWORD", 'PUT THE BIND PASSWORD HERE');
define("LDAP_SEARCH_DN", "PATH OF THE TREE TO SEARCH FOR USERS");
 
class LDAPAuthenticator {
 
    # Look up a users full distinguised name from
    # their email address, since Eventum uses
    # email address as the login name.
    function email_to_dn($emailAddress) {
        $returnDN = "";
        
        $server = ldap_connect(LDAP_HOST);
        
        if ($server == FALSE) {
            return($returnDN);
        }
                
        ldap_set_option($server, LDAP_OPT_PROTOCOL_VERSION, 3) ;
    
        $ldapbind = ldap_bind($server, LDAP_BIND_DN, LDAP_BIND_PASSWORD);
    
        # verify binding
        if ($ldapbind) {
            #  find the user based on the entered email address.
            $result = ldap_search($server,
                                  LDAP_SEARCH_DN,
                                  "(&(mail=$emailAddress))",
                                  array("dn"));
    
            $info = ldap_get_entries($server, $result);
            
            # if we actually got a value back, return the users DN
            if ($info["count"] > 0) {
                $returnDN = $info[0]["dn"];    
            }
    
            ldap_unbind($server);
        }
        
        return($returnDN);
    }
    
    # Authenticate with the LDAP server.  Function returns true
    # if authentication was successful, false otherwise.
    function ldap_authenticate($email, $password) {
        $returnValue = FALSE;  
        $userDN = LDAPAuthenticator::email_to_dn($email);
        
        if ($userDN == "") {
            return($returnValue);
        }
        
        $server = ldap_connect(LDAP_HOST);
        
        if ($server == FALSE) {
            return($returnValue);
        }
        
        ldap_set_option($server, LDAP_OPT_PROTOCOL_VERSION, 3) ;
    
        $ldapbind = ldap_bind($server,
                              LDAPAuthenticator::email_to_dn($email),
                              $password);
    
        if ($ldapbind) {
            $returnValue = TRUE;
            ldap_unbind($server);
        }
        
        return($returnValue);  
    }
}

Save this file as class.LDAPAuthenticator.php and put it in your Eventum includes directory. Modify the define statements at the top to contain your LDAP server information.

Now, to use it. Go to your Eventum includes directory and add the following line to the top of the class.auth.php file:

require_once(APP_INC_PATH . "class.LDAPAuthenticator.php");

I have this at the end of all of the rest of the require statements.

Now, replace the isCorrectPassword function in class.auth.php with the following function:
    /**
     * Checks whether the provided password match against the email
     * address provided.
     *
     * @access  public
     * @param   string $email The email address to check for
     * @param   string $password The password of the user to check for
     * @return  boolean
     */
     function isCorrectPassword($email, $password) {
         return(LDAPAuthenticator::ldap_authenticate($email, $password));
     }

… and VOILA. You can now authenticate off of your LDAP tree.

Now, I know it isn’t pretty, hacking the code directly - but it works, and its more of a starting point than I can find anywhere else. I hope its useful to others. Again, if anyone takes this further and does it “right”, I would be really happy to get a copy of the modifications.

One more thing - don’t forget to require SSL on the URL to your Eventum installation by using the SSLRequireSSL directive in your Apache server. You don’t want these passwords floating around in the clear across the network.

Download the Eventum LDAP hack here and happy authenticating.

Related posts

Tagged with: , , , , , , ,

Video: How Open Source Projects Survive Poisonous People (And You Can Too)

Since getting a 80G iPod about a month ago two weeks ago, I’ve been really getting into watching the Google Tech Talks on Google Video. I recently watched How Open Source Projects Survive Poisonous People (And You Can Too), a lecture given by Brian Fitzpatrick and Ben Collins-Sussman from the Subversion team (now both Google employees) that summarizes a lot of information in Karl Fogels book Producing Open Source Software: How to Run a Successful Free Software Project.

If you haven’t had time to pick up and read Karls book, this video would be a good primer to some of the concepts in it and could very well motivate you to pick it up. Its an excellent book and one that I thoroughly enjoyed reading.

Related posts

Tagged with: , , , , ,

  • Jonna pointed me to Chumby, an open source alarm clock. According to the article, the clock will run for $150 and because of its open source nature, will have an “array of downloadable, hackable widgets”. (1)
  • Got an email this morning stating that SQLYog, an excellent MySQL client comparable to TOAD for Oracle, is now open sourced. Go grab it! Comments Off
  • In this article, Shai Agassi predicts that the open source database MySQL will be certified to run SAP by the end of the year. Comments Off
  • OnLamp had an excellent article yesterday called What Corporate Projects Should Learn from Open Source. The articles pretty long, but well worth the read. While there are obvious differences in the two types of projects (like budgets and deadlines), I still believe that corporations can move closer to the OSS model of development and get major productivity increases. Comments Off

Transparent Commodity Infrastructure and Web 2.0

Tom the Architect pointed me over to this article called Transparent Commodity Infrastructure and Web 2.0. Excellent piece.

I especially like this quote here:

Let me use an example: back in 1998 if you were building a web-based startup, you were probably running on Solaris/SPARC and using an Oracle database. You were also likely to be running on some sort of a Java servlet engine (though there were exceptions, this was again the leading edge). This huge apparatus usually required at least 1 of the following: DBA, sys-admin, release manager, and build manager– nevermind all of the consultants and vendor people that it took to solve problems that arose from trying to get everything working together.

Fast forward to 2005. Anyone still using Solaris/SPARC for web apps is either a moron or a depressed Sun shareholder. MySQL and Postgres are now considered “enterprise-grade,” and if you should be so masochistic as to still want to do Java development on the app-tier, you’ve got Tomcat, Jetty, and even JBOSS available to you on your platform of choice.

I couldn’t agree more. So many companies stuck in the 90’s … excellent article and worth a full read.

Related posts

Tagged with: ,

  • This article over on The Register talks about the latest version of Mozilla Thunderbird and its support of RSS and, yes folks, podcasting. For more information, check out the release notes for the 1.5 release. Comments Off
  • I posted about the initial release of this software a while back, but I’m really getting the urge to see what it would take to use JavaSVN, a 100% Pure Java implementation of Subversion as the beginnings of a content management solution. Version 1.0.1 was announced yesterday afternoon. Comments Off
  • This article talks about the rise in VC interest in companies whose products are based on an open source model. Comments Off