Maintain a [we]blog with your WAP phone

  version 1 - rough version.

About blogging

Unless you have been living under a rock for the past year or so, you can't have failed to notice the explosion of personal Web Logs - or 'Blogs' that have appeared on the intenet. Basically, every Man has become his/her own content editor, and the white noise of a million jottings is filling the bandwidth of the internet. It's an online diary, there for all the world to see.

What's made a great deal of difference is the sharing of these jottings. We already know how easy it easy it is to publish on the 'net, but how to make your presence known among millions of me-too entries is potentially difficult. Then Site Syndication came on the scene. This is a system that lets you make content available to other web sites just by creating a simple file in XML format. Don't panic - there are lots of utilities out there to help you, so keep reading, OK ? For more information, check out this link. The idea is that you create an RSS file that contains headlines and a short description (of your blog entry): readers will then be drawn back to your site to read the full article. Clever eh ?

If you work for the type of person that does live under a rock (or is unreceptive), point them at this article. It's suitable for managerial levels..

Well, as always, I wanted to do something a little different. I wanted to be able to write my blog any time, and place, anywhere, using my trusty mobile ! And hey, I wanted to reuse code, like what every good software developer should ! And so, MH[B]log was born... Yes folks, you can now challenge Reuters, CNN et al, without spending loads of money!

Time for a short disclaimer..
.
The system I have built works fine with my *nix hosted ISP - as always, your mileage may vary. The creation of the RSS/RDF files requires write permission on your web site - make sure that you are happy with this, know how to do this securely and understand the implications (of getting it wrong).

System Overview

It's a relatively simple system, and most of the processing is done server side. It consists of a WAP page that presents the user with a text-entry form, which is then used to enter the blog text using the phone keyboard (ha!). Initially I restricted the size of the text field to 100 characters, but then i read that the WAP Forum standards had decreed that the size of the text field is limited only by the phone. Now why did I guess that would cause problems ? Sure enough, I increased the field size to 1024 bytes - more than enough for a decent blog entry, and ran it - the web server duly returned the ubiquitous 500 Server Error message. Ho hum - in real life this meant that I was stuck with 255 characters, so I finally decided on a field size of 254 characters ( plus 1 for a carriage return - don't forget that important point! ). Standards.. don'tcha just love them ?

The other half of the project is viewing your entries, and there's so much scope here that I'm only going to cover the WAP viewing aspect.

OK, so once you have entered your pearls of wisdom and pressed enter, the following events occur:

  1. The entry is stored in an RSS file stored on your web server;
  2. The same entry is also stored as part of a record in a blog database - this will form the basis of a real journal;
  3. the last 15 entries are extracted from the database and written to another RSS file on your web server.

Well, a bit of explanation is required here. In (1), it's dead easy to write the text into the RSS file, so that happens first. In (2), this also easy, and if you have followed any of my other projects you'll be getting used to this sort of thing by now :-) The 15 entries thing is down to the RSS/RDF standard - a file should contain no more than 15 entries, so we have to get the most recent entry and work backwards - there's no point in taking the first 15 entries, as they'd never change.

Important point to remember: you ought to read up on RSS before you start this project: it will make a lot more sense.

Setting Up The Database

Here is my dog-simple table. This holds the details of the time and date the entry was posted, and the entry text itself. The database itself is simple to set up.




#

# Table structure for table 'blog'

#



CREATE TABLE blog (

   CTR int(11) NOT NULL auto_increment,

   postdate varchar(32) NOT NULL,

   entry varchar(255) NOT NULL,

   PRIMARY KEY (CTR),

   KEY postdate (postdate)

);



and here's a typical record:



 1 | Wed, 27 Aug 2003 09:37:53 +0000 | Welcome to the Mildew Hall Front Line ! This is a blog maintained .....

The one thing that annoys me intensely about PHP is that the timestamp is always the timezone of the web server, and as mine is in the USA it often looks like I'm out about way too early. I could fix this at the phone end I suppose, but I wanted a system that only involved entering the text itself, not time/date related info, so it looks like you're stuck with potentially wrong times..

In case you're wondering, I used varchar() for the timestamp as I wanted to be able to use PHP's date("r") to record the time as it incorporated the daylight saving info: MySQL's datetime function doesn't do this, I think..

Setting up a writeable directory on the web server

This is one where you may have to talk to your ISP about permissions. On my system I am only allowed to create directories off of my web server document root directory, and the ownership of the files has to be correct. Here's my entry: I called the directory 'TMP':


drwxrwx-wx   2 mildewh  mildewh      512 Sep  1 12:13 TMP



$ cd TMP

$ ls -l

total 10

-rw-r--r--  1 www      mildewh  6147 Sep  3 09:36 ffront.rss   ( this is created by the WAP text entry)

-rw-r--r--  1 www      mildewh  1329 Sep  3 09:36 front.rss    ( this is created by the WAP text entry)

-rw-r--r--  1 mildewh  mildewh    62 Aug 23 08:11 index.html   ( this is not - note the ownership )

