<?php
/*
* Pure: A dead simple and heavily customizable blogging engine
* Copyright (C) 2007 Ben Ng & Payam Yousefi
* 
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* 
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* 
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* 
* VERSION: 0.2 Foxtrot
*
* Depends on:
* template.class.php
* bbcode.class.php
* geshi.php
*/

class pure {
	var $_usr=false; //This is filled out with user details and permissions by pure()
	var $_cfg=array(
		'table_path'=>'tables/',
		'prefix'=>'',
	);
	
	/*
	* This will attempt to restablish the current session
	*/
	function pure($prefix='',$using_geshi=false) {
		//Start session
		session_start();
		$this->_cfg['prefix']=$prefix;
		//Add prefix
		$this->_cfg['table_path']=$prefix.$this->_cfg['table_path'];
		//First off, set up table objects that we will need
		$table_status=array(
			'archives'=>$this->table_exists('archives'),
			'posts'=>$this->table_exists('posts'),
			'categories'=>$this->table_exists('categories'),
			'pages'=>$this->table_exists('pages'),
			'users'=>$this->table_exists('users'),
			'comments'=>$this->table_exists('comments'),
		);
		//And our geshi object if needed
		if($using_geshi)
			$this->geshi=true;
		//Mount the tables
		$this->mount_table('archives');
		$this->mount_table('posts');
		$this->mount_table('categories');
		$this->mount_table('pages');
		$this->mount_table('users');
		$this->mount_table('comments');
		//Autofix tables we detected were missing
		if($table_status['archives']==false) {
			$this->tb_archives->addKey('month');
			$this->tb_archives->addField('created');
			$this->tb_archives->commit();
		}
		if($table_status['posts']==false) {
			$this->tb_posts->addField('title');
			$this->tb_posts->addField('content');
			$this->tb_posts->addField('timestamp');
			$this->tb_posts->addField('month'); //Special format for faster by-month-year processing: date('mY')
			$this->tb_posts->addField('author');
			$this->tb_posts->addField('comments');
			$this->tb_posts->addField('comment_count');
			$this->tb_posts->addField('is_published');
			$this->tb_posts->addField('categories');
			$this->tb_posts->commit();
			
			//Make a test post
			$this->make_post('Welcome to Pure',
'[h4]Welcome[/h4]
Thank you for choosing Pure!

If you have just uploaded Pure and are reading this right now, it has already been installed for you and seems to be working just fine.
[code]Default account

Username:admin
Password:admin[/code]

[h4]What to do now[/h4]
Log into the [URL=./admin]administration panel[/URL] and use the [b]Edit Settings[/b] page to set up your blog. Pay special attention to the first section, which will affect how search engines index your site.');
		}
		if($table_status['categories']==false) {
			$this->tb_categories->addKey('name');
			$this->tb_categories->commit();
			
			//Add the Uncategorized category
			if(!$this->add_category('Uncategorized'))
				echo('Error: Could not create new category');
		}
		if($table_status['pages']==false) {
			$this->tb_pages->addField('title');
			$this->tb_pages->addField('content');
			$this->tb_pages->addField('timestamp');
			$this->tb_pages->addField('is_published');
			$this->tb_pages->commit();
		}
		if($table_status['users']==false) {
			$admin=array(
				'username'=>'admin',
				'hash'=>md5('admin'),
			);
			$this->tb_users->addField('username');
			$this->tb_users->addField('hash');
			$this->tb_users->addRow($admin);
			$this->tb_users->commit();
		}
		if($table_status['comments']==false) {
			$this->tb_comments->addField('timestamp');
			$this->tb_comments->addField('approved');
			$this->tb_comments->addField('prominent');
			$this->tb_comments->addField('parent');
			$this->tb_comments->addField('content');
			$this->tb_comments->addField('ip');
			$this->tb_comments->addField('name');
			$this->tb_comments->addField('email');
			$this->tb_comments->commit();
		}
		//All done! Ready to begin work.
	}
	
	/*
	* Attempt to mount or create a table for use
	*/
	function mount_table($name) {
		$valid_name=preg_replace('[^0-9a-zA-Z_]','',$name);
		
		//Check if this is a valid table name
		if($valid_name!==$name || strlen($valid_name)==0) {
			trigger_error('Invalid table name "'.$name.'".');
			return false;
		}
		
		$tbname='tb_'.$name;
		$filename=$this->_cfg['table_path'].$tbname.'.php';
		
		//Check if this table has not been mounted
		if(!isset($this->$tbname)) {
			//Check if this table file exists
			if($this->table_exists($name)) {
				//Mount the table
				$this->$tbname=new fetch($filename);
				return true;
			}
			//If not, automagically create the table
			else {
				$this->$tbname=new fetch($filename,true);
				return true;
			}
		}
		//If not we can't mount it again
		else {
			trigger_error('The table "'.$name.'" is already mounted.');
			return false;
		}
	}
	
	/*
	* Is this a whitelisted IP?
	*/
	function is_whitelisted($whitelist_path) {
		$list=substr(file_get_contents($whitelist_path),strlen(DIE_MSG));
		$listentries=unserialize($list);
		foreach($listentries as $good)
			if(strpos($_SERVER['REMOTE_ADDR'],$good)===0)
				return true;
		return false;
	}
	
	/*
	* Is the user logged in?
	*/
	function is_logged_in() {
		$this->log_in_cookie();
		return (array_key_exists('auth_id',$_SESSION)&&is_int($_SESSION['auth_id']) && $this->tb_users->idExists($_SESSION['auth_id']));
	}
	
	/*
	* Log this user in
	*/
	function log_in($uid,$pass) {
		//If uid recieved was a username, attempt to convert to a uid
		if(is_string($uid)) {
			$uid=$this->usernameToId($uid);
		}
		$hash=md5($pass);
		return $this->log_in_hash($uid,$hash);
	}
	
	/*
	* Log in with cookie information
	*/
	function log_in_cookie(){
		if(isset($_COOKIE["ui"])){
			$cookies = explode("-", $_COOKIE["ui"]);
			$userid = $cookies[0];
			$mdpass = $cookies[1];
			$userid+=0;
			return $this->log_in_hash($userid, $mdpass);
		}
		return false;
	}
	
	function log_in_hash($uid, $hash) {
		//is_int() makes sure that the either user id is an int or the user is also already logged in
		//If already logged in, move on. Or else, check if passwords match
		if(is_int($uid) && $this->tb_users->getFieldValue($uid,'hash')==$hash) {
			//Update _usr with proper information
			$this->_usr=array(
				'username'=>$this->tb_users->getFieldValue($uid,'username'),
				'hash'=>$this->tb_users->getFieldValue($uid,'hash'),
			);
			$_SESSION['auth_id']=$uid;
			//Now we update the last active time
			$this->tb_users->updateRow($uid,array('last_active'=>time()));
			$this->tb_users->commit();
			return true;
		}
		return false;
	}
	
	/*
	* Rebuild the rss XML file
	*/
	function rebuildRSS() {
		//Get all posts
		$posts=$this->loadposts_all();
		
		//Remove private posts
		foreach($posts as $postid=>$postdata)
			if(array_search(PRIVATE_CATEGORY,$postdata['categories'])!==false)
				unset($posts[$postid]);
		
		//Remove the last# posts
		$i=0;
		$limit=count($posts)-RSS_MAX_ENTRIES;
		if(is_array($posts)&&count($posts)>0) {
			foreach($posts as $postid=>$postdata) {
				if($i<$limit)
					unset($posts[$postid]);
				$i++;
			}
			
			//Reverse the array
			krsort($posts);
		}
		
		//Build the file
		$file='<?xml version="1.0"?>
<rss version="2.0">
	<channel>
		<title>'.htmlentities(SITE_TITLE,ENT_QUOTES,'UTF-8').'</title>
		<link>'.htmlentities(SITE_URL,ENT_QUOTES,'UTF-8').'</link>
		<description>'.htmlentities(META_DESC,ENT_QUOTES,'UTF-8').'</description>
		<language>en-us</language>
		
		<copyright>Copyright '.date('Y').' '.SITE_URL.'</copyright>
		<lastBuildDate>'.htmlentities(date('D, d M Y H:i:s O'),ENT_QUOTES,'UTF-8').'</lastBuildDate>

		<generator>'.htmlentities(PURE_VERSION,ENT_QUOTES,'UTF-8').'</generator>
		<managingEditor>'.htmlentities(SITE_EMAIL,ENT_QUOTES,'UTF-8').'</managingEditor>
		<webMaster>'.htmlentities(SITE_EMAIL,ENT_QUOTES,'UTF-8').'</webMaster>
		<ttl>'.htmlentities(RSS_TTL,ENT_QUOTES,'UTF-8').'</ttl>';
		
		if(is_array($posts)&&count($posts)>0) {
			//Add the posts
			foreach($posts as $postID=>$postData) {
				//Start the item
				$post='
				<item>
					<author>'.htmlentities($this->tb_users->getFieldValue($postData['author'],'username'),ENT_QUOTES,'UTF-8').'</author>
					<comments>'.htmlentities(SITE_URL.'/index.php?post='.$postID,ENT_QUOTES,'UTF-8').'</comments>
					<link>'.htmlentities(SITE_URL.'/index.php?post='.$postID,ENT_QUOTES,'UTF-8').'</link>
					<quid>'.htmlentities($postID,ENT_QUOTES,'UTF-8').'</quid>
					<pubDate>'.htmlentities(date('D, d M Y H:i:s O',$postData['timestamp']),ENT_QUOTES,'UTF-8').'</pubDate>
					<title>'.htmlentities($postData['title'],ENT_QUOTES,'UTF-8').'</title>';
				
				//Control excerpt length if too big
				$postContent=$postData['content'];
				if(RSS_EXCEPRT_LENGTH>0&&strlen($postContent)>RSS_EXCERPT_LENGTH)
					$postContent=substr($postContent,0,RSS_EXCERPT_LENGTH-3).'...';
				
				//Barse bbCode?
				if(RSS_PARSE_BBCODE)
					$postContent=$this->parsePost($postContent,$postID,USE_GESHI_POSTS,true,true);
				
				//Clean up
				$postContent=htmlentities($postContent,ENT_QUOTES,'UTF-8');
				
				//Add in the content
				$post.='
					<description>
					'.$postContent.'
					</description>
					';
				
				//End the item
				$post.='
				</item>';
				
				$file.=$post;
			}
		}
		
		//Add the end
		$file.='
	</channel>
</rss>';
		return $file;
	}
	
	/*
	* Turn a username to an id
	*/
	function usernameToId($username) {
		return $this->tb_users->firstMatchingId('strtolower(%username%)==strtolower(\''.fetch::qEscape($username).'\')');
	}
	
	/*
	* Does a table exist?
	*/
	function table_exists($name) {
		return file_exists($this->_cfg['table_path'].'tb_'.$name.'.php');
	}
	
	/*
	* How many posts?
	*/
	function numOfPosts() {
		return count($this->tb_posts->select('true'));
	}
	
	/*
	* Does a post exist?
	*/
	function post_exists($post_id) {
		return count($this->tb_posts->select('%%ID%%==\''.fetch::qEscape($post_id).'\'',array()))===1;
	}
	
	/*
	* Does a page exist?
	*/
	function page_exists($page_id) {
		return count($this->tb_pages->select('%%ID%%==\''.fetch::qEscape($page_id).'\'',array()))===1;
	}
	
	/*
	* Loads posts made in the month of the year, defaulting to this year
	*/
	function loadposts_month($month,$year=false,$show_drafts=false,$fields=false) {
		if($month<1||$month>12)
			trigger_error('impossible month');
		if($year===false)
			$year=date('Y');
		$result=$this->tb_posts->select('%month%==\''.fetch::qEscape($month.$year).'\''.($show_drafts!==true?'&&%is_published%===true':''),$fields);
		if(count($result)==0)
			return false;
		else
			return $result;
	}
	
	/*
	* Loads all posts ever made
	*/
	function loadposts_all($show_drafts=false,$fields=false) {
		$result=$this->tb_posts->select('true'.($show_drafts!==true?'&&%is_published%===true':''),$fields);
		$result=$this->tb_posts->sortSelection($result,'timestamp',SORT_ASC);
		if(count($result)==0)
			return false;
		else
			return $result;
	}
	
	/*
	* Gets the list of categories
	*/
	function get_categories() {
		return $this->tb_categories->select('true');
	}
	
	/*
	* Gets posts in a category
	*/
	function get_cat($id) {
		if(!is_numeric($id))
			return false;
		
		return fetch::sortSelection($this->tb_posts->select('array_search('.$id.',%categories%)!==false && %is_published%'),'timestamp',SORT_DESC);
	}
	
	/*
	* Returns a category's name from its ID
	*/
	function cat_name($id) {
		if($this->tb_categories->idExists($id)===false)
			return false;
		
		return $this->tb_categories->getFieldValue($id,'name');
	}
	
	/*
	* Adds a category
	*/
	function add_category($name) {
		if($this->category_exists($name))
			return false;
		
		$this->tb_categories->addRow(array('name'=>$name));
		$this->tb_categories->commit();
		return true;
	}
	
	/*
	* Edits a category
	*/
	function edit_category($id,$newname) {
		if(!$this->category_id_exists($id)||$this->category_exists($newname))
			return false;
		
		$this->tb_categories->updateRow($id,array('name'=>$newname));
		$this->tb_categories->commit();
		return true;
	}
	
	/*
	* Deletes a category
	*/
	function delete_category($id) {
		//Get rid of listing only if it exists (duh) and if there is at least one category left
		if(!$this->tb_categories->idExists($id)||count($this->tb_categories->select('true',array()))<2)
			return false;
		
		//Get rid of post references
		$postsInCat=$this->get_cat($id);
		foreach($postsInCat as $postID=>$postData) {
			unset($postData['categories'][array_search($id,$postData['categories'])]);
			$this->tb_posts->updateRow($postID,array('categories'=>$postData['categories']));
		}
		
		$this->tb_posts->commit();
		$this->tb_categories->dropRow($id);
		$this->tb_categories->commit();
		return true;
	}
	
	/*
	* Returns a category's name
	*/
	function category_name($id) {
		if($this->tb_categories->idExists($id))
			return $this->tb_categories->getFieldValue($id,'name');
		return false;
	}
	
	/*
	* Checks if a category exists
	*/
	function category_exists($name) {
		return $this->tb_categories->firstMatchingId('%name%==\''.fetch::qEscape($name).'\'')!==false;
	}
	
	/*
	* Checks if a category ID exists
	*/
	function category_id_exists($id) {
		return $this->tb_categories->idExists($id);
	}
	
	/*
	* Swaps categories
	*/
	function category_swap($catID_one,$catID_two) {
		//Check if categories exist
		if(!$this->category_id_exists($catID_one)||!$this->category_id_exists($catID_two))
			return false;
		
		//Get posts in both categories
		$posts_one=$this->get_cat($catID_one);
		$posts_two=$this->get_cat($catID_two);
		
		//Merge into a single set of posts, to avoid problems with posts in both categories
		$posts_join=$posts_one+$posts_two;
		
		//Get names of both categories
		$name_one=$this->category_name($catID_one);
		$name_two=$this->category_name($catID_two);
		
		//Swap post references
		if(is_array($posts_join) && count($posts_join)>0) {
			foreach($posts_join as $postID=>$postData) {
				$editIndex_one=array_search($catID_one,$postData['categories']);
				$editIndex_two=array_search($catID_two,$postData['categories']);
				if($editIndex_one!==false)
					$postData['categories'][$editIndex_one]=$catID_two;
				if($editIndex_two!==false)
					$postData['categories'][$editIndex_two]=$catID_one;
				$this->tb_posts->updateRow($postID,array('categories'=>$postData['categories']));
			}
		}
		
		//Swap category names manually to avoid a duplicate name
		$this->tb_categories->updateRow($catID_one,array('name'=>false)); //name is a key so we have to make it unique first
		$this->tb_categories->updateRow($catID_two,array('name'=>$name_one));
		$this->tb_categories->updateRow($catID_one,array('name'=>$name_two));
		
		//Save changes
		$this->tb_posts->commit();
		$this->tb_categories->commit();
		
		return true;
	}
	
	/*
	* Makes a post
	*/
	function make_post($title, $content, $comments=true, $publish=true, $categories=array(0)) {
		//Add an archive entry if needed
		$archive_id=$this->tb_archives->firstMatchingId('%month%==\''.date('mY').'\'');
		if($archive_id===false) {
			$archive_month=array(
				'month'=>date('mY'),
				'created'=>time(),
			);
			$archive_id=$this->tb_archives->addRow($archive_month);
		}
		//Commit the changes
		$this->tb_archives->commit();
		
		//Grab categories that exist
		$cats=array();
		if(is_array($categories))
			foreach($categories as $catID)
				if($this->tb_categories->idExists($catID))
					$cats[]=$catID;
		else
			$cats[]=0; //0 for uncategorized
		
		//Add the post
		$post=array(
			'title'=>$title,
			'content'=>$content,
			'timestamp'=>time(),
			'comment_count'=>0,
			'month'=>date('mY'),
			'author'=>$_SESSION['auth_id'],
			'comments'=>($comments===true?true:false),
			'is_published'=>($publish===true?true:false),
			'categories'=>$cats,
		);
		$post_id=$this->tb_posts->addRow($post);
		$this->tb_posts->commit();
		return $post_id;
	}
	
	/*
	* Opens a post
	*/
	function open_post($post_id) {
		if(!is_int($post_id))
			return false;
		$result=$this->tb_posts->select('%%ID%%=='.$post_id);
		if(count($result)==0)
			return false;
		else
			return array_pop($result);
	}
	
	/*
	* Edits a post
	*/
	function edit_post($post_id, $title, $content, $comments=true, $publish=false, $categories=false) {
		$id=$this->tb_posts->firstMatchingId('%%ID%%=='.$post_id);
		if($id===false)
			trigger_error('The post to be edited does not exist');
		if($categories==false)
			$categories=array(0);
		$update=array(
			'title'=>$title,
			'content'=>$content,
			'comments'=>($comments===true?true:false),
			'is_published'=>$publish,
			'categories'=>$categories,
		);
		//Check if this post is about to be published and currently drafted
		if($publish&&!$this->tb_posts->getFieldValue($post_id,'is_published')) {
			//Update the time as well
			$update['timestamp']=time();
		}
		
		$this->tb_posts->updateRow($id,$update);
		$this->tb_posts->commit();
		return true;
	}
	
	/*
	* Deletes a post
	*/
	function delete_post($post_id) {
		$id=$this->tb_posts->firstMatchingId('%%ID%%=='.$post_id);
		$month=$this->tb_posts->getFieldValue($id,'month');
		if($id===false)
			trigger_error('The post to be deleted does not exist');
		$this->tb_posts->dropRow($id);
		$this->tb_posts->commit();
		//Now check if we need to delete the archive entry
		if($this->tb_posts->firstMatchingId('%month%==\''.$month.'\'')===false) {
			$archive_id=$this->tb_archives->firstMatchingId('%month%==\''.$month.'\'');
			if($this->tb_archives->idExists($archive_id)) {
				$this->tb_archives->dropRow($archive_id);
				$this->tb_archives->commit();
			}
		}
		return true;
	}
	
	/*
	* Makes a page
	*/
	function make_page($title, $content, $publish=true) {
		//Add the page
		$page=array(
			'title'=>$title,
			'content'=>$content,
			'timestamp'=>time(),
			'is_published'=>($publish===true?true:false),
		);
		$page_id=$this->tb_pages->addRow($page);
		$this->tb_pages->commit();
		return $page_id;
	}
	
	/*
	* Edits a page
	*/
	function edit_page($page_id, $title, $content, $publish=false) {
		$id=$this->tb_pages->firstMatchingId('%%ID%%=='.$page_id);
		if($id===false)
			trigger_error('The page to be edited does not exist');
		$update=array(
			'title'=>$title,
			'content'=>$content,
			'is_published'=>$publish,
		);
		$this->tb_pages->updateRow($id,$update);
		$this->tb_pages->commit();
		return true;
	}
	
	/*
	* Deletes a page
	*/
	function delete_page($page_id) {
		$id=$this->tb_pages->firstMatchingId('%%ID%%=='.$page_id);
		if($id===false)
			trigger_error('The page to be deleted does not exist');
		$this->tb_pages->dropRow($id);
		$this->tb_pages->commit();
		return true;
	}
	
	/*
	* Adds an attachment
	*/
	function attach($fileName,$tempFile,$uploadPath) {
		$realuploadPath=$uploadPath;
		if(!is_dir($realuploadPath)) {
			echo('<br />The upload path "'.htmlentities($realuploadPath,ENT_QUOTES,'UTF-8').'" does not exist');
			return false;
		}
		if($this->get_attachment($fileName,$uploadPath)!==false) {
			echo('<br />Error: Attachment filename already taken');
			return false;
		}
		if(filesize($tempFile)>MAX_UPLOAD_BYTES) {
			echo('<br />Error: Attachment too large');
			return false;
		}
		$uploadFile=$realuploadPath.'/'.$fileName;
		if(!move_uploaded_file($tempFile,$uploadFile)) {
			echo('<br />Error: Attachment did not upload properly');
			return false;
		}
		return $uploadFile;
	}
	
	/*
	* Gets a single attachment
	*/
	function get_attachment($fileName,$uploadPath) {
		$uploadPath=$uploadPath;
		$attachmentPath=$uploadPath.'/'.$fileName;
		if(!file_exists($attachmentPath))
			return false;
		$attachment['file_data']=file_get_contents($attachmentPath);
		$attachment['file_name']=$fileName;
		return $attachment;
	}
	
	/*
	* Gets a list of attachments
	*/
	function get_attachments($uploadPath) {
		$uploadPath=$uploadPath;
		$attachments=array();
		if($handle=@opendir($uploadPath)) {
			while(false!==($file=readdir($handle)))
				if($file != "."&&$file != "..")
					$attachments[]=$file;
		}
		else
			return false;
		closedir($handle);
		return $attachments;
	}
	
	/*
	* Generates the HTML required from an attachment search
	*/
	function render_attachment_search($is_post=true,$searchResults) {
		$html='';
		if($searchResults!==false) {
			if(count($searchResults)>0) {
				foreach($searchResults as $result) {
					$html.='<strong>'.$result.'</strong> &raquo; Add a <a href="javascript:addtext(\''.($is_post?'post':'page').'content\',\'[attach='.$result.']'.$result.'[/attach]\')">download link</a>';
					
					//If is image, then show the embed link
					$ext=substr(strrchr($result,'.'),1);
					if($ext=='jpg'||$ext=='gif'||$ext=='bmp'||$ext=='png')
						$html.=' or <a href="javascript:addtext(\''.($is_post?'post':'page').'content\',\'[img]download.php?attach='.$result.'[/img]\')">embed as image</a>';
					
					//If is video, then show the embed link
					$ext=substr(strrchr($result,'.'),1);
					if($ext=='flv'||$ext=='mp4'||$ext=='vp6')
						$html.=' or <a href="javascript:addtext(\''.($is_post?'post':'page').'content\',\'[vid='.$result.']Play Video[/vid]\')">attach with player</a>';
					
					$html.=' into this post<br />';
				}
			}
			else {
				$html.='No results were returned, try broadening your search';
			}
		}
		else {
			$html.='There was a problem getting the list of attachments; perhaps you haven\'t uploaded any?';
		}
		return $html;
	}
	
	/*
	* Searches for attachments
	*/
	function search_attachments($searchText,$uploadPath) {
		$results=array();
		$attachments=$this->get_attachments($uploadPath);
		
		//If there are no attachments just return false
		if(count($attachments)<=0)
			return false;
		
		//If nothing to search for, return all attachemtns
		if($searchText=='')
			return $attachments;
		
		//If not, go on searching
		if(is_array($attachments)) {
			foreach($attachments as $attachmentName) {
				if(strpos(strtolower($attachmentName),strtolower($searchText))!==false)
					$results[]=$attachmentName;
			}
			sort($results);
			return $results;
		}
		else
			return false;
	}
	
	/*
	* Gets last X attachments
	*/
	function recent_attachments($uploadPath,$limit=5) {
		$results=array();
		$attachments=$this->get_attachments($uploadPath);
		
		//If there are no attachments just return false
		if(count($attachments)<=0)
			return false;
		
		//If not, go on searching
		if(is_array($attachments)) {
			foreach($attachments as $attachmentName)
				$sortthis[$attachmentName]=filemtime($uploadPath.'/'.$attachmentName);
			arsort($sortthis);
			$i=0;
			$results=array();
			foreach($sortthis as $attachmentName=>$size) {
				if($i>$limit)
					break;
				array_push($results,$attachmentName);
				$i++;
			}
			return $results;
		}
		else
			return false;
	}
	
	/*
	* Deletes an attachment
	*/
	function delete_attachment($fileName,$uploadPath) {
		$uploadPath=$uploadPath;
		$attachmentPath=$uploadPath.'/'.$fileName;
		if(!file_exists($attachmentPath))
			return false;
		return unlink($attachmentPath);
	}
	
	/*
	* Parses (a post) with full bbcode
	*/
	function parsePost($post,$postid,$geshi=false,$readmore=true,$rss=false) {
		$parser=new uberbbcode($geshi,$postid,$rss===true);
		$parser->addCode($post);
		$parser->process($readmore);
		$parser->clean();
		//Phew, all done.
		return $parser->get();
	}
	
	/*
	* Parses bbcode
	*/
	function bbcode($string,$rss=false) {
		$parser=new escapedliteral('');
		return $parser->bbcode($string,$rss);
	}
	
	/*
	* Makes a comment
	*/
	function makeComment($target, $author, $comment, $email, $wordblacklist=array(), $ipblacklist=array()) {
		if($this->tb_posts->firstMatchingId('%%ID%%=='.$target)===false)
			die('Cannot comment to nonexistant post.');
		
		/*Get the last comment by this IP*/
		$commentsByIP=fetch::sortSelection($this->tb_comments->select('%ip%==\''.fetch::qEscape($_SERVER['REMOTE_ADDR']).'\'',array('timestamp')),'timestamp',SORT_ASC);
		if(count($commentsByIP)>0)
			$lastCmmnt=array_pop($commentsByIP);
		
		/*If the last comment was over x seconds*/
		if($this->is_logged_in() || (count($commentsByIP)>=0 && $lastCmmnt['timestamp']+MINIMUM_COMMENT_INTERVAL<time())) {
			$approved=false;
			
			//If we are always approving, or if we are already logged into admin
			if(ALWAYS_AUTO_APPROVE_COMMENTS || $this->is_logged_in() ||
			/*Check for previously approved comment by this email and IP address*/
			(AUTO_APPROVE_COMMENTS && count($this->tb_comments->select('%email%==\''.fetch::qEscape($email).'\'&&%approved%&&%ip%==\''.fetch::qEscape($_SERVER['REMOTE_ADDR']).'\'',array()))>0)) {
				$approved=true;
			}
			
			/*Look for blacklisted IP*/
			if(count($ipblacklist)>0)
				foreach($ipblacklist as $badip)
					if(strpos($_SERVER['REMOTE_ADDR'],$badip)===0)
						if(IP_BLACKLIST_DENY)
							return NULL;
						else
							$approved=false;
			
			/*Look for blacklisted words*/
			$commentlower=strtolower($author.$comment);
			if(count($wordblacklist)>0)
				foreach($wordblacklist as $badword)
					if(strpos($commentlower,strtolower($badword))!==false&&WORD_BLACKLIST_OVERRIDE)
						$approved=false;
			
			/*Add comment*/
			$this->tb_comments->addRow(
				array(
					'parent'=>$target,
					'name'=>$author,
					'content'=>$comment,
					'email'=>$email,
					'timestamp'=>time(),
					'ip'=>$_SERVER['REMOTE_ADDR'],
					'approved'=>$approved,
					'prominent'=>$this->is_logged_in()?$_SESSION['auth_id']:false,
				)
			);
			$this->tb_comments->commit();
			//Need to add to post count if automatically approved
			if($approved) {
				/*Increment post count*/
				$commentCount=$this->tb_posts->getFieldValue($target,'comment_count');
				$this->tb_posts->updateRow($target,array('comment_count'=>$commentCount+1));
				$this->tb_posts->commit();
			}
			return $approved;
		}
		return NULL;
	}
	
	/*
	* Approves a comment
	*/
	function approveComment($commentID) {
		//Check if comment exists
		if(!$this->tb_comments->idExists($commentID))
			trigger_error('Cannot approve nonexistant comment (ID "'.$commentID.'")');
		//Check if not already approved
		if(!$this->tb_comments->getFieldValue($commentID,'approved')) {
			//Approve comment
			$this->tb_comments->updateRow($commentID,array('approved'=>true));
			$this->tb_comments->commit();
			//Add to post comment count
			$parent=$this->tb_comments->getFieldValue($commentID,'parent');
			$this->tb_posts->updateRow($parent,array('comment_count'=>$this->tb_posts->getFieldValue($parent,'comment_count')+1));
			$this->tb_posts->commit();
		}
	}
	
	/*
	* Unapproves a comment
	*/
	function unapproveComment($commentID) {
		//Check if comment exists
		if(!$this->tb_comments->idExists($commentID))
			trigger_error('Cannot unapprove nonexistant comment (ID "'.$commentID.'")');
		//Check if already approved
		if($this->tb_comments->getFieldValue($commentID,'approved')) {
			//Unapprove comment
			$this->tb_comments->updateRow($commentID,array('approved'=>false));
			$this->tb_comments->commit();
			//Subtract from post comment count
			$parent=$this->tb_comments->getFieldValue($commentID,'parent');
			$this->tb_posts->updateRow($parent,array('comment_count'=>$this->tb_posts->getFieldValue($parent,'comment_count')-1));
			$this->tb_posts->commit();
		}
	}
	
	/*
	* Deletes a comment
	*/
	function deleteComment($commentID) {
		//Check if comment exists
		if(!$this->tb_comments->idExists($commentID))
			trigger_error('Cannot delete nonexistant comment (ID "'.$commentID.'")');
		//Delete comment
		$this->tb_comments->dropRow($commentID);
		$this->tb_comments->commit();
		//Subtract from post comment count only if comment was already approved
		if($this->tb_comments->getFieldValue($commentID,'approved')) {
			$parent=$this->tb_comments->getFieldValue($commentID,'parent');
			$this->tb_posts->updateRow($parent,array('comment_count'=>$this->tb_posts->getFieldValue($parent,'comment_count')-1));
			$this->tb_posts->commit();
		}
	}
	
	/*
	* Blacklists a comment (deletes all by same IP)
	*/
	function blacklistCommentIP($commentID) {
		//Get the IP
		$IP=$this->tb_comments->getFieldValue($commentID,'ip');
		//Get all comments by this IP
		$comments=$this->tb_comments->select('%ip%==\''.fetch::qEscape($IP).'\''.array());
		//Delete all posts by this IP
		foreach($comments as $cid=>$junk) {
			$this->tb_comments->dropRow($cid);
		}
		$this->tb_comments->commit();
		//Return deleted amount
		return count($comments);
	}

	/*
	* Creates a user
	*/
	function create_user($username, $password) {	
		$exists=$this->usernameToId($username);
		if($exists===false){
			$this->tb_users->addRow(array('username'=>$username,'hash'=>md5($password)));
			$this->tb_users->commit();
			return true;
		} else {			
			echo('<h2 class="red">Error</h2>
			A user with that name exists already. Please change.<br />');
			return false;
		}
	}
	
	/*
	* Edits a user
	*/
	function edit_user($user_id,$data) {
		if($this->tb_users->firstMatchingId('%%ID%%=='.$user_id)!==false) {
		$this->tb_users->updateRow($user_id,$data);
			$this->tb_users->commit();
			return true;
		}
		else
		 return false;
	}
	
	/*
	* Deletes a user
	*/
	function delete_user($user_id) {
		if($this->tb_users->firstMatchingId('%%ID%%=='.$user_id)!==false) {
			$comments=$this->tb_comments->select('%prominent%==\''.$user_id.'\'');
			$name=$this->tb_users->getFieldValue($user_id,'username');
			foreach($comments as $id=>$data) {
				if(!$this->post_exists($data['parent']))
					$this->tb_comments->dropRow($id);
				else
					$this->tb_comments->updateRow($id,array('prominent'=>false,'name'=>$name,'email'=>'n/a'));
			}
			$this->tb_comments->commit();
			$this->tb_users->dropRow($user_id);
			$this->tb_users->commit();
			return true;
		}
		else
		 return false;
	}
	
	function refresh_comments() {
		$orphaned_comments=$this->tb_comments->select('true');
		foreach($orphaned_comments as $comment_id=>$data)
			if(!$this->post_exists($data['parent']))
				$this->tb_comments->dropRow($comment_id);
		$this->tb_comments->commit();
	}
	
	/*
	* Checks if an email address is valid
	* Used with permission from Cal Henderson
	* http://www.iamcal.com/publish/articles/php/parsing_email/
	*/
	function is_valid_email($email){
		$qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
		$dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
		$atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c'.
			'\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
		$quoted_pair = '\\x5c[\\x00-\\x7f]';
		$domain_literal = "\\x5b($dtext|$quoted_pair)*\\x5d";
		$quoted_string = "\\x22($qtext|$quoted_pair)*\\x22";
		$domain_ref = $atom;
		$sub_domain = "($domain_ref|$domain_literal)";
		$word = "($atom|$quoted_string)";
		$domain = "$sub_domain(\\x2e$sub_domain)*";
		$local_part = "$word(\\x2e$word)*";
		$addr_spec = "$local_part\\x40$domain";
		return preg_match("!^$addr_spec$!", $email) ? 1 : 0;
	}
}
?>