If you have been participating up until now we have performed a lot of housekeeping duties in setting up our custom Content Management System. In this installment we will start getting to the meat of a CMS, designing for the workflow process. Covered will be article submission, allowing an author to preview and edit his or her own articles, and more workflow theory.

Workflow Theory Examined

In this model of a content management system (CMS) we are installing a very basic workflow;

As you can see, the article goes through many levels of approval prior to publication. This offers assurance that the article represents the subject and the presenter in the best possible light. Depending on the flow for a particular document set, errors have the opportunity to be caught and corrected at multiple levels.

For workflow to operate properly it must be well defined (as in our example above) from the task level to the responsibility level. Assigning proper personnel resources to workflow is critical, perhaps the most critical aspect of content management. If the personnel resources fail to understand and perform their duties as defined by the workflow specifications then having a CMS is like having a nifty toy on Christmas morning... without the batteries. This becomes especially true when the CMS is expanded to handle other content.

An example of this is a product tear sheet. When a product tear sheet is initially generated there are many hands at work. The copywriter, the photographer, the graphic artist, and others agonize over the most minute of details bringing the tear sheet to life. Rather than fax things, email things and have endless conference calls, it is decided the product tear sheet can reside in the CMS. This does not sound bad until you realize that many people can work on the tear sheet all at the same time. This could create a traffic jam that would ulimately leave the users frustrated and the tear sheet incomplete, at best. This is where a Content Versioning System (CVS) comes in.

A CVS allows people who are working on a particular document to 'check-out' the document, just as you would check-out a book at your local library. During the check-out period the document is unavailable to others. When the document is 'checked-in' it becomes available to others who are working on the same document, and information about what has been done to that document is available (usually in a log associated with the document) to the person(s) working on it. Everybody participating in the document process knows then what has been done to the document, when it was done, and any other information that might be relevant to the document. A full explanation of CVS is beyond the scope of this series but there are many good resources available online and in bookstores.

Specifying Workflow

Each element in a CMS, such as the article being worked with in this model, may have a different workflow scheme. This could involve different levels of approval or different personnel resources.

For instance, say there is a daily announcements section on the front page of your Intranet site. The workflow could be;

As the CMS developer it your responsibility to know the definition of each CMS element and the workflow associated with it. You may have to interact with various departments and personnel for each element. However, by knowing the workflow process for each element within the CMS your job as a developer is much easier. What do you need to know for each element?

  1. Who are the potential authors?
  2. Once an element is submitted who should it go to for editing and/or approval?
  3. Once approved, who has the responsibility to schedule the release of this element to the appropriate forum?

Consider the following graphic and determine the number of elements and workflows.

You can create a workflow design/management tool within the CMS that allows an end-user to specify workflows for various elements. This is a complex development chore, beyond the scope of this series, that can pay off in the most satisfying way when it is correctly designed. Usually this kind of functionality is found only in the most expensive of the retail systems, but should be considered in any content management system.

Article Submission

Here is the form for submitting an article, subart.php;

<? session_start(); ?>

<?

//connect to and select database