The index.html file contains a PHP header redirect so that anyone (or thing) wandering into your TMP directory gets sent somewhere else. This is just to stop casual browsers, and you may also want to alter your robots.txt file to incorporate this area. You do use a robots.txt file, don't you ?

The Pages Explained

Let's take a look at the blog entry page, here. It looks a bit of a monster, but this is only because a lot of the RSS stuff is hard-coded into the file. A better way would be to put it into PHP include files, or better still, rewrite it as a class and pass it parameters, but that's for later - right now we just want to get it working. Grab a coffee and let's work through it all.

First, we put in a small code segment to make sure that the script is only executed form your server. If you don't want people fiddling with your blogs, this is a handy tip.


if    (

	( $_SERVER['SERVER_NAME'] != 'www.yourserver.com' ) || 

	( $_SERVER['PHP_SELF'] != '/eblog.wml' )

	)

	{

	$GO = "http://www.yourserver.com/index.wml";

	header("Location: $GO");	

	exit;

	}

Now we're going to check if a single variable, called $M has any content: this is actually the text you are going to post. If it contains something when the page is loaded it means that something has been entered, so we'll do the database update, write our RSS files, display the status of our database update, and after a short delay (3 seconds) return to the index page. Here's testing the variable and dynamically writing out the WAP page:


if ( $M > "" ) 

	{

	header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");

	header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");

	header("Cache-Control: no-cache, must-revalidate");

	header("Pragma: no-cache");

	include 'WAPheader.inc';

	echo "<wml>";

	echo "<template>";

	echo "<do type=\"prev\" label=\"Previous\">";

	echo "  <prev/>";

	echo "</do>";

	echo "</template>";

	echo "<card id=\"card2\" title=\"Msg Status\">";

	echo " <onevent type=\"ontimer\">";

	echo " <go href=\"index.wml\"/>";

	echo " </onevent>";

	echo "<timer value=\"30\"/>";

	echo "<p>";

	}

	........

Now we'll add our blog entry to the database. No rocket science here, just a simple SQL insert. If the insert was successful, we just capture the record number from the mysql_insert_id so we can show it to the user later.


mysql_connect("localhost", $DBUSER, $DBPASS) || die( mysql_error());

mysql_select_db("yourserver_com");

$result = mysql_query("INSERT INTO blog (postdate,entry) VALUES('$dt','$M')");



if ( $result ) { $recID =  mysql_insert_id(); }



mysql_close();



if ( $recID  ) { echo "Db OK, rec=$recID"; }

else { echo "Db FAILED<br/>"; echo mysql_error(); }

That's the easy bit out of the way. The next bit creates the RSS files and looks a lot worse than it is. My advice is to borrow my code and amend it to suit. I wanted to hard-code as much as possible, so most of the channel information is shown like so:


$rssHD1  =  '<?xml version="1.0" encoding="ISO-8859-1"?>';

$rssHD1 .=  '<rdf:RDF ';

$rssHD1 .=  'xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" ';

$rssHD1 .=  'xmlns="http://purl.org/rss/1.0/" ';

$rssHD1 .=  'xmlns:dc="http://purl.org/dc/elements/1.1/" ';

$rssHD1 .=  'xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" ';

$rssHD1 .=  'xmlns:admin="http://webns.net/mvcb/" ';

$rssHD1 .=  'xmlns:syn="http://purl.org/rss/1.0/modules/syndication/">';

$rssHD1 .=  '<channel rdf:about="http://www.yourserver.com/index.html">';     // where the blog is found

$rssHD1 .=  ' <title>TITLE OF YOUR BLOG</title>';                       // this is the overall (channel) description

$rssHD1 .=  ' <link>http://www.yourserver.com/index.html</link>';       // link back to your web site

$rssHD1 .=  ' <description>';

//                               <== your message goes in here !!!      // this is where $M will go !

$rssHD2  =  ' </description>';

$rssHD2 .=  ' <dc:language>en-GB</dc:language>';                        // adjust language info to suit

$rssHD2 .=  ' <syn:updatePeriod>hourly</syn:updatePeriod>';                 // update interval

$rssHD2 .=  ' <syn:updateFrequency>8</syn:updateFrequency>';                // update interval frequency

$rssHD2 .=  ' <syn:updateBase>2000-01-01T12:00+00:00</syn:updateBase>';

$rssHD2 .=  ' <items>';

$rssHD2 .=  ' <rdf:Seq>';

$rssHD2 .=  ' <rdf:li rdf:resource="http://www.yourserver.com/rblog.html" />';    // this is where this entry will link to 

$rssHD2 .=  ' </rdf:Seq>';

$rssHD2 .=  ' </items>';

$rssHD2 .=  '</channel>';

......

$rssTLR  = '</rdf:RDF>';

That's the channel definition out of the way. Now we'll get to the part where we write the single-entry RSS file.
We will build the ITEM that's going to make up our blog entry filling in variables as required. then, once we have all our variables written just open, write and close the file.


$fp = fopen("TMP/blog1.rss", 'w');

if ( $fp )

