<?php
/* vim: set tabstop=4: */
/*
 * blogs class - provides functions for blogs
 */

// PHP Notices are fun, but we don't really want to see them right now
error_reporting(E_ALL ^ E_NOTICE);

// Initialise the database
require_once("/usr/share/adodb/adodb.inc.php");
$BlogDB = NewADOConnection('postgres8');
$BlogDB->Connect('dbname=blogs');
$BlogDB->SetFetchMode(ADODB_FETCH_ASSOC);

// Some useful validation functions
require_once("validation.lib.php");

// Some useful miscellaneous functions
require_once("miscfunctions.lib.php");

/* a stub of an error handler

scale of 1-5, 5 being warnings, 1 being fatal errors?
1 : fatal
2 : dberror
3
4 : bad input
5 : not found/doesnt exist etc
*/
function error($level, $error) {
	echo("<p class=\"errorinfo\">"._("Level ").$level._(" error - ").$error."</p>");
}
//A bit of a nicer error handler, to allow errors encounterd in the construction phase to be displayed in the 'correct' place
function error_exc($e) {
	if (!isset($e->error) || !isset($e->errormsg)) {
		echo("<p class=\"errorinfo\">"._("Level 0 error - no error message available")."</p>");
	} else {
		echo("<p class=\"errorinfo\">"._("Level ").$e->error._(" error - ").$e->errormsg."</p>");
	}
}

//Our Blogs Class
class blogs {
	//Blog ID
	var $id;
	//Blogger's Details
	var $userName;
	var $realName;
	//Blog Details
	var $title;
	var $description;
	var $cssFile;
	//Date formats
	var $shortDateFormat;
	var $longDateFormat;
	//Paths
	var $httpPath;
	var $blogPath;
	var $basePath;
	var $adminPath;
	//bools
	var $comment_moderation;
	var $editor; //surely this should be in admin.lib? it doesnt seem to be used in blog.lib
	var $currentEntry;
	//Errors
	var $error;
	var $errormsg;
	//SVN Revision... the closest thing we've got to a version number
	var $svnRevision;
	
	//Constructor - checks we've been given a valid username, and pulls in generic blog info
	function blogs($user) {
		global $BlogDB;
		//set the error string first, so we dont wipe out any errors
		$this->error = null;
		$this->errormsg = null;
		//set the locale
		setlocale(LC_ALL, 'en_GB');
		//check the username
		if(!safeuname($user)) {
			$this->error = 1;
			$this->errormsg = "No such user";
			return;
		} else {
			//check to see if the user has a blog
			$sql = $BlogDB->GetRow("SELECT id, name, title, description, css, moderate, editor from users where username = '".$user."' and enabled = true;");
			if (!$sql) {
				$this->error = 1;
				$this->errormsg = "No such user";
				return;
			} else {
				//pull in the blog details
				$this->id = $sql['id'];
				$this->userName = $user;
				$this->realName = $sql['name'];
				$this->title = $sql['title'];
				$this->description = $sql['description'];
				$this->cssFile = $sql['css'];
				$this->shortDateFormat = "%x %X";
				$this->longDateFormat = "%c";
				//set path for all http stuff.. ie images, css and so on
				$this->httpPath = "/Blogs/";
				//make the httpPath work nicely if we're not in a subdir
				//if(substr($this->httpPath, -1)!="/") {
				//	$this->httpPath .= "/";
				//}
				//path for the blog viewer with no user
				$this->basePath = "/Blogs/";
				//path to this blog
				$this->blogPath = $this->basePath.$this->userName."/";
				//path to the admin bits
				$this->adminPath = $this->httpPath."Admin/";
				$this->comment_moderation = ($sql['moderate']=='t') ? TRUE : FALSE;
				$this->editor = ($sql['editor']=='t') ? TRUE : FALSE;
				$this->currentEntry = "";
				$this->svnRevision = getSVNRevision();
				//setup the session
				session_name("BlogSession");
				session_start();
			}
		}
	}

