Java Platform, Enterprise Edition

Java EE Journal

Subscribe to Java EE Journal: eMailAlertsEmail Alerts newslettersWeekly Newsletters
Get Java EE Journal: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn


J2EE Journal Authors: Stackify Blog, Sumith Kumar Puri, Javier Paniza, Yakov Fain, Ken Fogel

Related Topics: Java EE Journal, PHP Developer's Journal

J2EE Journal: Article

X-oops, I did it again

The X-oops and E-Xoops packages allow for easy code-modification and less mess when compared to PHP-Nuke

(LinuxWorld) — One of my non-profit Web sites, VarLinux.org, ran on my modified version of the PHP-Nuke weblog package from the site's inception in March 2001 until late November 2002. I chose PHP-Nuke as a starting point because I was very impressed with it. However, the more I learned how to use PHP, the more I realized that PHP-Nuke was not only a tangled mess but that I had made it even worse with my modifications. A year later, I was faced with the fact that VarLinux.org was not only lame because it was based on PHP-Nuke, it was also showing its age. It lacked features of other weblogs, and still lacked many of the features I had intended to add to the site.

If I were going to fix the code, the first thing I would do would be to make it an object-oriented system. I toyed with the idea of converting my code into OOP-based code, but I concluded that if the PHP-Nuke code base from which VarLinux.org began was at one time worth the effort to convert into an object-oriented system, I had mangled the code enough to make it a waste of time.

So a few weeks ago, I decided that I would replace the mess that VarLinux.org had become. I stumbled upon two almost identical packages, X-oops and E-Xoops. Both are portals, which are expanded versions of weblogs. Both are use object-oriented PHP, hence the name X-oops: eXtensible Object Oriented Portal System. The additional "E" in E-Xoops is supposed to stand for "e-business."

I started by examining the E-Xoops code. The structure of the database suggested E-Xoops had roots in PHP-Nuke. The X-oops source code confirmed this. It documents the evolution of the project from Thatware to PHP-Nuke to myPHPNUKE and finally to X-oops, so it looks like others wanted to migrate PHP-Nuke to object-oriented code and had the time and energy to make it happen.

E-Xoops could be golden or it could be spaghetti code, but I figured the best way to find out would be to try to make it work with my existing data and then start modifying the code to add the features I wanted.

Thanks to the similarities in the data structure, I was able to migrate my entire VarLinux.org database to the one used by E-Xoops in just one afternoon. All it took was a hack of SQL commands to move the data while keeping the information from the original site intact. The whole process might have taken an hour if it weren't for some changes they made to the data types in the tables. I also ran into some MySQL limitations along the way. Nevertheless, it was surprisingly easy to move the data. It might have been an even easier transition if I hadn't modified the VarLinux.org PHP-Nuke database to accommodate a bunch of features I had added to PHP-Nuke. These changes to PHP-Nuke made the database structures less similar than they would have been had I left PHP-Nuke alone.

Unfortunately, there are still many features missing from E-Xoops that I feel are essential for a decent site. Weblogs present a table of summaries or comments, generally about stories published on other sites. If you want to read the off-site story, you have to click on a link that takes you there. That link usually appears somewhere within the comment submitted by the user of the weblog. The problem is that many users submit comments with multiple links. If the person who submits the comment is clever enough, it will be obvious which link takes you to the story that is primarily under discussion. Even then, if the Web site uses HTML or cascading style sheets to make links less than obvious, it makes the site difficult to navigate in spite of the best attempts by the person submitting a link.

Personally, I find that whole approach unintuitive, so I provided a way for the submitter to provide the URL that takes you directly to the story. My software modifications use the URL to make the title of every story a link to the off-site story. It should be more obvious to click on the title of every story to get to the article than to hunt through the comment for a link.

Use the source, Luke

This approach has a couple other added benefits. Since my software can identify the URL for the story, it can dissect the URL to discover what the source for that story may be. My software recognizes sources such as LinuxWorld, Linux Today and other magazines, so it is able to automatically present the reader with a link to the home page of those off-site magazines.

