Simple CMS

Content Management Systems (CMS) are software frameworks that allow people to easily maintain the contents of a web site. A good CMS will even allow a user to edit the structure of the page itself, like the navigation links that appear on each page. Designing a CMS may seem tough to a PHP novice, but this article will show that it only takes a handful of routines to drive the entire site.

Note: I started with some of the basic concepts from my last article, Scratch Pad, and expanded it to create an entire CMS. I suggest reading about the Scratch Pad before jumping to this stage.

Note that this CMS is not very feature-rich. You have to know HTML to edit articles, and a lot of security and validation steps are skipped. This is meant mainly as a tutorial. Because I have released this code under the GPL, others are free to turn this into a rich, usable CMS.

The heart of this CMS is in one class named Article. Article handles three main actions: displaying a form for the user to edit an article, saving an article when a user hits save, and displaying an article depending on the URL. Here are the contents of inc_article.php:


<?php
/**
 * simple CMS class
 * assumes table:

create table articles(
  url text,
  title text,
  body text,
  stamp datetime
);

grant select, insert, update on devblog.* 
    to 'author'@'localhost' identified by 'password';

 */

define('OPT_DB_CONN', 'mysql:host=localhost;dbname=mydatabase');
define('OPT_DB_READONLY_USER', 'author');
define('OPT_DB_READONLY_PASS', 'password');
define('OPT_DB_READWRITE_USER', 'author');
define('OPT_DB_READWRITE_PASS', 'password');

class Article {
  public function handle_post() {
    if (isset($_POST['url'])) {
      $db = new PDO(OPT_DB_CONN, OPT_DB_READWRITE_USER, OPT_DB_READWRITE_PASS);
      if (isset($_GET['url'])) {
        $sql = 'update articles set url=?, title=?, body=?'
            . ' where url=?';
      } else {
        $sql = 'insert into articles (url, title, body, stamp)'
            . ' values (?,?,?,now());';
      }
      $stmt = $db->prepare($sql);
      $stmt->bindParam(1,$_POST['url']);
      $stmt->bindParam(2,$_POST['title']);
      $stmt->bindParam(3,$_POST['body']);
      if (isset($_GET['url'])) $stmt->bindParam(4,$_GET['url']);
      $stmt->execute();
      $db = null;
    }
  }

  public function get_article($url, &$article) {
    if ($url != '') {
      $db = new PDO(OPT_DB_CONN, OPT_DB_READONLY_USER, OPT_DB_READONLY_PASS);
      $sql = 'select url, title, body from articles where url=?';
      $stmt = $db->prepare($sql);
      $stmt->bindParam(1,$url);
      if ($stmt->execute()) {
        if ($row = $stmt->fetch()) {
          $article['url'] = $row['url'];
          $article['title'] = $row['title'];
          $article['body'] = $row['body'];
        }
      }
      $db = null;
    }
  }

  public function display_article_form($article) {
    echo "<form action=\"article_editor.php";
    if (isset($_GET['url'])) echo "?url=" . $_GET['url'];
    echo "\" method=\"post\">\n";
    echo "<p>";
    echo "  URL (used in apache mod_rewrite)<br />\n";
    echo "  <input name=\"url\" value=\"";
    echo $article['url'];
    echo "\" /><br />\n";
    echo "  Title (displayed as an h3 header)<br />\n";
    echo "  <input name=\"title\" value=\"";
    echo $article['title'];
    echo "\" /><br />\n";
    echo "  Article body.  No markup is added or filtered.<br />\n";
    echo "  <textarea name=\"body\" cols=\"80\" rows=\"24\">\n";
    echo htmlspecialchars($article['body']);
    echo "  </textarea><br />\n";
    echo "  <input type=\"submit\" value=\"save\" />\n";
    echo "</p>\n";
    echo "</form>\n";
  }
}

?>

To create a full site with the class I create the following files:

  • article_editor.php which uses Article->display_article_form() and Article->handle_post() to perform page editing
  • article.php which checks the url for a ?url=foo and then does a get_article('foo') to display the article named foo found in the database.
  • inc_header.php which displays the common header for all files on the site (the <head> tag and nav structures). The header is simply an article named "header", so it performs a get_article('header').
  • inc_footer.php that operates similarly to inc_header.php
  • .htaccess file that protects article_editor.php with a password and routes /foo urls to /article.php?url=foo

I'll be promoting the code for SimpleCMS to a full project. If you'd like a copy of the rest of the source in the meantime, let me know.

I've set up a demo of SimpleCMS on my server, and initialized it with a very simple web site. Feel free to experiment with it.

I've already ported all of my articles into a derivative of this CMS design. I also plan to create a CMS for my Software Development and Profile pages.