	// print a blog entry, when provided with a database $row containing one.
	function printEntry($row, $commentLink = true, $titleLink = true) {
		global $pathlist;
		echo "<div class=\"box\">\n";

		echo "<div class=\"boxhead\"><h2>";
		if ($titleLink) {
			echo "<a href=\"{$this->blogPath}entry/". htmlentities($row['shortsubject']) ."\">". htmlentities($row['subject']) ."</a>";
		} else {
			echo htmlentities($row['subject']);
		}
		if ($pathlist[3]=="entry") {
		// If we're displaying a single entry, hack the pathlist into shape
			$pathlist[3]=$row['subject'];
			unset($pathlist[4]);
		}
		echo "</h2></div>\n";
		
		echo "<div class=\"boxcontent\">\n";
		echo $row['body'] . "\n";
		echo "</div>\n";
		echo "<div class=\"boxfoot\"><p>[ Entry posted at: ".strftime($this->longDateFormat,strtotime($row['timestamp']));
		if ($commentLink) {
			echo " | <a href=\"".$this->blogPath."entry/{$row['shortsubject']}\">Comments</a>: ".$this->commentCount($row['id']);
		} else {
			echo " | ".$this->commentCount($row['id'])." comment(s)...";
		}
		echo " | Cat: <a href=\"".$this->blogPath."category/{$row['category']}\">".$this->categoryName($row['category'])."</a> ";
		if($this->checkSessionOwner()){
			echo "| <a href=\"".$this->adminPath."update/{$row['shortsubject']}\">"._("Edit")."</a> ";
			//delete link, disabled for now
			//echo "| <a href=\"".$this->adminPath."deleteentry/{$row['shortsubject']}\">"._("Delete")."</a>";
		}
		echo " ]</p></div>\n";
		echo "</div>\n";
		
	}

	// print lots of blog entries
	function printEntries($offset=0, $limit=15, $constraint='') {
		global $BlogDB;
		//get the entries from the database
		$sql = $BlogDB->GetAll("SELECT id, category, subject, body, timestamp, shortsubject from entries where user_id = '".$this->id."' ".$constraint." order by timestamp desc limit ".$limit." offset ".$offset.";");
		//return an error if we cant find any
		if (count($sql) < 1) {
			error(5,"No relevant posts");
		} else {
			//print each entry
			while ($sqlRow = array_shift($sql)) {
				$this->printEntry($sqlRow);
			}
			//archive link
			echo "<div class=\"archivelink\"><a href=\"{$this->blogPath}Archive/\">"._("archived posts...")."</a></div>";
		}
	}

	// print old entries sorted by either date (default), subject or category
	function printArchive($request) {
		switch(trim($request[0])) {
			case 'category' : 
				array_shift($request);
				$this->printArchiveByCategory($request);
				break;
			case 'subject' : 
				array_shift($request);
				$this->printArchiveBySubject($request);
				break;
			case 'date' :
				array_shift($request);
				$this->printArchiveByDate($request);
				break;
			default :
				$this->printArchiveByDate($request);
		}
	}

	// print a list of entries by date
	function printArchiveByDate($request) 
	{
		global $BlogDB;
		$request = preg_grep('/.+/', $request); // Remove any additional silly extra elements due to additional /'s
		//get the refinements if set
		$year = (isset($request[0]) && is_numeric($request[0])) ? $request[0] : "";
		$month = (isset($request[1]) && is_numeric($request[1])) ? $request[1] : "";
		$day = (isset($request[2]) && is_numeric($request[2]) ) ? $request[2] : "";
		// Get the last request option (sort order) after the date
		$lastIndex = count($request) - 1; 
		$order = strtoupper($request[$lastIndex]);
		//this ensures order is sane
		switch($order) {
			case 'ASC' :
				$strOppositeOrder = 'Descending';
				$oppositeOrder = 'DESC';
				$strOrder = 'Ascending';
				break;
			case 'DESC' :
			default :
				$strOppositeOrder = 'Ascending';
				$oppositeOrder = 'ASC';
				$strOrder = 'Descending';
				$order = 'DESC';
		}
 
		if($month=="" && $day=="") {
			$enddate = $year+1;
		} elseif($month != "" && $day=="") {
			$enddate = $year.(sprintf("%02d", $month+1));
		} else {
			$enddate = $year.$month.(sprintf("%02d", $day+1));
		}

		$sql = "SELECT shortsubject,subject,timestamp FROM entries WHERE ".((!$year)? "" : "timestamp >= $year$month$day AND timestamp < $enddate AND ") . 
			"user_id = '".$this->id."' ORDER BY timestamp " . $order;
		$result = $BlogDB->GetAll($sql);

		$requestPath = (count($request) > 0)?implode ( $request, '/' ) . '/':'';
	 
		$curyear = "";
		$curmonth = "";
		$curday = "";
		echo "<div class=\"td\"><h2>Sorted By <a href=\"" . $this->blogPath . "Archive/date/\">Date</a> (" . $strOrder . ")</h2><a href=\"" . $this->blogPath . 
			"Archive/date/" . $requestPath  . "" . $oppositeOrder . "\"> Sort " . $strOppositeOrder . 
			"</a> || Sort By <a href=\"" . $this->blogPath . "Archive/category\">Category</a> | <a href=\"" .
			$this->blogPath . "Archive/subject\"> Subject </a><br />";
		if ( count($result) >= 1 ) {
			while($row = array_shift($result)){
				if($curyear!=date("Y", strtotime($row['timestamp']))) {
					$curyear = date("Y", strtotime($row['timestamp']));
					echo "<h1><a href=\"".$this->blogPath."Archive/$curyear\">$curyear</a></h1>";
				}
				if($curmonth!=date("F", strtotime($row['timestamp']))) {
					$curmonth = date("F", strtotime($row['timestamp']));
					echo "<h2><a href=\"".$this->blogPath."Archive/".date("Y/m", strtotime($row['timestamp']))."\">$curmonth</a></h2>\n";
				}
				if($curday!=date("l jS", strtotime($row['timestamp']))) {
					$curday = date("l jS", strtotime($row['timestamp']));
					echo "<h3><a href=\"".$this->blogPath."Archive/".date("Y/m/d", strtotime($row['timestamp']))."\">$curday</a></h3>\n";
				}
				echo date("g:ia", strtotime($row['timestamp']))." - <a href=\"{$this->blogPath}entry/{$row['shortsubject']}\">". htmlentities($row['subject']) ."</a><br />\n";
			}
		} else {
			error(5,"No Entries Available" . ($allentries ? '' : " for $year" . ($month != '' ? "/$month":'') . ($day != '' ? "/$day":'')));
		}
		echo "</div>";
	}