Here's the code I use to pull the information from the URL.

$source = "";
$storyurl = $story->storyurl();
if ($storyurl) {
  $sa = parse_url($storyurl);
  $thehost = $sa['host'];
  if (stristr($thehost,"www")) {
    $thehost = stristr($thehost,".");
    $thehost = substr($thehost,1,strlen($thehost));
  }
  $result = mysql_query("select source from xoops_source where domain='$thehost';");
  if ($result) {
    list($sourcename)=mysql_fetch_row($result);
    if ($sourcename) {
      $source = "Source: <a href=\"http://$sa[host]\">$sourcename</a>";
    } else {
      $source = "Source: <a href=\"http://$sa[host]\">$thehost</a>";
    }
  } else {
    $source = "Source: <a href=\"http://$sa[host]\">$thehost</a>";
  }
}

Now let's take a look at it again, in detail (with comments), assuming the story URL is "http://www.linuxworld.com/site-stories/2002/1203.barr.html":

$source = "";
// Start with empty source in case this is a local story
$storyurl = $story->storyurl();
// If the field storyurl is empty, it is local
if ($storyurl) {
  $sa = parse_url($storyurl);
// parse_url is a PHP feature that breaks up a URL into separate parts
  $thehost = $sa['host'];
// we're interested in the host name www.linuxworld.com
  if (stristr($thehost,"www")) {
    $thehost = stristr($thehost,".");
    $thehost = substr($thehost,1,strlen($thehost));
  }
// Not everyone precedes host names with www, so we remove the www. 
// to get to the least common denominator for the host (least, that is
// in terms of what we want for the purpose of our lookup table)
  $result = mysql_query("select source from xoops_source where domain='$thehost';");
// We look up linuxworld.com, and find our entry in the MySQL table
// which gives us the proper name for the site, LinuxWorld
// Then we format it with a link to the main site for use in our
// table of contents. Done!
  if ($result) {
    list($sourcename)=mysql_fetch_row($result);
    if ($sourcename) {
      $source = "Source: <a href=\"http://$sa[host]\">$sourcename</a>";
    } else {
      $source = "Source: <a href=\"http://$sa[host]\">$thehost</a>";
    }
  } else {
    $source = "Source: <a href=\"http://$sa[host]\">$thehost</a>";
  }
}

The above code isn't appropriate as-is for use with Xoops or E-Xoops. These programs provide abstract functions for accessing the database, and one should use them whenever possible. I used direct MySQL functions above, but that should be enough to show you the basic logic one may use to accomplish the desired task.

Better popularity tracking

Most importantly, having access to the URL in the database makes it easy to keep track of what people like to read. Normally, weblogs only keep track of the number of times people view an expanded version of the weblog entry. If there are no reader comments, and there is no extra text for the entry, people have no reason to view this page. You can have a situation where readers find an article interesting, but the hit counter never increments to indicate that people liked it.

That's where it really comes in handy to store the URL separately from the comment. Whenever you click on the title of the story in the table of contents, my software appears to take you directly to the off-site article. However, what it actually does is pass the story ID to a tiny PHP program that increments the counter for the article and then redirects you to the story off-site. Now I can see not only how many times people read the comments others make, but also how often people click through to the articles on other sites. This gives me a better idea about what kinds of stories VarLinux.org readers like best.

Each story title has a link that boils down to something like the following:

<a href="http://www.varlinux.org/vlox/html/modules/news/redirect.php?storyid=1541" title="http://www.theregister.co.uk/content/53/28472.html">Open Source J2EE 1.4 gets Sun green light</a>

Notice that the "href" attribute is set to my redirect.php program, not the article URL. It passes the storyid field to redirect.php, which will redirect your browser to the correct off-site article. Why do I set the "title" attribute of the link to the URL of the off-site article? Simple. When you place the mouse pointer over the link, you'll see the correct URL as the tooltip. This information is more useful to the reader than the redirect.php URL, which would normally appear by default.