{

$rssITM  = '<item rdf:about="http://www.yourserver.com/index.html">';   // links back to your web site

$rssITM .= '<title>';

$rssITM .= $dt;                                                      // timestamp of the blog entry

$rssITM .= '</title>';

$rssITM .= '<link>';

$rssITM .= "http://www.yourserver.com/rblog1.html";                           // this is where this entry will link to

$rssITM .= '</link>';

$rssITM .= '<description>';

$rssITM .= stripslashes($M);                                         // this is your actual blog entry text

$rssITM .= '</description>';

$rssITM .= '</item>';

//                                                                                                   // write the file out....

fputs( $fp, $rssHD1);

fputs( $fp, 'Latest entry in the mobile blog');

fputs( $fp, $rssHD2);

fputs( $fp, $rssITM);

fputs( $fp, $rssTLR);

fclose($fp);

}

else

{

die;

}

Now we are going to update the database and get the multiple-entry RSS file written. The slight problem here is that MySQL 3.23.x doesn't let you run a single SELECT that will return values and the number of rows in the database - v4 allows nested queries - so we have to get the number of rows in the table first, and this is held as a variable called $CE.


$maxItems = 15;

//

// connect to database

//

$rc = mysql_connect("localhost", $DBUSER,$DBPASS);

if ( $rc )

{

mysql_select_db("someDB);

//

// get number of rows in table

//

$query  = "SELECT count(*) as CE FROM blog";

$result = mysql_query($query);

if ( mysql_num_rows($result) )

{ 

$row = mysql_fetch_array($result, MYSQL_ASSOC);

$CE = $row[CE];

}

//

// set the retrieval start and end limits accordingly

//

if ( $CE > $maxItems ) 

   { $START = 0; $END = $maxItems; }         // more than 15 items

else

   { $START = 0; $END = $CE; }               // less than 15 items

We have the start and end values from our database table, so now we can read the last 15 entries. They have to be in descending order so hat the latest entry always appears first :-) We write out the channel header information just before doing this:


//

// grab the last '$maxItems' items 

//

$query  = "SELECT * FROM blog ORDER BY CTR desc LIMIT $START, $END ";

$result = mysql_query($query);

$nr =  mysql_num_rows($result);

if ( $nr )

{

$fp = fopen("TMP/blog2.rss", 'w');

if ( $fp ) 

{ 

fputs( $fp, $rssHD1); 

fputs( $fp, 'SOME DESCRIPTION OF YOUR BLOG');

fputs( $fp, $rssHD2); 

}

//


$ptr = $END;

while ( $ptr > $START ) 

{

$row = mysql_fetch_array($result, MYSQL_ASSOC);

$rssITM  = '<item rdf:about="http://www.yourserver.com/index.html">';   // links back to your web site

$rssITM .= '<title>';

$rssITM .= $row[postdate];                                            // timestamp retrieved from database

$rssITM .= '</title>';

$rssITM .= '<link>';

$rssITM .= "http://www.yourserver.com/rblog2.html";                     // this is where this entry will link to

$rssITM .= '</link>';

$rssITM .= '<description>'; 

$rssITM .= stripslashes($row[entry]);                               // this is your actual blog entry text

$rssITM .= '</description>';

$rssITM .= '</item>';

fputs( $fp, $rssITM); 	                                             // write the item out

//

$ptr--;                                                                 // decrement the counter and get the next entry

}

fputs( $fp, $rssTLR); 

fclose($fp);

And finally, don't forget to write the end of the WAP page ! Note the exit statement - as we are going to write out the WAP page and simply display it.


...

echo "</p>";

echo "</card>";

echo "</wml>";

exit;                 

}

The code shown below is for actually entering the blog entry text. Note that the first thing we do is to write out the WAP page header and the rest is a simple form, which if you have created using HTML will be no problem.


header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");

header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");

header("Cache-Control: no-cache, must-revalidate");

header("Pragma: no-cache");

include 'WAPheader.inc';

?>

<wml>

<card id="card1" title="Blog It">

<p>

<?php if ( $M > "" ) { echo "<setvar name=\"M\" value=\"\" />"; } ?>

Message:<br/>

<input title="Enter text" name="M" maxlength="254" type="text" format="*m" emptyok="false" />

<anchor>

Send

<?php

echo "<go href=\"$PHP_SELF\" method=\"post\">";

echo " <postfield name=\"M\" value=\"$(M)\"/>";

?>

</go>

</anchor>

</p>

</card>

</wml>

.. And finally, here are some screen shots showing the finished product.

Using the phone to blog..
 
Main Screen Entering text About to send All OK

Reading the blog..
 
On the phone
..

On the web using a browserDitto, multiple entries

Using IE to display my raw RSS file !

 
Using an RSS reader


You Ought To Visit..

http://www.eevl.ac.uk/rss_primerRSS Primer - read first!
rss.org It's all their fault !

Pyweb.com Home of the wonderful Pyweb emulator
w3schools.com The source of much WAP knowledge
mysql.com A Great Database
php.net A Great Scripting Tool
syndic8.com Loads of RSS/RDF/synication info (1)
userland.com Loads of RSS/RDF/synication info (2)


Peter Garner, August 2003