	//print a list of entries by category
    function printArchiveByCategory($request) 
    {
    		global $BlogDB;
		// Check for a category id
		// There must be a better way to check that it isn't $order
 		$request = preg_grep('/.+/', $request); // Remove any additional silly extra elements due to additional /'s
		if (isset($request[0]) && (strtoupper($request[0]) != 'ASC') && (strtoupper($request[0]) != 'DESC')) {
			$category = $this->makeCleanString($request[0]);
			if (strlen($category) < 3)
                		$allentries = true;
		} else {
			$allentries = true;
		}
	 
		$lastIndex = count($request) - 1; // Get the last request option after the date
		$order = 'ASC';
		if (isset($request[$lastIndex]) && (($lastIndex > 0) || isset($allentries)) &&
			(strlen($request[$lastIndex]) > 2)) {
			$order = strtoupper($request[$lastIndex]);
			array_pop($request);
		}
		switch($order) {
			case 'DESC' :
				$strOppositeOrder = 'Ascending';
				$oppositeOrder = 'ASC';
				$strOrder = 'Descending';
				break;
			case 'ASC' :
			default :
				$strOppositeOrder = 'Descending';
				$oppositeOrder = 'DESC';
				$strOrder = 'Ascending';
				$order = 'ASC';
		}

		$sql = "SELECT shortsubject,subject,timestamp, name FROM entries AS e,categories AS c WHERE " . 
			($allentries ? "" : " lower(c.name) = '" . $category . "' AND ") .
			"e.user_id = '".$this->id."' AND e.category = c.id ORDER BY " . ($allentries? "name " . $order . " ,timestamp ASC" : "timestamp " . $order );
		$result = $BlogDB->GetAll($sql);

		$requestPath = (count($request) > 0)?implode ( $request, '/' ) . '/':'';
		$dbCategory = '';
		echo "<div class=\"td\"><h2>Sorted By <a href=\"" . $this->blogPath . "Archive/category/\">Category</a> (" . $strOrder . ")</h2><a href=\"" . $this->blogPath . 
			"Archive/category/" . $requestPath  . "" . $oppositeOrder . "\"> Sort " . $strOppositeOrder . 
			"</a> || Sort By <a href=\"" . $this->blogPath . "Archive/date\">Date</a> | <a href=\"" .
			$this->blogPath . "Archive/subject\"> Subject </a><br />";
		
		if ( count($result) >= 1 ) {
			while($row = array_shift($result)){
				if($dbCategory != $row['name']) {
					$dbCategory = $row['name'];
					echo "<h1><a href=\"".$this->blogPath."Archive/category/$dbCategory\">$dbCategory</a></h1>";
				}
				echo date("d/m/Y", strtotime($row['timestamp'])) . " - <a href=\"{$this->blogPath}entry/{$row['shortsubject']}\">". htmlentities($row['subject']) ."</a><br />\n";
			}
			echo "</div>";
		} else {
			error(5,"No Entries Available" . (isset($category) ? " in $category":''));
		}
	}