Here's the code in the redirect.php program:

<?php
include_once("header.php");
include_once(XOOPS_ROOT_PATH."/modules/".$xoopsModule->dirname()."/cache/config.php");
include_once("class/class.newsstory.php");

$storyid = (!empty($storyid)) ? intval($storyid) : 0; $item_id = (!empty($item_id)) ? intval($item_id) : 0;

if ( empty($storyid) && empty($item_id) ) { redirect_header("index.php", 2, _NW_NOSTORY); exit(); }

$item_id = (!empty($storyid)) ? $storyid : $item_id; $story = new NewsStory($item_id); $location = $story->storyurl();

if ( $location ) { $story->updateCounter(); Header("Location: $location"); } else { $location = "article.php?storyid=$item_id"; Header("Location: $location"); } exit(); ?>

I added several other features to Xoops and E-Xoops that I had already added to PHP-Nuke. To my surprise and delight, it was far easier to modify the Xoops and E-Xoops code than it was the PHP-Nuke code. The ease with which I was able to tweak E-Xoops was due almost entirely to its adoption of an object-oriented approach to writing PHP. Any problems I encountered were with code that has not yet been fully assimilated into the objects. In those cases, I had to hunt down and modify several files, which is the price you pay for failing to consolidate these functions into objects. If you're going to start any new projects in PHP, I strongly urge you to begin with an OOP approach.

MySQL and limitations

I encountered only two problems translating the data from the PHP-Nuke database to Xoops and E-Xoops. The most annoying problem was due to a limitation in MySQL versions that precede version 4.0, and there are supposed bugs even in the 4.0 series related to this feature. The goal is to do something like this (taken from the comments section of the MySQL documentation):

update table_name set table1.field1=table2.field2 where table1.key=table2.key

In my case, I wanted to put the number of comments a user has posted in the "comments" table into a field in the "users" table. Since this is impossible in a single query, I wrote a little PHP code to work around the problem using a temporary table. The second problem I had was one of user identification. The comments table stored the information by name, but I wanted to identify the user by userid to update the users table.

This may not be the most elegant solution, but what the heck — it only has to execute once for a migration, and then you can throw it away.

<?php
// insert code to connect to your particular database

$sql = "CREATE TABLE comcount ( uid int(11) NOT NULL, uname varchar(60) NOT NULL default '', posts int(11) ) TYPE=ISAM PACK_KEYS=1;";

$result = mysql_query($sql);

// Fill the comcount table with the count of posts in the comments table, // grouped by the person who submitted the comments.

$sql = "insert into comcount (uname, posts) select name, count(*) from comments group by comments.name;";

$result = mysql_query($sql);

// Now plug in the user id for each user name

$sql = "select e_xoops_users.uid, e_xoops_users.uname from e_xoops_users;";

$result = mysql_query($sql);

while (list($uid, $uname) = mysql_fetch_row($result)) { mysql_query("update comcount set uid = $uid where uname = '$uname';"); }

// Finally, plug the comcount (total number of posts) into the users table

$sql = "select comcount.uid, comcount.posts from comcount;";

$result = mysql_query($sql);

while (list($uid, $posts) = mysql_fetch_row($result)) { mysql_query("update e_xoops_users set e_xoops_users.posts = $posts where e_xoops_users.uid = $uid;"); } ?>

