<?php
/*
** Post class
** Handles all database things dealing with the "posts" table. All the get_*
** functions return HTML-friendly code. All the set_* functions do sanity
** checks (string length, etc). All queries use prepared statements to prevent
** SQL injections.
**
** Also, the database is not "trusted", so if your database gets injected with
** values that don't pass the above mentioned sanity checks an error will be
** set.
**
** Note: do not rely on the function get_error() for hard-coded error messages,
** they may change over time!
**
*/
class Post
{
private $error_desc; // string to hold error information
// database fields
private $date;
private $id;
private $name;
private $message;
private $ip;
// used in error checking
public static $max_name_length = 20;
public static $max_message_length = 255;
public static $post_threshhold = 120; // min number of seconds between
// posts for a given IP address
function __construct()
{
$this->date = -1;
$this->id = -1;
$this->name = '';
$this->message = '';
$this->ip = '';
}
/*
** accessors
**
** input: none
** output: HTML-friendly private member variables
*/
public function get_error()
{
return htmlentities($this->error_desc, ENT_QUOTES);
}
public function get_id()
{
return htmlentities($this->id, ENT_QUOTES);
}
public function get_name()
{
$ret = $this->name;
// cut off long lines to prevent horizontal scroll bars
$ret = wordwrap($ret, 10, ' ', true);
// replace html entities
$ret = htmlentities($ret, ENT_QUOTES);
// replace newlines with breaks
$ret = nl2br($ret);
return $ret;
}
public function get_message()
{
$ret = $this->message;
$ret = wordwrap($ret, 60, " ", true);
$ret = htmlentities($ret, ENT_QUOTES);
$ret = nl2br($ret);
return $ret;
}
public function get_ip()
{
return htmlentities($this->ip, ENT_QUOTES);
}
public function get_date()
{
return htmlentities($this->date, ENT_QUOTES);
}
/*
** mutators
**
** input: values to set private member variables to
** output: false (and set error_desc) if error, true if success
*/
public function set_id($value)
{
if (!is_numeric($value) || $value < 1)
{
$this->error_desc = 'id must be numeric, greater than 1';
return false;
}
$this->id = $value;
return true;
}
public function set_name($value)
{
$length = strlen($value);
if ($length < 3 || $length > Post::$max_name_length)
{
$this->error_desc = 'name must be between 3 and '
. Post::$max_name_length . ' characters';
return false;
}
$this->name = $value;
return true;
}
public function set_message($value)
{
$length = strlen($value);
if ($length < 1 || $length > Post::$max_message_length)
{
$this->error_desc = 'message must be between 3 and '
. Post::$max_message_length . ' characters';
return false;
}
$this->message = $value;
return true;
}
public function set_ip($value)
{
$length = strlen($value);
if ($length < 7 || $length > 15)
{
$this->error_desc = 'ip must be between 7 and 15 characters';
return false;
}
$this->ip = $value;
return true;
}
public function set_date($value)
{
if (!is_numeric($value))
{
$this->error_desc = 'incorrect date format';
return false;
}
else
{
$this->date = $value;
}
return true;
}
/*
** save
**
** input: PDO database handle
** output: false (and set error_desc) if error, true if success
*/
public function save($db)
{
// make sure this ip didn't write to db within the minimum
// threshhold time
$sql = 'SELECT post_date
FROM posts
WHERE post_ip = :ip
ORDER BY post_id DESC';
try
{
$statement = $db->prepare($sql);
$statement->execute(array(':ip' => $_SERVER['REMOTE_ADDR']));
}
catch (Exception $exc)
{
$error_desc = 'problem with SQL';
return false;
}
$result = $statement->fetchAll();
if (isset($result[0]) &&
($result[0]['post_date'] + Post::$post_threshhold) > time())
{
$this->error_desc = 'ip address already posted (minimum time between posts is
' . Post::$post_threshhold . ' seconds)';
return false;
}
// new ip, let them post:
$sql = 'INSERT INTO posts (post_name, post_message, post_ip, post_date)
VALUES (:name, :message, :ip, :date)';
try
{
$statement = $db->prepare($sql);
}
catch (Exception $exc)
{
$this->error_desc = 'problem with SQL';
return false;
}
// execute statement
try
{
$statement->execute(array(':name' => $this->name,
':message' => $this->message,
':ip' => $this->ip,
':date' => $this->date));
}
catch (Exception $exc)
{
$this->error_desc = 'problem inserting into database';
return false;
}
// check to make sure statement inserted
if ($statement->rowCount() == 0)
{
$this->error_desc = 'problem inserting into database';
return false;
}
return true;
}
/*
** retrieve
**
** sets private member variables from data in database according to post_id
**
** input: PDO database handle
** output: false (and set error_desc) if error, true if success
*/
public function retrieve($db)
{
if (!is_numeric($this->id) || $this->id < 1)
{
$this->error_desc = 'invalid id';
return false;
}
$sql = 'SELECT post_name, post_message
FROM posts
WHERE post_id = :id';
try
{
$statement = $db->prepare($sql);
$statement->execute(array(':id' => $this->id));
}
catch (Exception $exc)
{
$error_desc = 'problem with SQL';
return false;
}
$results = $statement->fetchAll();
// make sure we found a match
if(count($results) == 0)
{
$this->error_desc = 'no rows matching that id';
return false;
}
// set vars, check to make sure data is in correct format
if (!$this->set_name($results[0]['post_name']) ||
!$this->set_message($results[0]['post_message']))
{
$error_desc = 'invalid row, possible database corruption!';
return false;
}
return true;
}
/*
** get_posts (static)
**
** returns array of all postss in the database in DESC order.
** can return array with no data.
**
** input: PDO database handle
** output: false (and set error_desc) if error, array if success
*/
public static function get_posts($db)
{
$posts = array();
$results = array();
$sql = 'SELECT post_name, post_message
FROM posts
ORDER BY post_id DESC';
try
{
$statement = $db->prepare($sql);
$statement->execute();
}
catch (Exception $exc)
{
$error_desc = 'problem with SQL';
return false;
}
$results = $statement->fetchAll(PDO::FETCH_ASSOC);
if (count($results) > 0)
{
for($i = 0; $i < count($results); $i++)
{
$post = new Post();
if (!$post->set_name($results[$i]['post_name']) ||
!$post->set_message($results[$i]['post_message']))
{
$error_desc = 'invalid row found in database';
return false;
}
array_push($posts, $post);
}
}
return $posts;
}
/*
** prune (static)
**
** deletes all posts with lower ids than $num'th row (in DESC order)
** eg: we want to keep 15 of newest posts, get 15 highest
** post_ids and delete all post_ids below that.
**
** input: PDO database handle, number of posts to keep
** output: false if error, true if success
*/
public static function prune($db, $num)
{
if (!is_numeric($num) || $num < 1)
{
return false;
}
// delete posts with post_ids between 1 and lowest post_id of set of
// newest $num posts
$sql_sel = 'SELECT post_id
FROM posts
ORDER BY post_id DESC
LIMIT '.$num.', 1';
$sql_del = 'DELETE FROM posts WHERE post_id <= :id';
try
{
$statement = $db->prepare($sql_sel);
$statement->execute();
$results = $statement->fetchAll(PDO::FETCH_ASSOC);
if (isset($results[0]))
{
$statement = $db->prepare($sql_del);
$statement->execute(array(':id' => $results[0]['post_id']));
}
}
catch (Exception $exc)
{
return false;
}
return true;
}
}
?>