	//print a list of entries by title
	function printArchiveBySubject ($request) 
	{
		global $BlogDB;
		// Look for a single character to show subjects by 
		$request = preg_grep('/.+/', $request); // Remove any additional silly extra elements due to additional /'s
		if (isset($request[0]) && (preg_match('/^[a-z]$/i', $request[0]))) {
			$letter = strtolower($request[0]);
		} else {
			$allentries = true;
		}
	
		// Get whether it is Ascending or Descending
		$lastIndex = count($request) - 1; // Get the last request option after the date
		$order = 'ASC';
		if (isset($request[$lastIndex]) && !is_numeric($request[$lastIndex])) {
			$order = strtoupper($request[$lastIndex]);
			array_pop($request);
		}
		switch($order) {
			case 'DESC' :
				$strOppositeOrder = 'Ascending';
				$oppositeOrder = 'ASC';
				$strOrder = 'Descending';
				break;
			case 'ASC' :
				default :
				$strOppositeOrder = 'Descending';
				$oppositeOrder = 'DESC';
				$strOrder = 'Ascending';
				$order = 'ASC';
		}

		$requestPath = (count($request) > 0)?implode ( $request, '/' ) . '/':'';
	 
		// lower() exists in PG and MySQL, but given that db abstraction is wanted, is it part of the SQL standard?
		// Should a better method be used?
		$sql = "SELECT shortsubject,subject,timestamp FROM entries WHERE ".(($allentries)? "" : "lower(subject) LIKE '" . $letter . "%' AND ") . 
			"user_id = '".$this->id."' ORDER BY subject " . $order;
		$result = $BlogDB->GetAll($sql);

		echo "<div class=\"td\"><h2>Sorted By <a href=\"" . $this->blogPath . "Archive/subject/\">Subject</a> (" . $strOrder . ")</h2><a href=\"" . $this->blogPath . 
			"Archive/subject/" . $requestPath  . "" . $oppositeOrder . "\"> Sort " . $strOppositeOrder . 
			"</a> || Sort By <a href=\"" . $this->blogPath . "Archive/date\">Date</a> | <a href=\"" .
			$this->blogPath . "Archive/category\"> Category </a><br /><a href=\"" . $this->blogPath . 
			"Archive/subject/a/$order\">a</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/b/$order\">b</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/c/$order\">c</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/d/$order\">d</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/e/$order\">e</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/f/$order\">f</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/g/$order\">g</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/h/$order\">h</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/i/$order\">i</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/j/$order\">j</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/k/$order\">k</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/l/$order\">l</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/m/$order\">m</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/n/$order\">n</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/o/$order\">o</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/p/$order\">p</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/q/$order\">q</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/r/$order\">r</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/s/$order\">s</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/t/$order\">t</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/u/$order\">u</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/v/$order\">v</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/w/$order\">w</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/x/$order\">x</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/y/$order\">y</a> | <a href=\"" . $this->blogPath . 
			"Archive/subject/z/$order\">z</a><br />";
		if ( count($result) >= 1 ) {
			while($row = array_shift($result)){
				echo date("d/m/Y", strtotime($row['timestamp'])) . " - <a href=\"{$this->blogPath}entry/{$row['shortsubject']}\">". htmlentities($row['subject']) ."</a><br />\n";
			}
		} else {
			error(5, "No Entries Available" . ($allentries ? '' : " beginning with '$letter'"));
		}
		echo "</div>";
	}

	//print Prev/Next nav bar
	function printNavigationBar($id) {
		global $BlogDB;
		$sql = $BlogDB->GetRow("SELECT timestamp from entries WHERE id='".$id."'");
		$prev = $BlogDB->GetAll("SELECT id, shortsubject, subject FROM entries WHERE timestamp < '".$sql['timestamp']."' AND user_id = '".$this->id."' ORDER BY timestamp DESC LIMIT 1");
		$next = $BlogDB->GetAll("SELECT id, shortsubject, subject FROM entries WHERE timestamp > '".$sql['timestamp']."' AND user_id = '".$this->id."' ORDER BY timestamp ASC LIMIT 1;");
		if (count($prev)>0) $prevRow=array_shift($prev);
		if (count($next)>0) $nextRow=array_shift($next);

		echo "<div class=\"navbar\"><div><div><div>\n";
		echo "<ul class=\"blognav\">";
		if ($prevRow['shortsubject']!="") echo "<li class=\"prev\"><a href=\"{$this->blogPath}entry/{$prevRow['shortsubject']}\">&lt; ".htmlentities($prevRow['subject'])."</a></li>";
		if ($nextRow['shortsubject']!="") echo "<li class=\"next\"><a href=\"{$this->blogPath}entry/{$nextRow['shortsubject']}\">".htmlentities($nextRow['subject'])." &gt;</a></li>";
		echo "</ul>\n";
		echo "<br style=\"clear: both:\" /></div></div></div></div>\n";
	}