This last problem was the trickiest. For reasons unknown to this author (even if it's his own fault), the old code stored dates in different formats, depending on the table. The worst was the users table, where the registration date was stored as a string, such as "Mar 10, 2001." The new tables took the more useful approach of storing all dates in unix_timestamp format, which is an integer.

MySQL has a convenient unix_timestamp function for converting certain types of dates, but it doesn't work on strings like "Mar 10, 2001." However, it will work on a string such as "2001-03-10".

I wrote the following code that examines the strings formatted as "Mar 10, 2001" and then uses string manipulation to convert the dates to strings like "2001-03-10" that the MySQL unix_timestamp function can understand. The fact that there are 12 possible three-letter strings for months made it somewhat tricky, but I solved that problem with brute force — I created a separate query for each month.

<?php
// add code to connect to database first!

// Add a new integer (unix timestamp) field to the table

$sqlusers = "alter table users add column regdate int(10);";

$result = mysql_query($sqlusers);

// For each field that matches "Jan" in the correct location, // rearrange the string into something MySQL can convert into a unix_timestamp

$sqlusers = "update users set regdate = unix_timestamp(concat_ws('-',mid(user_regdate,9,4),'01',mid(user_regdate,5,2))) where left(user_regdate,3) = 'Jan';"; $result = mysql_query($sqlusers); $sqlusers = "update users set regdate = unix_timestamp(concat_ws('-',mid(user_regdate,9,4),'02',mid(user_regdate,5,2))) where left(user_regdate,3) = 'Feb';"; $result = mysql_query($sqlusers); $sqlusers = "update users set regdate = unix_timestamp(concat_ws('-',mid(user_regdate,9,4),'03',mid(user_regdate,5,2))) where left(user_regdate,3) = 'Mar';"; $result = mysql_query($sqlusers); $sqlusers = "update users set regdate = unix_timestamp(concat_ws('-',mid(user_regdate,9,4),'04',mid(user_regdate,5,2))) where left(user_regdate,3) = 'Apr';"; $result = mysql_query($sqlusers); $sqlusers = "update users set regdate = unix_timestamp(concat_ws('-',mid(user_regdate,9,4),'05',mid(user_regdate,5,2))) where left(user_regdate,3) = 'May';"; $result = mysql_query($sqlusers); $sqlusers = "update users set regdate = unix_timestamp(concat_ws('-',mid(user_regdate,9,4),'06',mid(user_regdate,5,2))) where left(user_regdate,3) = 'Jun';"; $result = mysql_query($sqlusers); $sqlusers = "update users set regdate = unix_timestamp(concat_ws('-',mid(user_regdate,9,4),'07',mid(user_regdate,5,2))) where left(user_regdate,3) = 'Jul';"; $result = mysql_query($sqlusers); $sqlusers = "update users set regdate = unix_timestamp(concat_ws('-',mid(user_regdate,9,4),'08',mid(user_regdate,5,2))) where left(user_regdate,3) = 'Aug';"; $result = mysql_query($sqlusers); $sqlusers = "update users set regdate = unix_timestamp(concat_ws('-',mid(user_regdate,9,4),'09',mid(user_regdate,5,2))) where left(user_regdate,3) = 'Sep';"; $result = mysql_query($sqlusers); $sqlusers = "update users set regdate = unix_timestamp(concat_ws('-',mid(user_regdate,9,4),'10',mid(user_regdate,5,2))) where left(user_regdate,3) = 'Oct';"; $result = mysql_query($sqlusers); $sqlusers = "update users set regdate = unix_timestamp(concat_ws('-',mid(user_regdate,9,4),'11',mid(user_regdate,5,2))) where left(user_regdate,3) = 'Nov';"; $result = mysql_query($sqlusers); $sqlusers = "update users set regdate = unix_timestamp(concat_ws('-',mid(user_regdate,9,4),'12',mid(user_regdate,5,2))) where left(user_regdate,3) = 'Dec';"; $result = mysql_query($sqlusers); ?>

Wrap-up

You probably won't be porting data or functions across weblogs, but hopefully the above solutions will be helpful for your next project in PHP. If you come away from this article with nothing more than the advice to use object-oriented code whenever possible, that alone will have made my work worthwhile.

More Stories By Nicholas Petreley

Nicholas Petreley is a computer consultant and author in Asheville, NC.

Comments (1) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
Mikhail 04/23/04 01:57:27 AM EDT

Thanks! superuseful for me :-)