I have created a blog class that is used to build the posts for each blog.
Class blog {
private $db, $path;
private $posts = array();
function __construct() {
$this->db = new database();
}
// get a setting value from database
function setting($name) {
$sql = "SELECT value FROM ".PREFIX."settings WHERE name='".$name."'";
$this->db->query($sql);
return $this->db->get("value");
}
//get all posts or $this->posts
function posts() {
$html = "";
$sql = "SELECT
".PREFIX."posts.title,
".PREFIX."posts.sub_title,
DATE_FORMAT(".PREFIX."posts.post_date, '%D of %M %Y') AS posted,
DATE_FORMAT(".PREFIX."posts.post_date, '%Y') AS year,
LCASE(DATE_FORMAT(".PREFIX."posts.post_date, '%M')) AS month,
LCASE(DATE_FORMAT(".PREFIX."posts.post_date, '%W')) AS day,
alias,
".PREFIX."posts.content
FROM ".PREFIX."posts
WHERE ".PREFIX."posts.post_date < NOW() AND ".PREFIX."posts.published = 'Y'";
if(count($this->posts) > 0) {
$sql .= " AND ".PREFIX."posts.id='".implode("' OR ".PREFIX."posts.post_date < NOW() AND ".PREFIX."posts.published = 'Y' AND ".PREFIX."posts.id='",$this->posts)."'";
}
$sql .= " ORDER BY post_date DESC";
$this->db->query($sql);
$x = 1;
while($this->db->next()) {
$html .= '<h1><a href="/'.$this->db->get("year").'/'.$this->db->get("month").'/'.$this->db->get("day").'/'.$this->db->get("alias").'">'.$this->db->get("title").'</a></h1>';
$html .= '<h2>'.$this->db->get("sub_title").'</h2>';
$html .= '<h3>'.$this->db->get("posted").'</h3>';
$html .= $this->db->get("content");
if($x < $this->db->num_rows()) {
$html .= '<hr>';
$x++;
}
}
return $html;
}
//get post title
function title() {
//single post
if(count($this->path) > 3) {
$sql = "SELECT title
FROM ".PREFIX."posts
WHERE id='".$this->posts[0]."'";
$this->db->query($sql);
return $this->db->get("title");
} else { //this returns Monday September 2008 etc
return implode(" ",array_map("ucfirst",array_reverse($this->path)));
}
}
//find out if url is valid.
//if url is valid store in $this->posts
function parse_url($url) {
// path format for blog is / YEAR / MONTH / DAY / ALIAS
$this->path = preg_split('/\//', $url, -1, PREG_SPLIT_NO_EMPTY);
//find out if page is valid page
$sql = "SELECT id FROM ".PREFIX."posts WHERE published='Y' AND post_date < NOW()";
$pathlength = count($this->path);
if($pathlength > 0) {
$sql .= " AND YEAR(post_date) = '".$this->path[0]."'";
if($pathlength > 1) {
$sql .= " AND LCASE(DATE_FORMAT(post_date, '%M')) = '".$this->path[1]."'";
if($pathlength > 2) {
$sql .= " AND LCASE(DATE_FORMAT(post_date, '%W')) = '".$this->path[2]."'";
if($pathlength > 3) {
$sql .= " AND alias = '".$this->path[3]."'";
}
}
}
}
$sql .= " ORDER BY post_date DESC";
$this->db->query($sql);
if($this->db->num_rows() > 0) {
while($this->db->next()) {
array_push($this->posts, $this->db->get("id"));
}
return true;
}
return false;
}
}
There is a settings method that allows us to retrieve a value from a settings table in the database. This is a new table that has the following structure:
CREATE TABLE IF NOT EXISTS `blog_settings` ( `name` varchar(50) NOT NULL, `value` varchar(255) NOT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
The Blog Class also has a method call "parse_url" that determins valid posts. This is useful if a post exists or has been turned off or even deleted and will allow us to output a 404 error.
The Posts method does most of the work and returns the post html.Title returns the title of the post. In my next post I will explain how all this goes together with the database class.
I mentioned in my previous post that I would talk about connecting to the database.
Firstly I created a config.php file that stores the basic database connection details and anything else that will be required as a global configuration value. This file currently has the following content:
// Database Connection Details
define('DATABASE', '#####' );
define('HOST', '#####' );
define('USER', '#####' );
define('PASSWORD', '#####' );
define('PREFIX', 'blog_' );
The hashes represent the mySQL connection values.
Next, I wrote a database class that will be used to query the database. I've added some functions that I call "helper" functions that will make inserting, updating and retrieval easier.
The database class currently looks like this:
Class database extends mysqli {
private $result; //store query result
private $row; //store current row
private $link; //store database link
function __construct() {
$this->link = parent::__construct(HOST,USER,PASSWORD,DATABASE);
}
//use these functions to escape user input
function escape($var) {
if(get_magic_quotes_gpc()){
$var = stripslashes($var);
}
return $this->real_escape_string($var);
}
function escape_get($var) {
return $this->escape($_GET[$var]);
}
function escape_post($var) {
return $this->escape($_POST[$var]);
}
// get a result from database
// can be field name or numeric position
function get($field) {
if($this->row != NULL) {
if(is_string($field)) {
return $this->row[$field];
} else {
$keys = array_keys($this->row);
return $this->row[$keys[$field]];
}
}
return false;
}
function getArray(){
if($this->row != NULL) {
$return = array();
while($this->next()) {
array_push($return,$this->row);
}
$this->first();
return $return;
}
return false;
}
//get result from database as escaped html
//this is useful for ajax
function getSafe($field) {
return htmlspecialchars($this->get($field));
}
//query the database , insert, update, select etc
function query($sql,$resultmode=MYSQLI_STORE_RESULT) {
$this->result = parent::query($sql,$resultmode);
if(!$this->result) {
printf("Error: %s\n", $this->error);
}
//get first item and then reset pointer in case we want to loop results
if(is_object($this->result) && $this->result->num_rows>0) {
$this->row = $this->result->fetch_assoc();
$this->first();
}
return $this->result;
}
function first() {
if($this->result) {
$this->result->field_seek(0);
$this->result->data_seek(0);
return true;
}
return false;
}
function num_rows(){
if($this->result != NULL) {
return $this->result->num_rows;
}
return false;
}
function next(){
if($this->result != NULL && ($this->row = $this->result->fetch_assoc()) != NULL) {
return $this->row;
}
return false;
}
}
You might notice that the database class extends the MySQL Improved Extension (mysqli). The advantage of doing this means I will be able to access the mysqli properties and methods without needing to re-implement them in the database class. For example if I want to access the last insert id I can use the following code:
include("config.php");
include("class/class.database.php");
$db = new database();
$sql = "INSERT INTO ".PREFIX."posts SET title = 'test insert'";
$db->query($sql);
echo $db->insert_id;
I hinted at using Apache mod_rewrite to clean up the urls for this blog in my previous post.
I have now enabled this feature using a few simple lines in a .htaccess file. Some webhosts don't always allow .htaccess files as they consider them a security risk so it may not work for you. In that case consult your webhost or server configuration.
So, to enable the url rewriting I created a .htaccess file in the root of this site with the following contents:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?url=$1 [L]
#php_value memory_limit 80M
#php_value upload_max_filesize 20M
#DirectoryIndex index.php
php_flag display_errors On
Basically the first line turns on the rewrite engine and the next two lines tell Apache to ignore files and directories. This allows you to navigate directly to files without the rewrite taking over. The next line applies the rewrite and forces all urls to be passed to index.php. ?url=$1 puts all of the url into a variable called url. We will access this variable in php like this: echo $_GET['url']; and it will return something like: /categories/movies. The [L] flag tells Apache to stop the rewriting process here and don't apply any more rewrite rules.
The next lines I might use in the future to override some default php/Apache settings. The hash comments them out. The final line turns on php errors because my webhost by default does not show errors and this makes development a bit tedious.
In my next post I will go over what is in the index.php file and connect it to the database.
I've decided to use a traditional LAMP (Linux, Apache, MySQL and php) setup for this project mainly because its what I'm familiar with.
My host has Apache 2, MySQL 5 and php 5 so I will be using this as the minimum requirements. I'm sure that most of the code I write will work with older versions but I'm not going to any extra effort to test this.
Today I've started thinking about the database structure. I've created a database and started building the tables in phpMyAdmin. To start off with I've made a posts table which will store all the blog posts.
The posts table initial structure is:
CREATE TABLE IF NOT EXISTS `blog_posts` (
`id` int(11) NOT NULL auto_increment,
`post_date` datetime NOT NULL,
`created` datetime NOT NULL,
`edited` datetime NOT NULL,
`title` varchar(60) NOT NULL,
`sub_title` varchar(60) NOT NULL default '',
`content` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
You might notice that I've added a prefix to the table name (blog_) as I think this will help organisation of the database in the future and will also make it possible to have multiple blogs using the one database. The ID field is auto incremented and will serve as the unique identifier for each row.
I was going to have a category_id field to link a post to a category but I want posts to have more than one category. To solve this I've created a separate table that will link up the post table and the category table.
The structure for these two tables is:
CREATE TABLE IF NOT EXISTS `blog_category` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(50) NOT NULL,
`alias` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
CREATE TABLE IF NOT EXISTS `blog_post_category` (
`post_id` int(11) NOT NULL,
`category_id` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
The alias field in the blog_category table will be used in the url of the page. Eventually I want to use clean url's that will help with human readability and search indexing. Using this alias field I will be able to browse the blog categories with urls like this: http://www.castlesblog.com/category/drums or http://www.castlesblog.com/category/computers
Today I went to the effort of plugging this domain into a few services so that I can monitor visits and what not. My host does provide awstats which is great but I couldn't go past a Google Analytics account. While I was at it I added this site to Google Webmaster Tools.
The webmaster tools recommended I add a robots.txt file and as this might be useful in the future I went straight ahead. They also wanted a sitemap so away I went and whipped up something simple.
Google provides some fantastic services to help with website indexing. It will be interesting to see how all this pans out. This got me wondering what services other search engines provide. Ten minutes later and I've signed up for a Webmaster Center account and a Yahoo Site Explorer account. Neither of these appear to have the same level of features but still, I don't think it can hurt to see what happens.
If you are surprised at this release let me explain myself. I've grown very tired of my previous site and have't updated it for months. I don't really have that much to say so the blog was somewhere between pointless and lame. This change is the begining of a new direction for this site.
What CastlesBlog will become is its own Blog. That's right, I'm going to build a custom blog from scratch. Its not really intended to compete with other blogging software (eg. WordPress) but rather serve as a training medium for both myself and others. Hopefully when I get comments working the community will steer the direction of this project. I will document each change in a post and release code each release. Each release will also include a screenshot because everyone loves screenshots.
Currently this project will have the following guidelines (this may change):
As of today you can grab the latest version of CastlesBlog by viewing the source and copying it.