	//print one entry and its comments
	function printEntryAndComments($shortsubject) 
	{
		global $BlogDB;
		$shortsubject = $this->makeCleanString($shortsubject);
		$sql = $BlogDB->GetRow("SELECT id, category, subject, body, timestamp, shortsubject from entries where shortsubject='".$shortsubject."' and user_id = ".$this->id." LIMIT 1;");
		if (!$sql) {
			error(5,"No relevant posts");
		}
		else	{
			$this->currentEntry = $sql['shortsubject'];
			$this->printNavigationBar($sql['id']);
			$this->printEntry($sql, false, false);
			$this->printComments($sql['id']);
			$this->printCommentForm($sql['id']);
		}
	}

	//print lots of comments
	function printComments($postid, $offset=0, $limit=15) 
	{
		global $BlogDB;
		$sql = $BlogDB->GetAll("SELECT timestamp, name, email, body, host, id FROM comments WHERE post = ".$postid." and moderated = true ORDER BY timestamp ASC limit ".$limit." OFFSET ".$offset.";");
		echo "<div id=\"comments\">\n";
		if (count($sql) > 0) {
			$blogOwner = $this->checkSessionOwner();
			if($blogOwner) {
				echo "<form name=\"deletecomments\" id=\"deletecomments\" method=\"post\" action=\"{$this->adminPath}deletecomments/{$this->currentEntry}\">\n";
			}
			
			$count=0;
			while ($sqlRow = array_shift($sql)) {
				$this->printComment($sqlRow, $blogOwner, $count++);
			}


			if($blogOwner) {
				echo "<div style=\"width: 100%; text-align: right\">\n";
				echo "<input type=\"submit\" name=\"submit\" value=\"Delete Comments\" />\n";
				echo "</div>\n";
				echo "</form>\n";
			}
		}
		echo "</div>\n";
	}

	//print a comment
	function printComment($row, $printCheckBox=FALSE, $checkBoxNum=0) 
	{
		echo "<div class=\"box\">\n";
		echo "<div class=\"boxhead\"><h3>" . htmlentities($row['name']) . " writes:</h3></div>";
		echo "<div class=\"boxcontent\"><p>" . nl2br(htmlentities(br2nl($row['body']))) . "</p></div>\n";
		echo "<div class=\"boxfoot\"><p>[ " .strftime($this->longDateFormat,strtotime($row['timestamp']));
		if($printCheckBox){
			echo " | <input class=\"smallcheckbox\" type=\"checkbox\" id=\"comment{$checkBoxNum}\" name=\"comment[{$checkBoxNum}]\" value=\"{$row['id']}\" />\n";
			echo "<label for=\"comment{$checkBoxNum}\">Delete</label>\n";
		}
		echo " ]</p></div>\n";
		echo "</div>\n";
	}
	
	//counts the number of comments 
	function commentCount($entry) {
		global $BlogDB;
		$sql = $BlogDB->GetCol("SELECT count(id) from comments where post = ".$entry." and moderated = true;");		
		return $sql[0];
	}
	
	//returns a category name
	function categoryName($category) {
		global $BlogDB;
		$sql = $BlogDB->GetCol("SELECT name from categories where id = ".$category.";");		
		return $sql[0];
	}
	