if(!($dbconnect = mysql_pconnect("127.0.0.1", "cms_user", "cms_password"))){

print("Failed to connect to database!

");

exit();

}

if(!mysql_select_db("cms", $dbconnect)){

print("Failed to select database!

");

exit();

}

//query to get navigation from database

$qnav = "SELECT URL ";

$qnav .= "FROM tblNavigation ";

$qnav .= "WHERE accesslevel = '$level' ";

if(!($dbnav = mysql_query($qnav, $dbconnect))){

print("MySQL reports: " . mysql_error() . "

");

exit();

}

//query to get available editors

$qedit = "SELECT name ";

$qedit .= "FROM tblUser ";

$qedit .= "WHERE accesslevel = 'editor' ";

if(!($dbedit = mysql_query($qedit, $dbconnect))){

print("MySQL reports: " . mysql_error() . "

");

exit();

}

?>

<html>

<head>

<title>CMS</title>

<LINK REL="StyleSheet" HREF="cms.css" type="text/css">

</head>

<body>

<div class="left">

<?

// list the nav links for this type of user

while($dbrow = mysql_fetch_object($dbnav)){

print($dbrow->URL . "<br>

");

}

?>

</div>

<div class="right">

<h1>Submit An Article</h1>

<form action="cmssubmit.php" method="POST">

<table cellpadding="3" cellspacing="0" border="1">

<tr>

<td>Article Title</td>

<td><input type="text" name="title" size="64" maxlength="64"></td>

</tr>

<tr>

<td>Author email</td>

<td><? print($emailid) ?></td>

</tr>

<tr>

<td>Article Teaser</td>

<td><textarea name="teaser" cols="50" rows="3" wrap="virtual" maxlength="255"></textarea></td>

</tr>

<tr>

<td>Article Body</td>

<td><textarea name="body" cols="50" rows="25" wrap="virtual"></textarea></td>

</tr>

<tr>

<td>Editor</td>

<td>

<select name="editor">

<option>---Select Editor---</option>

<?

//list the editors available

while($dbrow = mysql_fetch_object($dbedit)){

print("<option>" . $dbrow->name . "</option>

");

}

?>

</select>

</td>

</tr>

<tr>

<td>Date Submitted<br>Format - YYYY-MM-DD</td>

<td><input type="text" name="submitted" size="10" maxlength="10"></td>

</tr>

<tr>

<td></td>

<td><INPUT type="submit" name="action" value="Save"> <INPUT type="submit" name="action" value="Submit Article"> <INPUT type="reset"></td>

</tr>

</table>

</form>

</div>

</body>

</html>

As in the page listing this author's articles (myart.php) the code in the <div class="left"> prints out the navigation links for available for authors. We will be adding to this as the CMS is developed and other users, such as editors, approvers, and schedulers become users of the system.

Just after the comment "//list the editors available", the PHP code will create a drop down option in the form, allowing the author to choose his editor. While we have taken a rather loose approach in this series, you may want the editor to be chosen automatically for each author in other situations. If that is the case you will have to modify your users table, adding a column to contain editor information for each author. In the case of the announcement example above you could just hard code the information in the page where announcements are submitted or have a specially designated access level for certain types of editing/approving chores (i.e. EditorA, EditorB, etc. ).

You'll also note that there are three buttons for form actions, 'Save', 'Submit Article', and 'Reset'. This is different from most form handling via a web-interface, but the result allows an efficient way to use the workflow process. If the author needs to perform editing on his or her article, or maybe has not quite put the finishing touches on it, the 'Save' button allows the article to be sent to the database with no notification (as you will see in the script that processes the submission) to an editor. The author will then be able to return to the article at a later time to perform whatever changes that they need to perform. The 'Submit Article' button send the article to the database and notifies and editor. Of course, the 'Reset' button clears the form.

Here is the script that processes article submission, cmssubart.php;

<? session_start(); ?>

<?

//connect to and select database

if(!($dbconnect = mysql_pconnect("127.0.0.1", "cms_user", "cms_password"))){

print("Failed to connect to database!

");

exit();

}

if(!mysql_select_db("cms", $dbconnect)){

print("Failed to select database!

");

exit();

}

// perform action based on which button was pushed on submit form

switch($action)

{

case "Save":

// query to insert saved elements only

$qsave = "INSERT INTO tblArticle(title, teaser, body, author) ";

$qsave .= "VALUES('$title', '$teaser', '$body', '$emailid') ";

if(!($dbsave = mysql_query($qsave, $dbconnect))){

print("MySQL reports: " . mysql_error() . "<br>

");

exit();

}

break;

case "Submit Article":

//query to submit article

$qsubmit = "INSERT INTO tblArticle(title, teaser, body, author, editor, submitted) ";

$qsubmit .= "VALUES('$title', '$teaser', '$body', '$emailid', '$editor', '$submitted') ";

if(!($dbsubmit = mysql_query($qsubmit, $dbconnect))){

print("MySQL reports: " . mysql_error() . "<br>

");

exit();

}

//select editor information and send an email

$qeditor = "SELECT email ";

$qeditor .= "FROM tblUser ";

$qeditor .= "WHERE name = '$editor' ";

if(!($dbeditor = mysql_query($qeditor, $dbconnect))){

print("MySQL reports: " . mysql_error() . "<br>

");

exit();

}

//message to editor

$dbeditor = mysql_fetch_object($dbeditor);

$messeditor = "$author has submitted an article, $title, for editing.

";

$messeditor .= "Please review this article at your earliest convenience.

";

mail("$dbeditor->email", "CMS Article Submission", $messeditor, "From: CMS System");

break;

}

header("Location: myart.php"); exit;

?>

The path for the content can be selected by using multiple buttons in the form; (leaving out the 'Reset' button)

<INPUT type="submit" name="action" value="Save"> <INPUT type="submit" name="action" value="Submit Article"> 

Each button has the name 'action', and the value of the action is the 'value' of each button. Within the PHP script that processes the article submission the value of the variable $action is the value of the button pushed. By using;

switch($action){}

(which is similar to ASP's SELECT CASE) we can select what should be done with the article. This gives you the opportunity to specify multiple actions for any given form, you are only limited by the amount of action buttons you can create. A word of caution is in order here, you must provide adequate training, along with a help system or other clues so that users of the CMS understand what each action perfroms.

If the author chooses an editor, includes a submission date, and clicks the 'Submit Article' button he or she will no longer have the opportunity to edit the article (unless the article is 'sent back' to them from someone higher up the workflow ladder). The article will show up on their 'My Articles' page, from where they will be able to preview / review the article.

An Author Edits

Doing Reviews

The author can submit articles, now he or she needs to be able to edit articles which have not been submitted to an editor. To enable this action we are going to modify our original myart.php, placing a link next to the articles where editing is allowed. Articles needing editing will be the first to be listed, while articles already forwarded to editing will only be available for review. (You can just replace the old myart.php with the following code.)

<? session_start(); ?>

<?

//connect to and select database

if(!($dbconnect = mysql_pconnect("127.0.0.1", "cms_user", "cms_password"))){

print("Failed to connect to database!

");

exit();

}

if(!mysql_select_db("cms", $dbconnect)){

print("Failed to select database!

");

exit();

}

//query to get navigation from database

$qnav = "SELECT URL ";

$qnav .= "FROM tblNavigation ";

$qnav .= "WHERE accesslevel = '$level' ";

if(!($dbnav = mysql_query($qnav, $dbconnect))){

print("MySQL reports: " . mysql_error() . "

");

exit();

}

//query to get article information

$qart = "SELECT a.ID, a.title, a.publish, a.editor, u.name ";

$qart .= "FROM tblArticle a, tblUser u ";

$qart .= "WHERE u.email = '$emailid' ";

$qart .= "AND a.author = u.email ";

$qart .= "ORDER BY a.editor ";

if(!($dbart = mysql_query($qart, $dbconnect))){

print("MySQL reports: " . mysql_error() . "

");

exit();

}

?>

<html>

<head>

<title>CMS</title>

<LINK REL="StyleSheet" HREF="cms.css" type="text/css">

</head>

<body>

<div class="left">

<?

while($dbrow = mysql_fetch_object($dbnav)){

print($dbrow->URL . "<br>

");

}

?>

</div>

<div class="right">

<?

$dbartrow = mysql_fetch_object($dbart);

if($dbartrow->ID == ""){

//if there are no articles for this author

print("You have not placed any articles in the Content Management System");

}

else

{

//print article links for this author

mysql_data_seek($dbart, 0);

print("<h3>Welcome " . $dbartrow->name . "</h3>

");

while($dbartrow = mysql_fetch_object($dbart)){

//if there is an editor print only a link to the article preview

if($dbartrow->editor != ""){

print("<a href=\"artprvw.php?aid=" . $dbartrow->ID . "\">" . $dbartrow->title . "</a><br>

");

}

else

{

print("<a href=\"artprvw.php?aid=" . $dbartrow->ID . "\">" . $dbartrow->title . "</a> ( <a href=\"authed.php?aid=" . $dbartrow->ID . "\">Edit</a> )<br>

");

}

}

}

?>

</div>

</body>

</html>

This line of code, if($dbartrow->editor != ""), tests for the existence of an editor in the record and prints out the links accordingly. If an editor exists then a link to the article preview is printed. If no editor exists for this article then the article preview link is printed, along with a link to a page where the author can edit their own article.

The following code block is for an author to preview an article (artprvw.php);

<? session_start(); ?>

<?

//connect to and select database

if(!($dbconnect = mysql_pconnect("127.0.0.1", "cms_user", "cms_password"))){

print("Failed to connect to database!

");

exit();

}

if(!mysql_select_db("cms", $dbconnect)){

print("Failed to select database!

");

exit();

}

//query to get navigation from database

$qnav = "SELECT URL ";

$qnav .= "FROM tblNavigation ";

$qnav .= "WHERE accesslevel = '$level' ";

if(!($dbnav = mysql_query($qnav, $dbconnect))){

print("MySQL reports: " . mysql_error() . "

");

exit();

}

//get article information from database

$qart = "SELECT a.ID, a.title, a.teaser, a.body ";

$qart .= "FROM tblArticle a ";

$qart .= "WHERE a.ID = $aid ";

if(!($dbart = mysql_query($qart, $dbconnect))){

print("MySQL reports: " . mysql_error() . "

");

exit();

}

//place database items in variables for easier manipulation

$dbartrow = mysql_fetch_object($dbart);

$atitle = $dbartrow->title;

$ateaser = $dbartrow->teaser;

$abody = $dbartrow->body;

?>

<html>

<head>

<title>CMS Article Preview</title>

<LINK REL="StyleSheet" HREF="cms.css" type="text/css">

</head>

<body>

<div class="left">

<?

while($dbrow = mysql_fetch_object($dbnav)){

print($dbrow->URL . "<br>

");

}

?>

</div>

<div class="right">

<?

print("<h2>" . $atitle . "</h2>

");

print("<p>" . $ateaser . "</p>

");

//place proper HTML paragraph mark up in body

print("<p>

" . ereg_replace(chr(10).chr(13), "</p>

<p>", $abody) . "

</p>

");

?>

</div>

</body>

</html>

This will display the article as it is selected from 'My Articles' so that the author may preview the work as it might appear on a web site. You can use the same techniques to display the article on a web site or Intranet site, just build a template that extracts the relevant information from the database and displays it in a readable form. You may also want to include things like the author's name and email address, and date published.

One of the more interesting aspects of the code for previewing the article is;

print("<p>

" . ereg_replace(chr(10).chr(13), "</p>

<p>", $abody) . "

</p>

");

This line of code prints out the body of the article with the proper HTML paragraph mark-up. It prints an opening paragraph tag, followed by a new line. Then the PHP function ereg_replace() replaces each linefeed (char(10)) and carriage return (char(13)) combination with a closing, and then opening paragraph tag. When the article is entered into the CMS an author will generally hit enter twice at the end of the paragraph, which gives the linefeed carriage return combination. We close the line of code by printing out a closing paragraph tag. Why do we do this? Two reasons:

  1. Since the data in the database contains no mark-up of any kind it makes it easier to output the data to multiple formats.
  2. Users do not have to be taught mark-up, saving time and easing their ability to use the system

Note: In the model being presented here we are not doing anything to prevent mark-up from being entered with the data in the database. If you desire to block mark-up you will have to catch and remove it during the article submission process.

Our output will look like this;

<p>

This is a paragraph.

</p>

<p>

This is another paragraph.

</p>

Changing Things

Giving authors the opportunity to save articles in the database without sending them through the workflow process until they are ready offers a big advantage for many situations. As you might imagine, working on an article can be a lengthy process, full of edits, and ravaged with changes. While many would fire up their favorite word processor or text editor, with a plan to cut-n-paste the article into the CMS later, there are many who would prefer to work with the article directly in the environment that it is destined for. Given that, here is the code for authed.php;

<? session_start(); ?>

<?

//connect to and select database

if(!($dbconnect = mysql_pconnect("127.0.0.1", "cms_user", "cms_password"))){

print("Failed to connect to database!

");

exit();

}

if(!mysql_select_db("cms", $dbconnect)){

print("Failed to select database!

");

exit();

}

//query to get navigation from database

$qnav = "SELECT URL ";

$qnav .= "FROM tblNavigation ";

$qnav .= "WHERE accesslevel = '$level' ";

if(!($dbnav = mysql_query($qnav, $dbconnect))){

print("MySQL reports: " . mysql_error() . "

");

exit();

}

//query to get available editors

$qedit = "SELECT name ";

$qedit .= "FROM tblUser ";

$qedit .= "WHERE accesslevel = 'editor' ";

if(!($dbedit = mysql_query($qedit, $dbconnect))){

print("MySQL reports: " . mysql_error() . "

");

exit();

}

//query to get article information

$qart = "SELECT a.ID, a.title, a.teaser, a.body ";

$qart .= "FROM tblArticle a ";

$qart .= "WHERE a.ID = $aid ";

if(!($dbart = mysql_query($qart, $dbconnect))){

print("MySQL reports: " . mysql_error() . "

");

exit();

}

//place database items in variables for easier manipulation

$dbartrow = mysql_fetch_object($dbart);

$atitle = $dbartrow->title;

$ateaser = $dbartrow->teaser;

$abody = $dbartrow->body;

?>

<html>

<head>

<title>CMS</title>

<LINK REL="StyleSheet" HREF="cms.css" type="text/css">

</head>

<body>

<div class="left">

<?

// list the nav links for this type of user

while($dbrow = mysql_fetch_object($dbnav)){

print($dbrow->URL . "<br>

");

}

?>

</div>

<div class="right">

<h1>Edit An Article</h1>

<form action="cmsauthed.php" method="POST">

<table cellpadding="3" cellspacing="0" border="1">

<tr>

<td>Article Title</td>

<td>

<input type="hidden" name="aid" value="<? print($aid) ; ?>">

<input type="text" name="title" size="64" maxlength="64" value="<? print($atitle); ?>"></td>

</tr>

<tr>

<td>Author email</td>

<td><? print($emailid) ?></td>

</tr>

<tr>

<td>Article Teaser</td>

<td><textarea name="teaser" cols="50" rows="3" wrap="virtual" maxlength="255"><? print($ateaser); ?></textarea></td>

</tr>

<tr>

<td>Article Body</td>

<td><textarea name="body" cols="50" rows="25" wrap="virtual"><? print($abody); ?></textarea></td>

</tr>

<tr>

<td>Editor</td>

<td>

<select name="editor">

<option>---Select Editor---</option>

<?

//list the editors available

while($dbrow = mysql_fetch_object($dbedit)){

print("<option>" . $dbrow->name . "</option>

");

}

?>

</select>

</td>

</tr>

<tr>

<td>Date Submitted<br>Format - YYYY-MM-DD</td>

<td><input type="text" name="submitted" size="10" maxlength="10"></td>

</tr>

<tr>

<td></td>

<td><INPUT type="submit" name="action" value="Save"> <INPUT type="submit" name="action" value="Submit Article"> <INPUT type="reset"></td>

</tr>

</table>

</form>

</div>

</body>

</html>

Note that we have given the author the options (again) to save or submit the article. If the author chooses to save the article it will still be available in the list of articles that he or she can edit themselves. Submitting the article starts it forward through the workflow process.

To process this code we are going to borrow the code we used originally for submitting articles with small modifications to the SQL queries. It will be called cmsauthed.php;

<? session_start(); ?>

<?

//connect to and select database

if(!($dbconnect = mysql_pconnect("127.0.0.1", "cms_user", "cms_password"))){

print("Failed to connect to database!

");

exit();

}

if(!mysql_select_db("cms", $dbconnect)){

print("Failed to select database!

");

exit();

}

// perform action based on which button was pushed on submit form

switch($action)

{

case "Save":

// query to insert saved elements only

$qsave = "UPDATE tblArticle ";

$qsave .= "SET title = '$title', ";

$qsave .= "teaser = '$teaser', ";

$qsave .= "body = '$body', ";

$qsave .= "author = '$emailid' ";

$qsave .= "WHERE ID = '$aid' ";

if(!($dbsave = mysql_query($qsave, $dbconnect))){

print("MySQL reports: " . mysql_error() . "<br>

");

exit();

}

break;

case "Submit Article":

//query to submit article

$qsubmit = "UPDATE tblArticle ";

$qsubmit .= "SET title = '$title', ";

$qsubmit .= "teaser = '$teaser', ";

$qsubmit .= "body = '$body', ";

$qsubmit .= "author = '$emailid', ";

$qsubmit .= "editor = '$editor', ";

$qsubmit .= "submitted = '$submitted' ";

$qsubmit .= "WHERE ID = '$aid' ";

if(!($dbsubmit = mysql_query($qsubmit, $dbconnect))){

print("MySQL reports: " . mysql_error() . "<br>

");

exit();

}

//select editor information and send an email

$qeditor = "SELECT email ";

$qeditor .= "FROM tblUser ";

$qeditor .= "WHERE name = '$editor' ";

$qeditor .= "AND accesslevel = 'editor' ";

if(!($dbeditor = mysql_query($qeditor, $dbconnect))){

print("MySQL reports this: " . mysql_error() . "<br>

");

exit();

}

//message to editor

$dbeditor = mysql_fetch_object($dbeditor);

$messeditor = "$author has submitted an article, $title, for editing.

";

$messeditor .= "Please review this article at your earliest convenience.

";

//mail("$dbeditor->email", "CMS Article Submission", $messeditor, "From: CMS System");

break;

}

header("Location: myart.php"); exit;

?>

Instead of performing INSERT's during this process we perform UPDATE's of the information already contained in the database. The path that the article takes is the same as before, depending on the button clicked during the submission process. This multi-button functionality gives the CMS a lot of flexibility.

Next On The Menu

This portion of the series should have provided you with a lot of information if you're going to be building a custom CMS in the near future.

Now that the author can submit an article, preview it, and edit it, it is time to take the next step in the path along our workflow model, having an editor review the article. We will give the editor the ability to preview and edit the article, as well as giving him or her the ability to return the article to the author should that be necessary.