Abstract Project Description Simple Database Page Layout Displaying the Blog "Add Entry" Form Inline Edit Form Online Demo
Describes a method of implementing a browser-based edit-in-place feature for quickly adding or editing web published articles, using a weblog (blog) application as a basis. The application can stand alone or be easily integrated into an existing portal-style web page. Requires mid-level PHP experience and a hosting service with PHP and MySQL support.
I use a did-it-myself blog on my home system as a private journal. Initially I had it set up so the content managment system was a separate page for new article entry and yet another to edit existing articles. I wanted to simplify it. I had played with the open source application "phpWebSite" and liked the idea of being able to edit articles within the page that also displays them normally. I decided to implement this technique in my own blog-journal.
The project consists of a page of XHTML/CSS enabled by PHP and a MySQL database. Two tables in the database, "author" and "content" permit session authorization and article control, respectively. When an authorized "author" is logged in, articles will bear an "Edit" link which will activate the editor for that article. The edit form will replace the article in the display stream on the page. Only the 5 or 10 most current articles will therefore be available for editing, depending on how many you want displayed in your blog.
This application only requires two database tables, "author" and "content":
CREATE TABLE author ( Author_Num tinyint(4) NOT NULL auto_increment, Fname varchar(20) default NULL, Lname varchar(20) default NULL, Passwd varchar(12) default NULL, PRIMARY KEY (Author_Num), UNIQUE KEY id (Author_Num) ) TYPE=MyISAM; INSERT INTO author VALUES (2, 'Site', 'Admin', 'secret');
The SQL table constructor query above creates the author table and its various fields. Each author has an automatically generated id number, first and last names, and a password. That last line creates the first author, "Site Admin" with a password of "secret".
CREATE TABLE content ( Id smallint(4) NOT NULL auto_increment, Posted varchar(19) default NULL, Updated varchar(19) default NULL, Begin varchar(10) default NULL, Expires varchar(10) default NULL, Head varchar(128) default NULL, Deck varchar(128) default NULL, Body text, PRIMARY KEY (Id), UNIQUE KEY id (Id) ) TYPE=MyISAM;
Put those two snippets into a file called "tables.sql" and upload to your host.
You will have to create a database, then SQL those files or import them. It's usually easiest to use your host's web tools to do this. Under Linux hosts, telnet or ssh into your host shell, and give this sequence of cli (command line interface) commands:
$ mysql -u user --password=pass create blog mysql> exit $ mysql -u user --password=pass blog < tables.sql mysql> exit
Our page will consist of a standard XHTML structure controlled by CSS. If you want a CSS tutorial, visit http://www.w3schools.com/.
The template I give here is just enough to show the blog and the add/edit functions. I leave it to you to customize it with your own stylesheet embelishments. The page will be named "index.php". Here's the XHTML-Strict code:
<?php
session_start();
?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>My Weblog</title>
<meta name="description" content="your site description" />
<meta name="keywords" content="your list of keywords" />
<link rel="shortcut icon" href="i/favicon.ico" type="image/ico" />
<style type="text/css"><!--
@import url("c/index.css");
-->
</style>
<?php include 'inc/stylesheets.inc'; ?>
<body>
<!-- M A S T H E A D ======================================== -->
<h1>My Weblog</h1>
<h2>Personal Journal</h2>
<!--end masthead-->
<!-- M A I N B L O G C O L U M N ========================= -->
<div id="main">
</div><!-- end main div-->
</body>
</html>
And here's the CSS stylesheet ("index.css") that makes it work:
/* basic font and color setting */
body {
font:.8em/1.2 geneva,arial,helvetica,sans-serif;
color:#333;
}
/* basic element settings */
a {
color: #333;
}
a:visited {
color: #999;
}
h1 {
font: 1.5em/1.0 geneva,arial,sans-serif;
}
h2 {
font: 1.2em/1.0 geneva,arial,sans-serif;
margin: 0 0 0 0;
}
h3 {
margin: 1em 0 0 0;
}
input {
font-size: .75em;
}
/* major page block definitions and placement */
#main {
position: absolute;
top: 80px;
left: 20%;
width: 65%;
}
/* styling for the articles */
.dbar {
font: 1em/1.0 geneva,sans-serif;
font-weight: 700;
width: 100%;
margin: 0 0 0 -1px; /* t r b l */
background-color: #ffd;
color: #333;
padding: 2px;
}
.head {
font-weight: 700;
}
.deck {
font-weight: 500;
font-style: italic;
}
.body {
color: #333;
}
/* use this style to embed images in article bodies:
ex.:
<div class="flrt"><img src="i/image.jpg" alt="" /></div>
*/
.flrt {
float: right;
margin-left: 1em;
}
/* these are used by the "toggleAdd()" JavaScript
function to hide/show the Add form */
.hideadd {
display: none;
z-index: 7;
}
.showadd {
display: block;
z-index: 7;
}
Put that css file in a subdirectory called simply, "c/".
We need some code to log into the database server. I usually put that in a separate file, called "db.inc" and put it in a subdirectory called "inc/". You have to use your own host's values for these variables (user and pass will be the same ones you used to create and table your database, above):
<?php $hostName = "localhost"; $databaseName = "blog"; $userName = "user"; $passWord = "pass"; ?>
Here's another one for the "inc/" directory, that displays errors in a nice way, if any occurr:
<?php
function showError()
{
die("Error " . mysql_errno() . " : " . mysql_error());
}
?>
To enable our page to log in to the database, put this code at the top, just under "session_start();" :
include 'inc/db.inc'; include 'inc/error.inc'; // log in to the blog database if (!($db = @ mysql_connect($hostName, $userName, $passWord))) showError(); if (!( @ mysql_select_db($databaseName,$db))) showError();
And to provide the author login authorization, add this code below that:
// If logging in, set the session user and password
if (isset($_POST['submit']) and isset($_POST['login']))
{
$userArray = explode(".", trim(stripslashes($_POST['login'])));
$passwd = trim(stripslashes($_POST['passwd']));
$firstn = ucfirst($userArray[0]);
$lastn = ucfirst($userArray[1]);
$who = "";
// check for staff author login
$result = mysql_query("
SELECT * FROM author
WHERE Fname='$firstn'
AND Lname='$lastn'
AND Passwd='$passwd'
",$db);
if ($myrow = mysql_fetch_array($result))
{
$who = "staff";
}
if ($who == "")
{
session_destroy();
// Restart if unauthorized user login
header("Location: $PHP_SELF");
} else {
$_SESSION['WHO'] = $who;
$_SESSION['AUTHORNUM'] = $myrow['Author_Num'];
$_SESSION['FNAME'] = $userArray[0];
$_SESSION['LNAME'] = $userArray[1];
$_SESSION['USERPASS'] = trim(stripslashes($_POST['passwd']));
}
}
We need a means of allowing an author to log in, so let's put in a block that shows who's logged in, and if no one is logged in, show a form to log in with:
<!-- L O G I N B L O C K =================================== -->
<div class="login" style="top:1em;left: 0;width:100%;">
<?php
// L O G G E D I N A S : //////////////////////////////////
// If she's logged in, show who she is
if(isset($_SESSION['USERPASS']))
{
?>
<p style="padding:0 1em;">
<?php echo "Hi, ".ucfirst($_SESSION['FNAME']);?>
<em><a href="logout.php">Logout</a></em>
</p>
</div>
<?php
} // end show if logged in
// A U T H E N T I C A T E ///////////////////////////////////
// Has she logged in and been granted a session?
//
if (!isset($_SESSION['USERPASS']))
{
// not logged in -- put up login form
?>
<form name="auth" id="auth" method="post" action="<?php echo $PHP_SELF;?>">
User: <input name="login" type="text" value="" size="8" /><br />
Pass: <input name="passwd" type="password" value="" size="8" />
<input name="submit" class="button1" type="submit" value="Log in" />
</form>
</div>
<?php
} // end if not logged in
?>
<!-- end of login block -->
Note: As implemented in this example, the format for the user name is "first.last" with a period between them, as shown here. Install the code block above in the page just below the <body> tag, or below your "masthead" XHTML code.
To log out of a blogging session, the blog page has a link to an external PHP script called "logout.php". Save the listing below as "logout.php" in the same directory as your blog "index.php" page:
<?php
session_start();
session_destroy();
?>
<html>
<head>
<meta name="author" content="Richard H. Nilsson" />
<meta name="copyright" content="(C) Copyright 2004 all rights reserved" />
<script type="text/javascript">
// if we opened as a popup, close --
// otherwise, start up index page
if (self.opener)
{ self.close(); }
else
{ document.location = "index.php"; }
</script>
</head>
<body onload="closeMe();">
</body>
</html>
Now here's the code to pull the 5 most recent articles from the database and display them. You can adjust that number as you like, to show more or fewer articles. Put this chunk of code in the "main" div of the page template:
<?php // S H O W B L O G ////////////////////////////////// // display current hlog entries $sql = "SELECT * FROM content ORDER BY Posted DESC LIMIT 5"; show($sql); ?>
That chunk calls the function "show()" which we put at the bottom of the page, just above the "</html>" tag, to put it out of the way:
<php
//
// s h o w ( ) /////////////////////////////////////////////////
// Function to display desired content
function show($sql)
{
// get stuff from outside the function
global $db;
if (!($result = mysql_query($sql,$db)))
showError();
while ($myrow = mysql_fetch_array($result))
{
// E D I T C H E C K ///////////////////
// edit this one?
if($_GET["id"] == $myrow["Id"]
and $_GET["edit"] == "yes"
and $_POST["cancel"] != "Cancel"
)
{
echo "<a name=\"".$myrow['Id']."\"></a>";
edit($myrow);
continue;
}
/////////////////////////////////////////
// nope, just show it...
if ($myrow["Posted"] != "")
{
// formatted date
echo "<p class=\"dbar\">"
.date("F jS, Y",strtotime($myrow["Posted"]))
."</p>\n";
}
$b = "<span class=\"byline\">" .$myrow["Lname"] ." "
.$cflag['sepstr'] ." " .$myrow["Posted"]."</span>\n";
$d = "<span class=\"pdate\">" .$myrow["Posted"]. "</span>\n";
if ($myrow["Head"] != '')
{
// headline
$myrow["Head"] = preg_replace("/\[BYLINE\]/",$b,$myrow["Head"]);
$myrow["Head"] = preg_replace("/\[DATE\]/",$d,$myrow["Head"]);
echo "<h1 class=\"head\">".$myrow["Head"]."</h1>\n";
}
if ($myrow["Deck"] != '')
{
// deck
$myrow["Deck"] = preg_replace("/\[BYLINE\]/",$b,$myrow["Deck"]);
$myrow["Deck"] = preg_replace("/\[DATE\]/",$d,$myrow["Deck"]);
echo "<h2 class=\"deck\">".$myrow["Deck"]."</h2>\n";
}
echo "<p class=\"hbody\">\n";
if ($myrow["Body"] != '')
{
// body
$quote_style = ENT_QUOTES;
$myrow["Body"] = html_entity_decode($myrow["Body"], $quote_style);
$myrow["Body"] = preg_replace("/\[BYLINE\]/",$b,$myrow["Body"]);
$myrow["Body"] = preg_replace("/\[DATE\]/",$d,$myrow["Body"]);
echo "<p>\n";
echo $myrow["Body"]."\n";
}
if (isset($_SESSION['USERPASS']))
{
printf(" [<a class=\"editbut\" href=\"%s?edit=yes&id=%s%s\"> Edit </a>]"
, "index.php", $myrow["Id"], "#".$myrow["Id"]);
}
echo "</p>\n";
echo "<p> </p>\n";
} // end while
return $myrow;
} // end show
?>
This function starts off with a check to see if we can read from the database. An error is generated if we can not. We enter a "while" loop and fetch the first article. If the "Edit" link following an article was clicked, the $_GET["id"] variable will be set, and if it matches the article we just fetched, we jump out to put up the edit form (I'll show that form on Page 2). If we're not editing, we just print each of the three parts of an article, properly styled by CSS classes, to the browser. Finally, if an authorized author is logged in (signaled by having set the $_SESSION['USERPASS'] variable) we add the "Edit" link to the end of the article body.
Of course, at this point you have no articles to display, so let's add some code to provide a way to add articles to the database. We'll use a standard XHTML form. To see how to do that, see "Add Entry" Form.
Here's the form that will allow you to input new entries to the blog content table:
<?php
// A D D E N T R Y F O R M ////////////////////////////////
if(isset($_SESSION["USERPASS"]) and $_GET["edit"] != "yes" )
{
// show the "new entry" form
$today = date("Y-m-d");
?>
<hr />
<h2><a href="javascript:toggleAdd();">Add new entry</a>:</h2>
<br />
<div class="hideadd" id="add">
<formname="newlog" method="post" action="<?php echo $PHP_SELF;?>" />
<table width="100%">
<tr valign="top">
<td>Head: </td>
<td>
<textarea name="head" type="text" cols="50" rows="1"
wrap="virtual"></textarea>
</td>
</tr>
<tr valign="top">
<td>Deck: </td>
<td>
<textarea name="deck" type="text" cols="50" rows="2"
wrap="virtual"></textarea>
</td>
</tr>
<tr valign="top">
<td>Body: </td>
<td>
<textarea align="top" name="body" type="text" cols="50" rows="10"
wrap="virtual"></textarea>
</td>
</tr>
<tr valign="top">
<td colspan="2">Begins:
<input type="text" name="begin" size="12" maxlength="10"
value="<?php echo $today;?>" />
Expires:
<input type="text" name="expires" size="12" maxlength="10"
value="<?php echo $today;?>" /> (never, if equal)
</td>
</tr>
<tr>
<td colspan="2" align="right">
<input name="submit" type="submit" value="Add" />
</td>
</tr>
</table>
</form>
</div>
<hr />
<?php
} // end new entry form
?>
Install that code right above the "show blog" code previously given.
The form snippet starts off with a small block of PHP that only shows the form in the browser if two conditions are met: an authorized author is logged in and the $_GET['edit'] global variable is not invoked. The "edit" flag is something we'll set when we want to edit an existing article -- that comes later. Today's date is formatted for initializing the begin and expires dates.
Because of a stylesheet rule (bottom of the stylesheet on page 1) that hides it, the Add form is initially not visible. All we see is the line "Add new entry" between two horizontal rules. Below the PHP snippet just discussed above, there is a link that calls a JavaScript function to toggle the form's display attribute from "none" to "block". It's a neat little trick to keep the form out of our faces until we need it. Here is the JavaScript code -- put this just above the </head> tag of the page:
<script type="text/javascript">
function toggleAdd()
{
var addForm = document.getElementsByTagName("div");
if(addForm['add'].className == 'hideadd')
{ addForm['add'].className = 'showadd'; }
else
{ addForm['add'].className = 'hideadd'; }
}
</script>
The form is set up to call this page again when it is submitted, and uses the "post" method to submit data. Post allows larger data streams to be submitted and also keeps the data from appearing in the URL of the page. I used a table in the form to arrange the blanks and their descriptive labels into neat columns. There are 5 inputs: headline, deck, body text and begin and expires dates. You must enter the dates in the form "yyyy-mm-dd". The posting date and update date stamps will be added automatically by the next snippet, which is the code to capture the form data and insert it into the database content table:
<?php
// I N S E R T N E W E N T R Y /////////////////////////////////////
//
if($_POST["submit"] == "Add")
{
extract($_POST);
$quote_style = ENT_QUOTES;
$body = htmlentities($body, $quote_style);
$sql = "INSERT INTO content (Posted, Updated, Begin, Expires, Head, Deck, Body)
VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '$begin', '$expires', '$head',
'$deck', '$body')";
if (!($result = mysql_query($sql)))
{ showError(); exit; }
echo "<p>New entry inserted.</p>\n";
}
?>
Install this block just above the Add Entry form.
Here we detect that a form has been submitted and that it is the "Add" form. Then we gather the submitted data and convert the body text so we can save html and other special characters imbedded in the text. Next we build and issue an SQL query that stuffs the form data into the database table "content". If that is accomplished without an error, the author is notified that the entry was inserted.
You will recall from the discussion of the "show()" function that when an author is logged in there is an "Edit" link at the bottom of each article's body text. Clicking that link causes the page to re-load using a "get" mode of URL. The URL will include the page name, the "edit" flag, the "id" of the article to edit, and the location within the page of where the form is positioned.
<?php
//
// e d i t ( ) /////////////////////////////////////////////////
// Function to edit desired content
function edit($myrow)
{
global $db;
$quote_style = ENT_QUOTES;
$myrow["Body"] = html_entity_decode($myrow["Body"], $quote_style);
?>
<hr />
<?php echo "<p class=\"dbar\">".date("F jS, Y",strtotime($myrow["Posted"]))."</p>\n";?>
<form name="editlog" method="post" action="http://g5" />
<table width="100%">
<tr valign="top">
<td>Head: </td>
<td>
<textarea name="head" type="text" cols="50" rows="1"
wrap="virtual"><?php echo $myrow["Head"];?></textarea>
</td>
</tr>
<tr valign="top">
<td>Deck: </td>
<td>
<textarea name="deck" type="text" cols="50" rows="2"
wrap="virtual"><?php echo $myrow["Deck"];?></textarea>
</td>
</tr>
<tr valign="top">
<td>Body: </td>
<td>
<textarea align="top"
name="body" type="text" cols="50" rows="10"
wrap="virtual"><?php echo $myrow["Body"];?></textarea>
</td>
</tr>
<tr valign="top">
<td colspan="2">Begins:
<input type="text" name="begin" size="12" maxlength="10"
value="<?php echo $myrow["Begin"];?>" />
Expires:
<input type="text" name="expires" size="12" maxlength="10"
value="<?php echo $myrow["Expires"];?>" />
</td>
</tr>
<tr>
<td colspan="2" align="right">
<hr />
<input name="id" type="hidden" value="<?php echo $myrow["Id"];?>" />
<input name="submit" type="submit" value="Update" />
<input name="cancel" type="submit" value="Cancel" />
</td>
</tr>
</table>
</form>
<?php
} // end editing
?>
Install the edit function block just above the "show()" function in the bottom of the page.
To handle updating from the edit form, install this block of code right below the "Insert" block:
<?php
// U P D A T E E N T R Y ///////////////////////////////////
//
if(isset($_POST["submit"]) and $_POST["submit"] == "Update" and isset($_POST["id"]))
{
extract($_POST);
$quote_style = ENT_QUOTES;
$body = htmlentities($body, $quote_style);
$sql = "UPDATE content
SET Updated=CURRENT_TIMESTAMP, Head='$head', Deck='$deck', Body='$body',
Begin='$begin', Expires='$expires' WHERE Id='$id'";
if(!($result = mysql_query($sql)))
showError();
} // end update
?>
Here's the little JavaScript that is installed in the page by PHP if in "Edit" mode, that causes the edit block to center vertically in the browser window (install it just above the </body> tag):
<?php
if($_GET["edit"] == "yes")
{
echo "
<script type=\"text/javascript\">
function scroll()
{
var howmuch = self.innerHeight * -.15;
window.scrollBy(0,howmuch);
}
setTimeout(\"scroll()\",100);
</script>
";
}
?>
Here's the effect of clicking the "Edit" link at the end of an article body. The form replaces the article display in the page stream and the page scrolls up a little to center the form for use. Neat, huh?:
That's pretty much it, except for the author login/logout form and routines. To see those, refer to the full page listing. NOTE!: If you're operating under PHP4 or earlier, be sure to download the "html_entity_decode.php" file from http://pear.php.net/package/PHP_Compat/ and install either in your host's /php/includes directory, or in the blog/ directory. In the latter case add this line to your index.php list of includes at the top:
include 'html_entity_decode.php';
Enjoy.
To try out my online demo of the basic version, log in as "guest.guest" password "guest". Or visit the real thing a full weblog app with major features (member-signup for posting comments, "recent posts", "browse all", image handling for posts, a calendar and archive for selecting specific dates). Donate $25 via PayPal, and get a link to download the SuperBlog script.