	//prints a form so people can comment
	function printCommentForm($id) 
	{
		echo "<div class=\"entry\">\n";
		echo "<h2>Add Comment<a id=\"cmt\"></a></h2>\n";
		echo "<div class=\"td\">\n";
		if ($this->commentError != "") {
			echo "<p class=\"invalid\">*** " . $this->commentError . " ***</p>\n";
		}
		elseif (isset($_POST['submit'])) {
			echo "<p>Thank you for your comment</p>\n";
		}

		// try to work out the viewer's name + email
		//seems a bit silly to check for the existance of the session stuff in two places, its probably fair to assume if one is set, the other will be too
		//these need the same validation checks as when we put things into the db, else people can inject what ever html they like into our pages
		if(isset($_SESSION['realName'])) {
			$name = $_SESSION['realName'];
		} elseif(isset($_COOKIE['Blog_CommentRealName'])) {
			$name = $_COOKIE['Blog_CommentRealName'];
		} else {
			$name = "";
		}
		if(isset($_SESSION['userName'])) {
			$email = $_SESSION['userName']."@sucs.org";
		} elseif(isset($_COOKIE['Blog_CommentEmailAddress'])) {
			$email = $_COOKIE['Blog_CommentEmailAddress'];
		} else {
			$email = "";
		}
		echo "<form onsubmit=\"return postcomment('".$this->httpPath."', '".$this->userName."', '".$id."')\" action=\"".$this->blogPath."postcomment/".$id."\" method=\"post\" id=\"commentform\">\n";
		echo "<div class=\"row\">\n";
		echo "<label for=\"author\">Name (required)</label>\n";
		echo "<span class=\"textinput\"><input type=\"text\" name=\"author\" id=\"author\" value=\"$name\" size=\"40\" maxlength=\"50\" tabindex=\"1\" /></span>\n";
		echo "</div>\n";
		echo "<div class=\"row\">\n";
		echo "<span class=\"textinput\"><input type=\"text\" name=\"email\" id=\"email\" value=\"$email\" size=\"40\" maxlength=\"70\" tabindex=\"2\" /></span>\n";
		echo "<label for=\"email\">E-mail (required, not displayed)</label>\n";
		echo "</div>\n";
		echo "<div class=\"row\">\n";
		echo "<span class=\"textinput\"><textarea name=\"comment\" id=\"comment\" cols=\"50\" rows=\"10\" tabindex=\"3\">" . (($this->commentError != "") ? strip_tags($_POST['comment']) : "") . "</textarea></span>\n";
		echo "</div>\n";
		echo "<div class=\"row\">\n";
		echo "<span class=\"textinput\"><input name=\"submit\" type=\"submit\" id=\"submit\" tabindex=\"4\" value=\"Submit Comment\" /></span>";
		echo "<img src=\"".$this->httpPath."img/spinner.gif\" alt=\"\" id=\"spinner\"/>\n";
		echo "<label class=\"invalid\" for=\"submit\" id=\"errors\"></label>\n";
		echo "</div>\n<div class=\"clear\"></div>";
		echo "</form>\n";
		echo "</div>\n";
		echo "</div>\n";
	}

	//takes a string and strips it, making it safe to put in a URL
	function makeCleanString($string,$externalSource=false)
	{
		//externalSource meaning directly inputed by a user, in most cases this should be false.. appart from starting a new post
		$string = strtolower($string);
		$string = preg_replace("/[^a-z0-9\- _]/i", "", $string);
		$string = str_replace(" ", "-",trim($string));
		if ($externalSource) {
			$string = str_replace("_", "-",$string);
		}
		$string = urlencode($string);
		return $string;
	}
	
	//handles posting of comments
	function newComment($id, $printentry=TRUE) 
	{
		global $BlogDB;
		$author = "";
		$email = "";
		$comment = "";
		//check the post exists, and is part of this blog
		$row = $BlogDB->GetRow("SELECT subject, id from entries where user_id = ".$this->id." and id = '".$id."';");
		if (!$row) {
			error(1,_("Invalid blog entry, This entry may have been removed..?"));
			return;
		}
		//pull in the unadulterated subject for later on
		$subject = $row['subject'];
		$postid =  $row['id'];
		//set hostname
		if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
			$host = addslashes($_SERVER['HTTP_X_FORWARDED_FOR']) . " : " . addslashes($_SERVER['REMOTE_ADDR']);
		}
		else {
			$host = addslashes($_SERVER['REMOTE_ADDR']);
		}
		//sanitise comment
		if (isset($_POST['comment']) && trim($_POST['comment']) != "") {
			if(strip_tags($_POST['comment']) == $_POST['comment']) {
				$comment = addslashes(nl2br(trim($_POST['comment'])));
			} else {
				$this->commentError = _("HTML within comments is not allowed, Please remove all html tags and try again");
				$element = "comment";
			}
		} else {
			$this->commentError = _("Please check the comment field");
			$element = "comment";
		}
		//decided if the comment is likly to be spam
		if (checkSpam($host,$_POST['comment'])) {
			$spam = true;
			//force this comment though moderation
			$this->comment_moderation = true;
		} else {
			$spam = false;
		}
		//sanitise email
		if (isset($_POST['email']) && trim($_POST['email']) != "" && validEmail(trim($_POST['email']))) {
			$email = addslashes(trim($_POST['email']));
		} else {
			$this->commentError = _("Check email address, it does not apear to be valid.");
			$element = "email";
		}
		//sanitse author
		if (isset($_POST['author']) && trim($_POST['author']) != "") {
			if(eregi("^([a-z0-9]+ *)+$", $_POST['author'])) {
				$author = addslashes(nl2br(trim(strip_tags($_POST['author']))));
			} else {
				$this->commentError = _("Invalid name. We only allow alphanumeric names!");
				$element = "author";
			}
		} 
		else {
			$this->commentError = _("Please give us your name.");
			$element = "author";
		}
		//if no errors have been raised so far commit to the db
		if ($this->commentError == "") {
			// do we need to set the moderated flag on this comment?
			if(!$this->comment_moderation) {
				$moderated = TRUE;
			} else {
				//check the list of 'authorised' commentors
				if(count($BlogDB->GetAll("SELECT name FROM authorised_emails WHERE user_id={$this->id} AND email='{$email}'"))>0) {
					$moderated = TRUE;
				} else {
					$moderated = FALSE;
				}
			}
			//actualy insert the new comment and check it worked
			$query = "INSERT INTO comments (post, name, email, body, host, moderated, spam) VALUES ('{$postid}','{$author}','{$email}','{$comment}','{$host}', ".(($moderated) ? "true" : "false").", ".(($spam) ? "true" : "false").")";
			if(!$BlogDB->Execute($query)) {
				error(2,_("Database commit failed -").$BlogDB->ErrorMsg());
			}
			//send out an notificaiton email if we have succeeded unless we think its spam or moderation has been bypassed
			else {
				if(!$spam or !$moderated) 
					mail($this->userName . "@sucs.org", "Blog comment on \"".$subject."\"", "You've received a comment from ".$author." on your blog post \"".$subject.". The comment is:\n==========\n".$comment.(($moderated) ? "" : "\n==========\nPlease login to your blog admin page to approve or delete this comment."), "From: Your Blog <noreply@sucs.org>");
				//inform the commentor if the message has been tagged for modderation
				if(!$moderated) {
					echo "<p class=\"updateinfo\">"._("Your comment has been added, but before it appears here it must be accepted by the blog owner.")."</p>";
				} 
				//or pass out the comment useing the standard form
				elseif(!$printentry) {
					$time = strftime($this->longDateFormat, time());
					$this->printComment(array('name'=>$author, 'body'=>$comment, 'timestamp'=>$time));
				}
				//reprint the entire entry (for the case where we're not useing the ajax goodness)
				if($printentry) {
					$this->printEntryAndComments($shortSubject);
				}
				//if we dont have a valid session store the name & email in there own cookies
				if(!isset($_SESSION['realName'])) {
					setcookie("Blog_CommentRealName", $author, time()+604800);
					setcookie("Blog_CommentEmailAddress", $email, time()+604800);
				}
				ob_end_flush();
				return array(TRUE);
			}
		//else return our error and the status gumf for the benifit of the ajax goodness
		} else {
			echo $this->commentError;
			return array(FALSE, $element);
		}
	}
	
	//reutrns the id of the message assosiated with a short subject
	function shortSubjectToID($shortsubject)
	{
		global $BlogDB;
		$sql = $BlogDB->GetRow("SELECT id from entries where user_id = ".$this->id." and shortsubject = '".$shortsubject."';");
		if ($sql) {
			return $sql['id'];
		}
		else {
			error(3,"No such post");
		}
	}
	
	//reutrns the short subject of the message given message 
	function IDToShortSubject($id)
	{
		global $BlogDB;
		$sql = $BlogDB->GetRow("SELECT shortsubject from entries where user_id = ".$this->id." and id = '".$id."';");
		if ($sql) {
			return $sql['shortsubject'];
		}
		else {
			error(3,"No such post");
		}
	}

	// Blog menu
	function menu()
	{
		global $smarty, $session;

		$submenu = array();

		if ($session->loggedin && blogger($session->username)) $submenu[_("My Blog")] = "{$this->httpPath}{$session->username}";

		if ($session->username != $this->userName) $userblog = $this->userName._("'s Blog");
		$submenu[$userblog] = $this->blogPath;

		if ($session->username == $this->userName) $archiveblog = _("My Archive");
		else $archiveblog = $this->userName._("'s Archive");
		$submenu[$archiveblog] = "{$this->blogPath}Archive/";
		
		if ($this->checkSessionOwner() && blogger($session->username)){
			$submenu[_("Blog admin")] = "{$this->adminPath}";
		}

		$menu = $smarty->get_template_vars("menu");
		$menu[Blogs] = $submenu;
		$smarty->assign("menu", $menu);
	}

	// Check the session to see if the user is browsing her own blog
	function checkSessionOwner()
	{
		global $session;
		$maxSessionAge = 3600;
		
		// if the session's expired then nuke it
		if ($session->username != $this->userName) {
			return FALSE;
		} else {
			// the time's not up and the usernames match so it's probably the right user.
			return TRUE;
		}
	}
}

// a pseudo-class to list all blog users
class bloglist {
	var $title;
	var $description;
	var $httpPath;
	var $listPath;
	var $adminPath;
	var $cssFile;
	var $svnRevision;       // the SVN revision number of the currently running blog

	// don't do anything apart from setting up default variables
	function bloglist() 
	{
		$this->title = _("Blogs");
		$this->description = _("Swansea University Computer Society member web logs");
		$this->httpPath = "/Blogs/";
		//if(substr($this->httpPath, -1)!="/") {
		//	$this->httpPath .= "/";
		//}
		$this->basePath = "/Blogs/";
		$this->adminPath = $this->httpPath."Admin/";
		$this->cssFile = "blog.css";
		$this->svnRevision = getSVNRevision();

		// setup the session purely so we get the debug bits..
		session_name("BlogSession");
		session_start();
	}

	// print a nice list of blog users and when they last updated
	function listBlogs()
	{
		global $BlogDB, $session;
		$sql = $BlogDB->GetAll("SELECT max(entries.timestamp) AS ts, users.username, users.description, users.name, users.title FROM entries LEFT JOIN users ON entries.user_id = users.id GROUP BY users.username, users.name, users.title, users.description ORDER BY ts DESC;");
		if (count($sql) > 0) {
			echo "<div class=\"td\">\n";
			echo "<p>"._("Welcome to SUCS Blogs - The multi-user web log system created by SUCS members for SUCS members.")."</p>\n";
			echo "<p>"._("Browse the blogs below or use the links on the left to navigate the site. Happy blogging!")."</p>\n";
			if ($session->loggedin && !blogger($session->username)) echo "<p>"._("Want to join the ranks of SUCS bloggers? - ")."<a href=\"{$this->adminPath}signup\">"._("Start a Blog!")."</a></p>\n";
			echo "</div>\n";
			echo "<div id=\"listofblogs\">\n";
			echo "<table class=\"border\">\n";
			echo "<tr><th class=\"bname\">"._("Name")."</th><th class=\"btitle\">"._("Blog")."</th><th class=\"bupdated\">"._("Last Updated")."</th></tr>";
			while($row = array_shift($sql)) {
				echo "<tr>\n";
				echo "    <td>".$row['name']."</td>\n";
				echo "    <td><a href=\"".$this->basePath.$row['username']."\" title=\"".$row['description']."\">".$row['title']."</a></td>\n";
				echo "    <td>".$this->timeDiff(strtotime($row['ts']))." ago</td>\n";
				echo "</tr>\n";
			}
			
			echo "</table>\n";
			echo "</div>\n";
		}
		else {
			error(3,_("No blogs"));
		}
	}
    
	//Returns a textual diff between two time stamps
	function timeDiff($first, $second=0)
	{
		if($second == 0) {
			$second = time();
		}

		$diff = max($first, $second) - min($first, $second);

		if($diff>604800) {
			$ret = round($diff/604800);
			return $ret.(($ret>1)? _(" weeks") : _(" week"));
		} 
		elseif($diff>86400) {
			$ret = round($diff/86400);
			return $ret.(($ret>1)? _(" days") : _(" day"));
		} 
		elseif($diff>3600) {
			$ret = round($diff/3600);
			return $ret.(($ret>1)? _(" hours") : _(" hour"));
		} 
		elseif($diff>60) {
			$ret = round($diff/60);
			return $ret.(($ret>1)? _(" minutes") : _(" minute"));
		}
		else {
			return $diff.(($diff>1)? _(" seconds") : _(" second"));
		}
	}

	// Blog menu - links displayed when the blog list is displayed
	function menu() {
		global $smarty, $session;

		if ($session->loggedin) {
		$submenu = array();
			if (blogger($session->username)) {
				$submenu[_("My Blog")] = "{$this->httpPath}{$session->username}";
				$submenu[_("Blog admin")] = "{$this->adminPath}";
			} else {
				$submenu[_("Start a Blog")] = "{$this->adminPath}signup";		
			}
		$menu = $smarty->get_template_vars("menu");
		$menu[Blogs] = $submenu;
		$smarty->assign("menu", $menu);
		}
	}
}