<?php
/*
 * Modification History
 *
 * 2005-April-18  Jason Rohrer
 * Created.
 *
 * 2005-July-26  Jason Rohrer
 * Moved settings into a separate file.
 *
 * 2005-September-1  Jason Rohrer
 * Added support for toggling permalinks.
 *
 * 2005-September-8  Jason Rohrer
 * Improved some parameter names.
 * Added fix for IE cookie clearing bug.
 *
 * 2005-September-18  Jason Rohrer
 * Fixed several undefined variable notices.
 *
 * 2005-September-20  Jason Rohrer
 * Added text formatting and pubDate fields to RSS feeds.
 *
 * 2005-November-14  Jason Rohrer
 * Added version action.
 * Changed behavior of story block formatting for headline-only lists.
 * Fixed redirect behavior after login and register.
 *
 * 2005-November-21  Jason Rohrer
 * Added [centerblock] tags for centering a left-aligned block of text.
 *
 * 2006-March-16  Jason Rohrer
 * Removed check for existing session ID when resetting password.
 * Added optional email notices to admins about pending items.
 *
 * 2006-August-10  Jason Rohrer
 * Added sb_ prefix to all internal functions to prevent conflicts with other
 * packages.
 *
 * 2006-September-21  Jason Rohrer
 * Changed image alignment HTML.
 *
 * 2006-October-9  Jason Rohrer
 * Fixed typo in admin letter.  Added list of all pending posts.
 * Added an Approve link when admin views a pending post.
 * Fixed a bug in URL detection for link-only posts.
 * Changed to support ordering by oldest-first, and switched comment ordering.
 *
 * 2007-May-29  Jason Rohrer
 * Fixed warning generated by post queue.
 */



global $seedBlogs_version;
$seedBlogs_version = "0.2_in_progress";



// edit settings.php to change seedBlogs' settings
include( "sbSettings.php" );




// no end-user settings below this point




// enable verbose error reporting to detect uninitialized variables
error_reporting( E_ALL );


// page layout for web-based setup
$setup_header = "
<HTML>
<HEAD><TITLE>seedBlogs Web-based setup</TITLE></HEAD>
<BODY BGCOLOR=#FFFFFF TEXT=#000000 LINK=#0000FF VLINK=#FF0000>

<CENTER>
<TABLE WIDTH=75% BORDER=0 CELLSPACING=0 CELLPADDING=1>
<TR><TD BGCOLOR=#000000>
<TABLE WIDTH=100% BORDER=0 CELLSPACING=0 CELLPADDING=10>
<TR><TD BGCOLOR=#EEEEEE>";

$setup_footer = "
</TD></TR></TABLE>
</TD></TR></TABLE>
</CENTER>
</BODY></HTML>";




// set to 1 to force magic_quote behavior on all user-submitted data
// set to 0 to disable magic_quote behavior

// WARNING:  setting $use_magic_quotes to 0 will make user-submitted
//           data (for example, web form data) unsafe to pass directly
//           into a MySQL database query.
$use_magic_quotes = 1;

if( get_magic_quotes_gpc() && !$use_magic_quotes ) {
    // force magic quotes to be removed
    $_GET     = array_map( 'sb_stripslashes_deep', $_GET );
    $_POST    = array_map( 'sb_stripslashes_deep', $_POST );
    $_REQUEST = array_map( 'sb_stripslashes_deep', $_REQUEST );
    $_COOKIE  = array_map( 'sb_stripslashes_deep', $_COOKIE );
    }
else if( !get_magic_quotes_gpc() && $use_magic_quotes ) {
    // force magic quotes to be added
    $_GET     = array_map( 'sb_addslashes_deep', $_GET );
    $_POST    = array_map( 'sb_addslashes_deep', $_POST );
    $_REQUEST = array_map( 'sb_addslashes_deep', $_REQUEST );
    $_COOKIE  = array_map( 'sb_addslashes_deep', $_COOKIE );
    }



// set to NULL so we can detect when we have set it on purpose
global $return_url;
$return_url = NULL;


// deal with cookies for logins

// ignore cookies if $loggedInID already set by another part of the script
global $loggedInID;


// set by the logout script to tell us to ignore cookies
global $justLoggedOut;


$cookieName = $tableNamePrefix . "cookie";

$cookie_user_id = "";
if( isset( $_COOKIE[ $cookieName ."_user_id" ] ) ) {
    $cookie_user_id = $_COOKIE[ $cookieName ."_user_id" ];
    }
$cookie_session_id = "";
if( isset( $_COOKIE[ $cookieName ."_session_id" ] ) ) {
    $cookie_session_id = $_COOKIE[ $cookieName ."_session_id" ];
    }

if( ! $justLoggedOut &&
    strcmp( $loggedInID, "" ) == 0 ) {  // $loggedInID not already set

    $loggedInID = sb_getLoggedInUser();
        
    if( strcmp( $loggedInID, "" ) != 0 ) { 
        // push the cookie expiration forward
        sb_refreshCookie( $cookie_user_id, $cookie_session_id );
        }
    }



/**
 * Displays either a login form or information about the currently logged-in
 * user (along with a logout link).
 */
function seedBlogs_showLoginBox() {
    global $loggedInID, $justLoggedOut, $tableNamePrefix;

    // don't use global $return_url here
    $return_url = sb_getReturnURL();
        
    if( sb_getUserCount() == 0 ) {
        // no registered users
        // show link to register form

        // use main site URL as return URL here
        // This avoid redirecting the user back to sb_setup
        global $mainSiteURL;
        $encoded_return_url= urlencode( $mainSiteURL );
        
        echo "[<A HREF=\"seedBlogs.php?action=show_register_form&".
            "return_url=$encoded_return_url\">".
            "Create Admin Account</A>]";
        }
    else if( $justLoggedOut || strcmp( $loggedInID, "" ) == 0 ) {
        $encoded_return_url= urlencode( $return_url );
        
        // show the login form
?>

    <FORM ACTION="seedBlogs.php" METHOD="post">
    <INPUT TYPE="hidden" NAME="action" VALUE="login">
    <INPUT TYPE="hidden" NAME="return_url" VALUE="<?php echo $return_url;?>">
    <TABLE BORDER=0>
    <TR><TD>User ID:</TD>
    <TD><INPUT TYPE="text" MAXLENGTH=20 SIZE=10 NAME="user_id"></TD></TR>
    <TR><TD>Password:</TD>
    <TD><INPUT TYPE="password" MAXLENGTH=20 SIZE=10 NAME="password"></TD></TR>
    <TR><TD VALIGN=TOP>
       [<A HREF="seedBlogs.php?action=show_register_form&return_url=<?php
           echo $encoded_return_url; ?>">New Account</A>]</TD>
       <TD ALIGN=RIGHT VALIGN=TOP><INPUT TYPE="Submit" VALUE="Log In"></TD><TR>
     <TR><TD COLSPAN=2 VALIGN=TOP>
        [<A HREF="seedBlogs.php?action=show_password_help_form">Forgot Password?</A>]</TD></TR>
    </TABLE>
    </FORM>

<?php
         }
    else {
        $return_url = urlencode( $return_url );
        
        // indicate which user is logged in
        echo "Logged in as <B>" . sb_stripMagicQuotes( $loggedInID ) .
            "</B><BR>\n";
        echo "[<A HREF=\"seedBlogs.php?action=logout\">Log Out</A>] ";
        echo "[<A HREF=\"seedBlogs.php?action=show_register_form&".
            "return_url=$return_url\">".
             "Edit Account</A>]\n";

        if( sb_isAdministrator() ) {
            // show a link to pending account queue, if any are pending
            sb_connectToDatabase();
            $query =
                "SELECT COUNT(*) FROM $tableNamePrefix"."users ".
                "WHERE approved = '0';";
            $result = sb_queryDatabase( $query );
            $pendingCount = mysql_result( $result, 0, 0 );
        
            sb_closeDatabase();

            if( $pendingCount > 0 ) {
                $countString = "<B>$pendingCount</B> account requests";
                if( $pendingCount == 1 ) {
                    $countString = "<B>$pendingCount</B> account request";
                    }
                echo "<BR>[<A HREF=\"seedBlogs.php?action=show_account_queue".
                    "&return_url=$return_url\">" .
                    "$countString waiting</A>]";
                }

            // show a link to pending post queue, if any are waiting
            sb_connectToDatabase();
            $query =
                "SELECT COUNT(*) FROM $tableNamePrefix"."posts ".
                "WHERE approved = '0' AND removed = '0';";
            $result = sb_queryDatabase( $query );
            $pendingCount = mysql_result( $result, 0, 0 );
        
            sb_closeDatabase();

            if( $pendingCount > 0 ) {
                $countString = "<B>$pendingCount</B> posts";
                if( $pendingCount == 1 ) {
                    $countString = "<B>$pendingCount</B> post";
                    }
                echo "<BR>[<A HREF=\"seedBlogs.php?action=show_post_queue".
                    "&blog_name=*&return_url=$return_url\">" .
                    "$countString pending approval</A>]";
                }
            
            echo "<BR>[<A HREF=\"seedBlogs.php?action=show_account_list".
                "&return_url=$return_url\">" .
                "Manage Accounts</A>]<BR>";
            }
        }
    }



/**
 * Displays the search box (used to search all seedBlogs).
 *
 * @param $inFieldWidth the width of the field, in characters.
 *   Defaults to 15.
 * @param $inShowButton true to show the "Search" button, or false
 *   to hide it.
 *   Defaults to true.
 */
function seedBlogs_showSearchBox( $inFieldWidth = 15, $inShowButton = true ) {
    // redisplay key words if they are present as POSTed variables
    $key_words = "";
    if( isset( $_REQUEST[ "key_words" ] ) ) {
        $key_words =
            sb_stripMagicQuotes(
                sb_getRequestVariableRaw( "key_words" ) );
        }
?>
    <FORM ACTION="seedBlogs.php" METHOD="post">
    <INPUT TYPE="hidden" NAME="action" VALUE="search">
    <INPUT TYPE="text" MAXLENGTH=20 SIZE=<?php echo $inFieldWidth;?>
           NAME="key_words"
           VALUE="<?php echo htmlspecialchars( $key_words ); ?>">
<?php
    if( $inShowButton ) {
        echo "<INPUT TYPE=\"Submit\" VALUE=\"Search\">";
        }
    echo "</FORM>";
    }



/**
 * Displays a seed blog with default formatting options
 *
 * @param $inBlogName the name of the blog in the database.  Should not
 *   contain spaces or special characters.
 * @param $inShowIntroText 1 to show intro text under headlines, or 0 to
 *   show only headlines.
 * @param $inShowAuthors (only applies if $inShowIntroText is 1) 1 to show
 *   the author of each post, or 0 to hide the authors.
 *   Defaults to 1.
 * @param $inShowDates (only applies if $inShowIntroText is 1) 1 to show
 *   the creation date for each post, or 0 to hide the dates.
 *   Defaults to 1.
 * @param $inOrder 1 to order by creation date with newest posts first,
 *   -1 to order by creation date with oldest posts first,
 *   0 to order by expiration date with oldest posts first, or
 *   2 to allow the administrators to tweak the ordering (up/down widgets
 *   will be displayed to allow admins to move posts up and down in the list).
 *   Defaults to 1.
 * @param $inMaxNumber the maximum number of entries to show.  -1 specifies
 *   no limit.
 *   Defaults to 10.
 * @param $inNumToSkip the number of posts to skip, starting at the top
 *   of the list.  Specifying 0 shows $inMaxNumber posts starting with the
 *   top post.  Defaults to 0.
 * @param $inShowArchive 1 to show the archive link, or 0 to hide it.
 *   Defaults to 1.
 * @param $inShowSubmitLinkToPublic 1 to show a link for the public to submit
 *   posts, or 0 to hide the link.
 *   Defaults to 1.
 */
function seedBlog( $inBlogName,
                   $inShowIntroText,
                   $inShowAuthors = 1,
                   $inShowDates = 1,
                   $inOrder = 1,
                   $inMaxNumber = 10,
                   $inNumToSkip = 0,
                   $inShowArchive = 1,
                   $inShowSubmitLinkToPublic = 1 ) {

    global $storyBlockFormatOpen, $storyBlockFormatClose,
        $headlineFormatOpen, $headlineFormatClose, $textBlockFormatOpen,
        $textBlockFormatClose, $storySeparator,
        $linkStoryBlockFormatOpen, $linkStoryBlockFormatClose,
        $linkHeadlineFormatOpen, $linkHeadlineFormatClose, $linkStorySeparator;

    // pick from defaults depending on whether intro text is shown or not
    $local_storyBlockFormatOpen = $linkStoryBlockFormatOpen;
    $local_storyBlockFormatClose = $linkStoryBlockFormatClose;
    $local_headlineFormatOpen = $linkHeadlineFormatOpen;
    $local_headlineFormatClose = $linkHeadlineFormatClose;
    $local_storySeparator = $linkStorySeparator;
    
    
    if( $inShowIntroText ) {
        $local_storyBlockFormatOpen = $storyBlockFormatOpen;
        $local_storyBlockFormatClose = $storyBlockFormatClose;
        $local_headlineFormatOpen = $headlineFormatOpen;
        $local_headlineFormatClose = $headlineFormatClose;
        $local_storySeparator = $storySeparator;
        }
    
    
    seedBlogFormatted( $inBlogName,
                       $inShowIntroText,
                       $inShowAuthors,
                       $inShowDates,
                       $inOrder,
                       $inMaxNumber,
                       $inNumToSkip,
                       $inShowArchive,
                       $inShowSubmitLinkToPublic,
                       $local_storyBlockFormatOpen,
                       $local_storyBlockFormatClose,
                       $local_headlineFormatOpen,
                       $local_headlineFormatClose,
                       $textBlockFormatOpen,
                       $textBlockFormatClose,
                       $local_storySeparator );
    }




/**
 * Displays a seed blog with customized formatting options.
 *
 * Parameters are the same as for the simpler call above, except:
 * @param $inStoryBlockFormatOpen opening HTML used to format each story block.
 * @param $inStoryBlockFormatClose closing HTML used to format each story
 *   block.
 * @param $inHeadlineFormatOpen opening HTML used to format headlines.
 * @param $inHeadlineFormatClose closing HTML used to format headlines.
 * @param $inTextBlockFormatOpen opening HTML used to format the text of a
 *   post under the headline.  Ignored if $inShowIntroText = 0.
 * @param $inTextBlockFormatClose closing HTML used to format the text of a
 *   post under the headline.  Ignored if $inShowIntroText = 0.
 * @param $inStorySeparator HTML to insert between each story block in a story
 *   list.
 */
function seedBlogFormatted( $inBlogName,
                            $inShowIntroText,
                            $inShowAuthors,
                            $inShowDates,
                            $inOrder,
                            $inMaxNumber,
                            $inNumToSkip,
                            $inShowArchive,
                            $inShowSubmitLinkToPublic,
                            // formatting options:
                            $inStoryBlockFormatOpen,
                            $inStoryBlockFormatClose,
                            $inHeadlineFormatOpen,
                            $inHeadlineFormatClose,
                            $inTextBlockFormatOpen,
                            $inTextBlockFormatClose,
                            $inStorySeparator ) {

    global $return_url;

    if( $return_url == NULL ) {
        $return_url = sb_getReturnURL();
        $return_url = urlencode( $return_url );
        }
    
    // display link for posting new item
    $postLinkName = "Submit";
    $postLinkHint = "Submit a post into the approval queue";
    
    $isCommentBlog = false;
    if( preg_match( "/_comments/", $inBlogName ) ) {
        $isCommentBlog = true;
        $postLinkName = "Submit Comment";
        $postLinkHint = "Submit a comment into the approval queue";
        }
    $allowPost = false;
    
    global $loggedInID, $autoApprovePosts, $allowSubmissionsFromPublic;
    global $tableNamePrefix;
    
    if( strcmp( $loggedInID, "" ) != 0 ) {
        if( $autoApprovePosts ||
            sb_getUserDatabaseField( $loggedInID,
                                             "administrator" ) == 1 ) {
            // post, don't submit
            $postLinkName = "New Post";
            $postLinkHint = "Add a new post";
            if( $isCommentBlog ) {
                $postLinkName = "Post Comment";
                $postLinkHint = "Add a new comment";
                }
            }
        $allowPost = true;
        }
    else {
        // no one logged in
        if( $inShowSubmitLinkToPublic && $allowSubmissionsFromPublic ||
            $isCommentBlog ) {
            $allowPost = true;
            }
        }
    
    if( $allowPost ) {
        echo "[<A HREF=\"seedBlogs.php?action=edit_post&blog_name=$inBlogName".
            "&return_url=$return_url\" TITLE=\"$postLinkHint\">" .
            "$postLinkName</A>]<BR>";
        }
    if( sb_isAdministrator() ) {
        // show link to queue, if there are posts wating

        sb_connectToDatabase();
        $query =
            "SELECT COUNT(*) FROM $tableNamePrefix"."posts ".
            "WHERE approved = \"0\" AND removed = \"0\" AND ".
            "blog_name = \"$inBlogName\";";
        $result = sb_queryDatabase( $query );
        $pendingCount = mysql_result( $result, 0, 0 );
        
        sb_closeDatabase();

        if( $pendingCount > 0 ) {
            $countString = "<B>$pendingCount</B> posts";
            if( $pendingCount == 1 ) {
                $countString = "<B>$pendingCount</B> post";
                }
            echo "[<A HREF=\"seedBlogs.php?action=show_post_queue".
                                          "&blog_name=$inBlogName".
                                          "&return_url=$return_url\">" .
            "$countString in queue</A>]<BR>";
            }
        }

    if( $inShowIntroText ) {
        // extra space
        //echo "<BR>";
        }
    
    // get blog posts from the database

    $orderClause = "ORDER BY creation_date DESC";

    if( $inOrder == 0 ) {
        $orderClause = "ORDER BY expiration_date ASC";
        }
    if( $inOrder == -1 ) {
        $orderClause = "ORDER BY creation_date ASC";
        }
    
    $limitNumber = $inMaxNumber;
    
    if( $inMaxNumber == -1 ) {
        // use a large number, as suggested in the MySQL docs, to cause
        // limit to be ignored
        $limitNumber = 99999;
        }
    
    // LIMIT is only supported by MySQL
    $query =
        "SELECT * " .
        "FROM $tableNamePrefix"."posts " .
        "WHERE approved = '1' AND removed = '0' ".
        "AND blog_name = '$inBlogName' ".
        "AND ( expiration_date > CURRENT_TIMESTAMP OR " .
              "expiration_date IS NULL ) " .
        "$orderClause LIMIT $inNumToSkip, $limitNumber;";

    sb_connectToDatabase();
    
    $result = sb_queryDatabase( $query );
    
    sb_closeDatabase();
    
    $numRows = mysql_numrows( $result );

    if( $numRows == 0 ) {
        echo "[no posts]<BR>";
        }

    $mapArray = NULL;
    if( $inOrder == 2 ) {
        // use map and ignore the above query
        $mapQuery = "SELECT map FROM $tableNamePrefix"."order_map ".
            "WHERE blog_name = '$inBlogName';";
        sb_connectToDatabase();

        $result = sb_queryDatabase( $mapQuery );

        $mapRaw = "";
        
        if( mysql_numrows( $result ) == 0 ) {
            // no order_map entry yet for this blog 

            // insert a new map containing an empty string
            $mapQuery = "INSERT INTO $tableNamePrefix"."order_map ".
                "VALUES ( " .
                "'$inBlogName', '' );";
            sb_queryDatabase( $mapQuery );
            }
        else {
            $mapRaw = mysql_result( $result, 0, 0 );
            }
        
        sb_closeDatabase();        

        
        $mapArrayRaw = preg_split( "/\s+/", $mapRaw );

        // filter the map array to remove unapproved, removed, or expired
        // post_ids
        $map = "";
        
        sb_connectToDatabase();
        for( $i=0; $i<count( $mapArrayRaw ); $i++ ) {
            $post_id = $mapArrayRaw[ $i ];
                        
            if( sb_isPostVisible( $post_id ) ) {
                // post in map is visible

                // add ID to our filtered map
                if( strcmp( $map, "" ) == 0 ) {
                    $map = $post_id;
                    }
                else {
                    $map = $map . "\n" . $post_id;
                    }
                }
            }
        sb_closeDatabase();

        // now split the filtered map to get our final array
        $mapArray = preg_split( "/\s+/", $map );

        if( strcmp( $map, "" ) == 0 ) {
            // force an empty array;
            $mapArray = array();
            }
        
        // reset numRows based on the size of our mapArray
        $numRows = count( $mapArray ) - $inNumToSkip;

        if( $inMaxNumber != -1 &&
            $numRows > $inMaxNumber ) {

            $numRows = $inMaxNumber;
            }
        }

    // finally, display the posts, using either the query results or the
    // map
    for( $i=0; $i<$numRows; $i++ ) {

        $subject_line = "";
        $post_id = "";
        $intro_text = "";
        $body_text = "";
        $user_id = "";
        $date = "";
        $allow_comments = "";
        $show_permalink = "";
        
        if( $mapArray == NULL ) {
            // use the query results
            $subject_line = mysql_result( $result, $i, "subject_line" );
            $post_id = mysql_result( $result, $i, "post_id" );
            $intro_text = mysql_result( $result, $i, "intro_text" );
            $body_text = mysql_result( $result, $i, "body_text" );

            $user_id = mysql_result( $result, $i, "user_id" );
            $date = mysql_result( $result, $i, "creation_date" );
            $allow_comments = mysql_result( $result, $i, "allow_comments" );
            $show_permalink = mysql_result( $result, $i, "show_permalink" );
            }
        else {
            // ignore query results
            // re-query database according to map
            $post_id = $mapArray[ $i + $inNumToSkip ];

            $query = 
                "SELECT * " .
                "FROM $tableNamePrefix"."posts " .
                "WHERE post_id = '$post_id';";
            sb_connectToDatabase();
            $singleResult = sb_queryDatabase( $query );
            sb_closeDatabase();

            $subject_line = mysql_result( $singleResult, 0, "subject_line" );
            $post_id = mysql_result( $singleResult, 0, "post_id" );
            $intro_text = mysql_result( $singleResult, 0, "intro_text" );
            $body_text = mysql_result( $singleResult, 0, "body_text" );

            $user_id = mysql_result( $singleResult, 0, "user_id" );
            $date = mysql_result( $singleResult, 0, "creation_date" );
            $allow_comments =
                mysql_result( $singleResult, 0, "allow_comments" );
            $show_permalink =
                mysql_result( $singleResult, 0, "show_permalink" );
            }

        // trim leading/trailing whitespace
        $subject_line = trim( $subject_line );
        $intro_text = trim( $intro_text );
        $body_text = trim( $body_text );

        
        if( $inShowIntroText ) {

            $author = NULL;
            if( $inShowAuthors ) {
                $author = $user_id;
                }
            $dateString = NULL;
            if( $inShowDates ) {
                $dateString = $date;
                }

            $showUpDownWidgets =
                ( $inOrder == 2 && sb_isAdministrator() );

            $index = $i + $inNumToSkip;

            // show up widget if we are down from the top
            $showUpWidget =
                ( $index > 0 ) &&
                $showUpDownWidgets;

            // show down widget if we are up from the bottom
            $showDownWidget =
                ( $index < count( $mapArray ) - 1 ) &&
                $showUpDownWidgets;

            sb_generateStoryBlock( $inBlogName,
                                           $post_id,
                                           $subject_line,
                                           $author,
                                           $dateString,
                                           $showUpWidget,
                                           $showDownWidget,
                                           $intro_text,
                                           $body_text,
                                           0,  // show link to body text
                                           $allow_comments,
                                           $show_permalink,
                                           $return_url,
                                           // formatting options:
                                           $inStoryBlockFormatOpen,
                                           $inStoryBlockFormatClose,
                                           $inHeadlineFormatOpen,
                                           $inHeadlineFormatClose,
                                           $inTextBlockFormatOpen,
                                           $inTextBlockFormatClose );

            }
        else {
            $linkTarget = "seedBlogs.php?action=display_post&".
                "post_id=$post_id".
                "&show_author=$inShowAuthors&show_date=$inShowDates";
            $directURLTarget = false;
            
            if( $intro_text != NULL && $body_text == NULL ) {
                // we just have intro text and no body.
                // check if the intro text contains just a URL
                
                // intro text has already been trimmed of leading/trailing
                // whitespace above
                
                if( strstr( $intro_text, "http://" ) != false &&
                    strpos( $intro_text, "http://" ) == 0 &&
                    strstr( $intro_text, " " ) == false ) {
                    // intro text starts with URL and contains nothing else
                    
                    // make a direct link
                    $linkTarget = trim( $intro_text );
                    
                    $directURLTarget = true;
                    }
                }

            // open a story block for the headline
            echo "$inStoryBlockFormatOpen";
            
            // link around subject, with formatting inside link tags
            echo "<A HREF=\"$linkTarget\">$inHeadlineFormatOpen".
                "$subject_line".
                "$inHeadlineFormatClose</A>";

            if( $directURLTarget && sb_canEdit( $post_id ) ) {
                // problem:  clicking a direct URL link takes you to the URL
                //           and not the display page, so there is no
                //           way to edit the post.

                // add a special edit link to these posts
                echo " [<A HREF=\"seedBlogs.php?action=edit_post".
                    "&blog_name=$inBlogName".
                    "&post_id=$post_id&return_url=$return_url".
                    "&show_author=$inShowAuthors&show_date=$inShowDates\">" .
                    "Edit</A>]";
                }
            
            if( $inOrder == 2 && sb_isAdministrator() ) {
                // show up/down widgets
                $index = $i + $inNumToSkip;

                $upShown = false;
                if( $index > 0 ) {
                    echo " [<A HREF=\"seedBlogs.php?action=move_up".
                        "&blog_name=$inBlogName".
                        "&post_id=$post_id&return_url=$return_url\">" .
                        "Up</A>]";
                    $upShown = true;
                    }
                if( $index < count( $mapArray ) - 1 ) {
                    if( ! $upShown ) {
                        // insert space to separate down widget from headline
                        echo " ";
                        }
                    echo "[<A HREF=\"seedBlogs.php?action=move_down".
                        "&blog_name=$inBlogName".
                        "&post_id=$post_id&return_url=$return_url\">" .
                        "Down</A>]";
                    }
                }

            echo "$inStoryBlockFormatClose";
            }
            

        if( $i < $numRows - 1 ) {
            // separate from next story
            echo "$inStorySeparator";
            }
        }


    
    if( $inShowArchive ) {
        // count total number of posts to see if we need the archive link

        $postCount = 0;
        
        if( $mapArray == NULL ) {
            sb_connectToDatabase();
            $query =
                "SELECT COUNT(*) FROM $tableNamePrefix"."posts ".
                "WHERE approved = '1' ".
                "AND removed = '0' AND blog_name = '$inBlogName' " .
                "AND ( expiration_date > CURRENT_TIMESTAMP OR " .
                      "expiration_date IS NULL );";
            $result = sb_queryDatabase( $query );
            $postCount = mysql_result( $result, 0, 0 );
            
            sb_closeDatabase();
            }
        else {
            $postCount = count( $mapArray );
            }

        $numOlderPosts = $postCount - ( $inNumToSkip + $numRows );
        
        if( $numOlderPosts > 0 ) {
            // there are more posts in the archive
            
            if( $inShowIntroText ) {
                // extra space
                echo "<BR><BR>";
                }
            
            // archive pages have 10 posts each
            // show link to archive
            $offset = $inNumToSkip + $numRows;
            echo "[<A HREF=\"seedBlogs.php?action=show_archive" .
                "&blog_name=$inBlogName&order=$inOrder&count=10&".
                "show_authors=$inShowAuthors&show_dates=$inShowDates&".
                "offset=$offset".
                "&show_intro=$inShowIntroText".
                "&show_submit_link_to_public=$inShowSubmitLinkToPublic".
                "\" TITLE=\"View the post archive\">$numOlderPosts ".
                "in Archive</A>]<BR>";
            }
        }
    }



/**
 * Generates a URL to the RSS 2.0 feed for a given seedBlog.
 * GETing this URL will return RSS XML.
 *
 * Order of RSS feed is fixed to "order by creation date", with
 * newest posts listed first.
 *
 * @param $inBlogName the name of the blog in the database.  Should not
 *   contain spaces or special characters.
 * @param $inChannelTitle the name of the RSS channel.
 * @param $inChannelDescription the description of the RSS channel.
 * @param $inMaxNumber the maximum number of items to include in the feed.
 *   -1 specifies no limit.
 *   Defaults to 10.
 * @param $inShowAuthors 1 to show authors, or 0 to hide them.  Defaults to 1.
 * @param $inShowDates 1 to show dates, or 0 to hide them.  Defaults to 1.
 */
function seedBlogRSSLink( $inBlogName,
                          $inChannelTitle,
                          $inChannelDescription,
                          $inMaxNumber = 10,
                          $inShowAuthors = 1,
                          $inShowDates = 1 ) {

    $encodedTitle = urlencode( $inChannelTitle );
    $encodedDescription = urlencode( $inChannelDescription );
    $urlParams =
        "?action=rss_feed&".
        "blog_name=$inBlogName&".
        "channel_title=$encodedTitle&" .
        "channel_description=$encodedDescription&".
        "max_number=$inMaxNumber&show_authors=$inShowAuthors&".
        "show_dates=$inShowDates";

    global $fullSeedBlogsURL;
    
    return $fullSeedBlogsURL . $urlParams;
    }



/**
 * Just like seedBlogRSSLink, but generates full HTML for an RSS button.
 *
 * Call this wherever you want an RSS button to appear on your page.
 */
function seedBlogRSSButton( $inBlogName,
                            $inChannelTitle,
                            $inChannelDescription,
                            $inMaxNumber = 10,
                            $inShowAuthors = 1,
                            $inShowDates = 1 ) {

    $rss_url = seedBlogRSSLink( $inBlogName,
                                $inChannelTitle,
                                $inChannelDescription,
                                $inMaxNumber,
                                $inShowAuthors,
                                $inShowDates );
    echo "<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=1>".
        "<TR><TD BGCOLOR=#898E79>".
        "<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=1>".
        "<TR><TD BGCOLOR=#FFFFFF>".
        "<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2>".
        "<TR><TD BGCOLOR=#FF6600>".
        "<A HREF=\"$rss_url\"><FONT COLOR=#FFFFFF><B>RSS 2.0</B></FONT></A>".
        "</TD></TR></TABLE></TD></TR></TABLE></TD></TR></TABLE>";
    }



// end of functions that might be called externally by end-users






// general processing whenver seedBlogs.php is accessed directly

// grab POST/GET variables
$action = "";
if( isset( $_REQUEST[ "action" ] ) ) {
    $action = sb_getRequestVariableSafe( "action" );
    }
$post_id = "";
if( isset( $_REQUEST[ "post_id" ] ) ) {
    $post_id = sb_getRequestVariableSafe( "post_id" );
    }
$blog_name = "";
if( isset( $_REQUEST[ "blog_name" ] ) ) {
    $blog_name = sb_getRequestVariableSafe( "blog_name" );
    }

global $return_url;
$return_url = "";
if( isset( $_REQUEST[ "return_url" ] ) ) {
    $return_url = sb_getRequestVariableSafe( "return_url" );
    }


if( strcmp( $post_id, "" ) == 0 ) {
    $post_id = NULL;
    }

if( strcmp( $action, "version" ) == 0 ) {
    global $seedBlogs_version;
    echo "$seedBlogs_version";
    }
else if( strcmp( $action, "login" ) == 0 ) {
    sb_login();
    }
else if( strcmp( $action, "logout" ) == 0 ) {
    sb_logout();
    }
else if( strcmp( $action, "show_register_form" ) == 0 ) {
    sb_showRegisterForm( "" );
    }
else if( strcmp( $action, "show_password_help_form" ) == 0 ) {
    sb_showPasswordHelpForm( "" );
    }
else if( strcmp( $action, "send_password_email" ) == 0 ) {
    sb_sendPasswordEmail( "" );
    }
else if( strcmp( $action, "register" ) == 0 ) {
    sb_register();
    }
else if( strcmp( $action, "setup_database" ) == 0 ) {
    sb_setupDatabase();
    }
else if( strcmp( $action, "edit_post" ) == 0 ) {
    sb_showEditor( $blog_name, $post_id );
    }
else if( strcmp( $action, "update_post" ) == 0 ) {
    sb_updatePost( $blog_name, $post_id );
    }
else if( strcmp( $action, "move_up" ) == 0 ) {
    sb_moveUp( $blog_name, $post_id );
    }
else if( strcmp( $action, "move_down" ) == 0 ) {
    sb_moveDown( $blog_name, $post_id );
    }
else if( strcmp( $action, "display_post" ) == 0 ) {
    sb_displayPost( $post_id );
    }
else if( strcmp( $action, "show_archive" ) == 0 ) {
    sb_showArchive( $blog_name );
    }
else if( strcmp( $action, "approve_post" ) == 0 ) {
    sb_approvePost( $post_id );
    }
else if( strcmp( $action, "approve_account" ) == 0 ) {
    sb_approveAccount();
    }
else if( strcmp( $action, "change_admin_status" ) == 0 ) {
    sb_changeAdminStatus();
    }
else if( strcmp( $action, "remove_account" ) == 0 ) {
    sb_removeAccount();
    }
else if( strcmp( $action, "show_post_queue" ) == 0 ) {
    sb_showPostQueue( $blog_name );
    }
else if( strcmp( $action, "show_account_queue" ) == 0 ) {
    sb_showAccountQueue();
    }
else if( strcmp( $action, "show_account_list" ) == 0 ) {
    sb_showAccountList();
    }
else if( strcmp( $action, "search" ) == 0 ) {
    sb_search();
    }
else if( strcmp( $action, "rss_feed" ) == 0 ) {
    sb_rssFeed();
    }
else if( strcmp( $action, "sb_setup" ) == 0 ) {
    global $header, $footer;
    //include_once( $header );
    global $setup_header, $setup_footer;
    echo $setup_header; 

    echo "<H2>seedBlogs Web-based Setup</H2>";

    echo "Creating tables:<BR>";

    echo "<CENTER><TABLE BORDER=0 CELLSPACING=0 CELLPADDING=1>
          <TR><TD BGCOLOR=#000000>
          <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=5>
          <TR><TD BGCOLOR=#FFFFFF>";

    sb_setupDatabase();

    echo "</TD></TR></TABLE></TD></TR></TABLE></CENTER><BR><BR>";
    
    echo "After you create an admin account, the setup process will be ".
        "complete.<BR><BR>";

    echo "Step 2: ";

    echo "<CENTER>";
    
    seedBlogs_showLoginBox();

    echo "</CENTER>";

    echo $setup_footer;
    //include_once( $footer );
    }
else if( preg_match( "/seedBlogs\.php/", $_SERVER[ "SCRIPT_NAME" ] ) ) {
    // seedBlogs.php has been called without an action parameter

    // the preg_match ensures that seedBlogs.php was called directly and
    // not just included by another script
    
    // quick (and incomplete) test to see if we should show ins
    global $tableNamePrefix;
    
    // check if our "posts" table exists
    $tableName = $tableNamePrefix . "posts";
    sb_connectToDatabase();

    $exists = sb_doesTableExist( $tableName );
    sb_closeDatabase();
    
    if( $exists  ) {
    
        // show main page
        global $mainSiteURL;
        // redirect
        header( "Location: $mainSiteURL" );
        }
    else {
        // start the setup procedure

        global $header, $footer;
        //include_once( $header );
        global $setup_header, $setup_footer;
        echo $setup_header; 

        echo "<H2>seedBlogs Web-based Setup</H2>";
    
        echo "seedBlogs will walk you through a brief setup process.<BR><BR>";
        
        echo "Step 1: ".
            "<A HREF=\"seedBlogs.php?action=sb_setup\">".
            "create the database tables</A>";

        echo $setup_footer;
        //include_once( $footer );
        }

    }



/**
 * Creates the database tables needed by seedBlogs.
 */
function sb_setupDatabase() {
    global $tableNamePrefix;
    
    // make sure our "posts" table exists
    $tableName = $tableNamePrefix . "posts";
    sb_connectToDatabase();
    if( ! sb_doesTableExist( $tableName ) ) {

        // this table contains all the information for each post
        $query =
            "CREATE TABLE $tableName(" .
            "post_id VARCHAR(255) NOT NULL PRIMARY KEY," .
            "blog_name VARCHAR(255) NOT NULL," .
            "user_id VARCHAR(20) NOT NULL," .
            "creation_date DATETIME NOT NULL," .
            "change_date DATETIME NOT NULL," .
            "expiration_date DATETIME," .
            "allow_comments TINYINT NOT NULL," .
            "show_permalink TINYINT NOT NULL," .
            "approved TINYINT NOT NULL," .
            "removed TINYINT NOT NULL," . 
            "subject_line VARCHAR(60) NOT NULL," .
            "intro_text LONGTEXT," .
            "body_text LONGTEXT );";

        $result = sb_queryDatabase( $query );

        echo "<B>$tableName</B> table created<BR>";
        }
    else {
        echo "<B>$tableName</B> table already exists<BR>";
        }

    $tableName = $tableNamePrefix . "users";
    if( ! sb_doesTableExist( $tableName ) ) {

        // this table contains information for each user
        $query =
            "CREATE TABLE $tableName(" . 
            "user_id VARCHAR(20) NOT NULL PRIMARY KEY," .
            "password_md5 CHAR(32) NOT NULL,".
            "email VARCHAR(255),".
            "session_id CHAR(32) NULL,".
            "approved TINYINT NOT NULL," .
            "administrator TINYINT NOT NULL );";
        
        $result = sb_queryDatabase( $query );
        
        echo "<B>$tableName</B> table created<BR>";
        }
    else {
        echo "<B>$tableName</B> table already exists<BR>";
        }

    $tableName = $tableNamePrefix . "order_map";
    if( ! sb_doesTableExist( $tableName ) ) {

        // this table contains order information for each blog
        $query =
            "CREATE TABLE $tableName(" .
            "blog_name VARCHAR(255) NOT NULL PRIMARY KEY," .
            "map LONGTEXT NOT NULL );";

        // each map field contains a list of post_ids separated by whitespace
        
        $result = sb_queryDatabase( $query );
        
        echo "<B>$tableName</B> table created<BR>";
        }
    else {
        echo "<B>$tableName</B> table already exists<BR>";
        }
    
    sb_closeDatabase();

    }



/**
 * Logs a user in (setting the global $loggedInID) according to
 * the POSTED variables.
 */
function sb_login() {
    // the body of this function was largely copied from the NCN project
    
    // grab posted variables

    $user_id = sb_getRequestVariableSafe( "user_id" );
    // never used in database query, so strip once here
    $password = sb_stripMagicQuotes( sb_getRequestVariableRaw( "password" ) );

    if( sb_doesUserExist( $user_id ) ) {
        
        if( sb_getUserDatabaseField( $user_id, "approved" ) == 0 ) {
            // display failure page
            sb_messagePage( "User ID <B>" .
                         sb_stripMagicQuotes( $user_id ) .
                         "</B> has no been approved yet." );
            }
        else {
            
            $passwordMD5 = sb_computePasswordHash( sb_stripMagicQuotes( $user_id ),
                                                $password );
        
            $truePasswordMD5 = sb_getUserDatabaseField( $user_id,
                                                     "password_md5" );

            if( strcmp( $truePasswordMD5, $passwordMD5 ) == 0 ) {
                
                $session_id = sb_computeSessionID( sb_stripMagicQuotes( $user_id ),
                                                $password );
                
                sb_setUserDatabaseField( $user_id, "session_id", $session_id );
                
                // set cookies with the user_id and session_id
                sb_refreshCookie( $user_id, $session_id );
                
                // set global
                global $loggedInID;
                $loggedInID = $user_id;
                
                // show page user logged in from

                // redirect
                global $return_url;
                header( "Location: $return_url" );
                }
            else {
                // display failure page
                sb_messagePage( "Log in failed." );
                }
            }
        }
    else {
        // display failure page
        sb_messagePage( "User ID <B>" .
                     sb_stripMagicQuotes( $user_id ) .
                     "</B> does not exist." );
        }
    }






/**
 * Logs the current user out and clears cookies.
 */
function sb_logout() {
    // clear cookie in user's browser
    sb_clearCookie();

    global $justLoggedOut, $loggedInID;
    
    // clear the session id in the database
    sb_setUserDatabaseField( $loggedInID, "session_id", NULL );
    
    // tell other parts of script to ignore set cookies
    $justLoggedOut = 1;

    // drop the ID that we have read from the cookies so that
    // the messagePage can reflect the fact that the user has logged out
    $loggedInID = "";
    
    sb_messagePage( "You have successfully logged out." );
    }



/**
 * Shows the user registration form, or shows the account editing form
 * if a user is already logged in.
 *
 * @param inMessage the message to display.
 */
function sb_showRegisterForm( $inMessage ) {
    global $header, $footer;
    
    include_once( $header );

    echo "<B>$inMessage</B>";

    global $loggedInID, $tableNamePrefix;

    $emailValue = "";
    $editExisting = false;
    $buttonName = "Register";
    
    if( strcmp( $loggedInID, "" ) != 0 ) {
        // user is already logged in

        // query to get the current email address
        $query = "SELECT * FROM $tableNamePrefix"."users ".
            "WHERE user_id = '$loggedInID';";
        sb_connectToDatabase();
        $result = sb_queryDatabase( $query );
        sb_closeDatabase();

        $emailValue = mysql_result( $result, 0, "email" );
        $editExisting = true;
        $buttonName = "Update";
        }
    
?>
    <FORM ACTION="seedBlogs.php" METHOD="post">
    <INPUT TYPE="hidden" NAME="action" VALUE="register">
<?php
    global $return_url;

    echo "<INPUT TYPE=\"hidden\" NAME=\"return_url\" VALUE=\"$return_url\">";
    
    if( $editExisting ) {
        echo "<INPUT TYPE=\"hidden\" ".
             "NAME=\"user_id\" VALUE=\"$loggedInID\">";
        }
    
        
    echo "<TABLE BORDER=0>";
    
    if( !$editExisting ) {
?>
        <TR><TD>User ID:</TD>
        <TD><INPUT TYPE="text" MAXLENGTH=20 SIZE=20 NAME="user_id"></TD></TR>
<?php
        }
    else {
        echo "<TR><TD COLSPAN=2>Leave blank to keep old password</TD></TR>";
        }
?>
    <TR><TD><?php if( $editExisting ) echo "New ";?>Password:</TD>
    <TD><INPUT TYPE="password" MAXLENGTH=20 SIZE=20 NAME="password"></TD></TR>
    <TR><TD>Re-type Password:</TD>
    <TD><INPUT TYPE="password" MAXLENGTH=20 SIZE=20
               NAME="password_b"></TD></TR>
    <TR><TD>Email:</TD>
    <TD><INPUT TYPE="text" MAXLENGTH=255 SIZE=20 NAME="email"
               VALUE="<?php echo $emailValue;?>"></TD></TR>
    <TR><TD ALIGN=RIGHT COLSPAN=2>
        <INPUT TYPE="Submit" VALUE="<?php echo $buttonName;?>"></TD><TR>
    </TABLE>
    </FORM>
<?php
    include_once( $footer );
    }



/**
 * Shows a form the user can fill out for help with forgotton passwords.
 *
 * @param inMessage the message to display.
 */
function sb_showPasswordHelpForm( $inMessage ) {
    global $header, $footer;
    
    include_once( $header );

    echo "<B>$inMessage</B><BR>";

    echo "Enter <EM>either</EM> your user ID or your email address:"
?>
    <FORM ACTION="seedBlogs.php" METHOD="post">
    <INPUT TYPE="hidden" NAME="action" VALUE="send_password_email">
    <TABLE BORDER=0>
    <TR><TD>User ID:</TD>
    <TD><INPUT TYPE="text" MAXLENGTH=255 SIZE=20 NAME="user_id"
               VALUE=""></TD></TR>
    <TR><TD>Email:</TD>
    <TD><INPUT TYPE="text" MAXLENGTH=255 SIZE=20 NAME="email"
               VALUE=""></TD></TR>
    <TR><TD ALIGN=RIGHT COLSPAN=2>
        <INPUT TYPE="Submit" VALUE="Send New Password by Email"></TD><TR>
    </TABLE>
    </FORM>
<?php
    include_once( $footer );
    }



/**
 * Send a notice to the admins.
 *
 * @param inMessage the email message to send.
 */
function sb_sendAdminNotice( $inMessage ) {

    // first, pull all admins from database
    $query = "";
    global $tableNamePrefix;

    $query = "SELECT * FROM $tableNamePrefix"."users ".
        "WHERE administrator = '1';";
    
    sb_connectToDatabase();
    
    $result = sb_queryDatabase( $query );
    
    sb_closeDatabase();

    
    $numRows = mysql_numrows( $result );

    if( $numRows < 1 ) {
        // no admins
        return;
        }
    
    $emailList = mysql_result( $result, 0, "email" );
    $userIDList = mysql_result( $result, 0, "user_id" );
    
    for( $i=1; $i<$numRows; $i++ ) {
        $user_id = mysql_result( $result, $i, "user_id" );
        $email = mysql_result( $result, $i, "email" );

        if( $i == $numRows - 1 ) {
            // last user, insert and before name in list
            $userIDList = $userIDList . ", and " . $user_id;
            }
        else {
            // middle of list, just comma and space
            $userIDList = $userIDList . ", " . $user_id;
            }
        
        $emailList = $emailList . ", " . $email;
        }

    $adminListNotice = "";

    if( $numRows > 1 ) {
        // more than one admin
        // remind them of this fact to avoid confusion

        $adminListNotice =
            "\nNote that these admins were all notified ".
            "about this issue:\n".
            "$userIDList\n";
        }
    
    global $siteName, $mainSiteURL, $siteEmailAddress;
    $mailHeaders = "From: $siteEmailAddress";
    $result = mail( $emailList, "$siteName admin action needed",
                    "The following action is pending ".
                    "administrator approval:\n\n".
                    "$inMessage\n".
                    "$adminListNotice",
                    $mailHeaders );
    
    }



/**
 * Sends out a password email using the POSTed variables.
 */
function sb_sendPasswordEmail() {
    global $header, $footer;

    $user_id = sb_getRequestVariableSafe( "user_id" );

    $email = sb_getRequestVariableSafe( "email" );


    $error = 0;
    
    // first, make sure the required fields are provided
    if( strcmp( $user_id, "" ) == 0 && strcmp( $email, "" ) == 0 ) {
        $error = 1;
        sb_showPasswordHelpForm( "You must provide some account information." );
        }

    if( ! $error ) {

        // query to either find user with this ID
        // or find all users with this email
        $query = "";
        global $tableNamePrefix;
        
        if( strcmp( $user_id, "" ) != 0 ) {
            $query = "SELECT * FROM $tableNamePrefix"."users ".
                "WHERE user_id = '$user_id';";
            }
        else {
            $query = "SELECT * FROM $tableNamePrefix"."users ".
                "WHERE email = '$email';";
            }

        sb_connectToDatabase();
        
        $result = sb_queryDatabase( $query );
        
        sb_closeDatabase();

        $numRows = mysql_numrows( $result );

        if( $numRows == 0 ) {
            sb_showPasswordHelpForm(
                "The information you entered does not match any account." );
            }
        else if( $numRows > 1 ) {
            sb_showPasswordHelpForm(
                "More than one account uses this email address.<BR>".
                "You must provide a User ID." );
            }
        else {
            $user_id = mysql_result( $result, 0, "user_id" );
            $email = mysql_result( $result, 0, "email" );
            $password_md5 = mysql_result( $result, 0, "password_md5" );

            // compute a new, temporary password
            
            // however, we need to generate a password that
            // cannot be guessed by attackers

            // we can use the password MD5 sum (which we know) as a seed

            $temp_session_id = sb_computeSessionID( $user_id, $password_md5 );

            // temp passwords are 10 hex digits long
            // there are roughly 10^12 possible temp passwords
            $temp_password = substr( $temp_session_id, 0, 10 );

            $temp_password_md5 =
                sb_computePasswordHash( sb_stripMagicQuotes( $user_id ),
                                     $temp_password );
            sb_setUserDatabaseField( $user_id,
                                  "password_md5", $temp_password_md5 );

            global $siteName, $mainSiteURL, $siteEmailAddress;
            $mailHeaders = "From: $siteEmailAddress";
            $result = mail( $email, "$siteName temporary password",
                            "Your password at $mainSiteURL has been ".
                            "reset.\n\n".
                            "Here is your temporary account information:\n\n".
                            "User ID: $user_id\n".
                            "Password: $temp_password\n",
                            $mailHeaders );

            sb_messagePage(
                "A temporary password has been sent to you by email." );
            }
        }
    }



/**
 * Processes the variables posted by the register form.
 */
function sb_register() {
    global $tableNamePrefix, $loggedInID, $autoApproveUsers;

    $updateExisting = false;
    if( strcmp( $loggedInID, "" ) != 0 ) {
        $updateExisting = true;
        }
    
    // grab posted variables

    $user_id = sb_getRequestVariableSafe( "user_id" );
    // never used in database query, so strip once here
    $password = sb_stripMagicQuotes( sb_getRequestVariableRaw( "password" ) );
    $password_b = sb_stripMagicQuotes( sb_getRequestVariableRaw( "password_b" ) );

    $email = sb_getRequestVariableSafe( "email" );

    $error = 0;
    
    // first, make sure the required fields are provided
    if( !$updateExisting && strcmp( $user_id, "" ) == 0 ) {
        $error = 1;
        sb_showRegisterForm( "\"User ID\" is a required field." );
        }
    else if( !$updateExisting && strcmp( $password, "" ) == 0 ) {
        $error = 1;
        sb_showRegisterForm( "You must enter a password." );
        }
    else if( strcmp( $email, "" ) == 0 ) {
        $error = 1;
        sb_showRegisterForm( "You must enter an email address." );
        } 
    else if( strcmp( $password, $password_b ) != 0 ) {
        $error = 1;
        sb_showRegisterForm( "Your re-typed password does not match." );
        }

    if( ! $error ) {
        if( !$updateExisting && sb_doesUserExist( $user_id ) ) {

            sb_showRegisterForm( "User id <B>$user_id</B> already exists." );
            }
        else if( !$updateExisting && strcmp( $user_id, "Anonymous" ) == 0 ) {

            sb_showRegisterForm( "User id <B>Anonymous</B> is reserved." );
            }
        else if( !$updateExisting ) {
            $password_md5 = sb_computePasswordHash( sb_stripMagicQuotes( $user_id ),
                                                 $password );

            $approved = 0;
            $administrator = 0;

            if( sb_getUserCount() == 0 ) {
                // auto admin and approve
                $approved = 1;
                $administrator = 1;
                }
            if( $autoApproveUsers ) {
                $approved = 1;
                }
            $query = "INSERT INTO $tableNamePrefix". "users VALUES ( " .
                "'$user_id', '$password_md5', '$email', NULL, ".
                "'$approved', '$administrator' );";

            sb_connectToDatabase();
            
            $result = sb_queryDatabase( $query );

            sb_closeDatabase();

            if( $approved ) {
                // log the user in using same POST variables
                sb_login();
                }
            else {
                // tell the user that their account registration is pending
                // display failure page

                sb_messagePage( "Your account request has been sent to the ".
                             "administrators for approval.<BR>".
                             "You will receive an email with further ".
                             "information." );

                global $fullSeedBlogsURL, $emailAdminsAboutPendingItems;

                if( $emailAdminsAboutPendingItems ) {
                    sb_sendAdminNotice(
                        "The following new account is waiting for approval:\n".
                        "$user_id\n\n".
                        "After you log in, check the following link for ".
                        "details:\n".
                        "$fullSeedBlogsURL?action=show_account_queue" );
                    }
                }

            $approvalMessage = "";
            if( $approved ) {
                $approvalMessage =
                    "Your account request has been auto-approved.";
                }
            else {
                $approvalMessage =
                    "Your account is awaiting approval from the ".
                    "administrators.  You will receive an email when your ".
                    "account is approved.";
                }
            // send an email with account information
            global $siteName, $mainSiteURL, $siteEmailAddress;
            $mailHeaders = "From: $siteEmailAddress";
            $result = mail( $email, "$siteName account requested",
                            "Your account request at $mainSiteURL has been ".
                            "received.\n\n".
                            "$approvalMessage\n\n".
                            "Here is your account information:\n\n".
                            "User ID: $user_id\n".
                            "Email: $email\n",
                            $mailHeaders );
            }
        else {
            // updating an existing account
            
            $passwordUpdate = "";
            if( strcmp( $password, "" ) != 0 ) {
                // new password (already checked that $password_b matches) 
                $password_md5 = sb_computePasswordHash(
                    sb_stripMagicQuotes( $loggedInID ), $password );

                $passwordUpdate = "password_md5 = '$password_md5', ";
                }
            $query = "UPDATE $tableNamePrefix". "users SET " .
                "$passwordUpdate email = '$email' ".
                "WHERE user_id = '$loggedInID';";

            sb_connectToDatabase();
            
            $result = sb_queryDatabase( $query );

            sb_closeDatabase();

            $passwordMessage = "";
            if( strcmp( $password, "" ) != 0 ) {
                // log the user in using same POST variables
                // need to do this to reset the cookie
                sb_login();
                $passwordMessage = "(new password set)\n";
                }
            else {
                sb_messagePage( "Your account information has been updated.<BR> ".
                             "You will receive an email with your new ".
                             "information." );
                }
            
            // send an email with the updated account information
            global $siteName, $mainSiteURL, $siteEmailAddress;
            $mailHeaders = "From: $siteEmailAddress";
            $result = mail( $email, "$siteName account information updated",
                            "Your account information at $mainSiteURL has ".
                            "been updated.\n\n".
                            "Here is your new account information:\n\n".
                            "User ID: $user_id\n".
                            "$passwordMessage".
                            "Email: $email\n",
                            $mailHeaders );
            }
        }
    }



/**
 * Shows the editor form.
 *
 * @param $inBlogName the name of the blog to edit.
 * @param $inPostID the postID to fill the form with, or NULL to
 *   show a blank form.
 */
function sb_showEditor( $inBlogName, $inPostID ) {
    global $tableNamePrefix, $autoApprovePosts;

    $show_author = sb_getRequestVariableSafe( "show_author" );
    $show_date = sb_getRequestVariableSafe( "show_date" );
    
    $blog_name = $inBlogName;
    $author_name = "";
    $subject_line = "";
    $intro_text = "";
    $body_text = "";
    $expiration_date = NULL;
    $allow_comments = 0;
    // default to showing permalink
    $show_permalink = 1;
    $approved = 0;
    $isExistingPost = false;
    
    // populate form fields from database
    if( $inPostID != NULL ) {
        $query =
            "SELECT * " .
            "FROM $tableNamePrefix"."posts " .
            "WHERE post_id = '$inPostID';";
        
        sb_connectToDatabase();
        
        $result = sb_queryDatabase( $query );

        if( mysql_numrows( $result ) != 1 ) {
            sb_closeDatabase();
            sb_fatalError( "Post $inPostID does not exist in database." );
            }

        $row = mysql_fetch_array( $result, MYSQL_ASSOC );

        $blog_name = $row[ "blog_name" ];
        $author_name = $row[ "user_id" ];
        $subject_line = $row[ "subject_line" ];
        $intro_text = $row[ "intro_text" ];
        $body_text = $row[ "body_text" ];
        $expiration_date = $row[ "expiration_date" ];
        $allow_comments = $row[ "allow_comments" ];
        $show_permalink = $row[ "show_permalink" ];
        $approved = $row[ "approved" ];
        
        sb_closeDatabase();

        $isExistingPost = true;
        }
    
    $buttonName = "Submit for Approval";

    if( $isExistingPost ) {
        $buttonName = "Update";
        }
    else {
        if( sb_isAdministrator() ||
            ( $autoApprovePosts && strcmp( $loggedInID, "" ) != 0 ) ) {
            // this is a direct post
            $buttonName = "Post";
            }
        }
    
    // include the header before generating a page
    global $header, $footer;
    include_once( $header );

    global $return_url;
?>
     <FORM ACTION="seedBlogs.php" METHOD="post">
     <INPUT TYPE="hidden" NAME="action"
          VALUE="update_post">
     <INPUT TYPE="hidden" NAME="return_url"
          VALUE="<?php echo $return_url; ?>">
     <INPUT TYPE="hidden" NAME="show_author"
          VALUE="<?php echo $show_author; ?>">
     <INPUT TYPE="hidden" NAME="show_date"
          VALUE="<?php echo $show_date; ?>">
     <INPUT TYPE="hidden" NAME="blog_name"
          VALUE="<?php echo $blog_name; ?>">
<?php
     if( $inPostID != NULL ) {
?>
     <INPUT TYPE="hidden" NAME="post_id"
         VALUE="<?php echo $inPostID; ?>">
<?php
         }
?>
    <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=1><TR><TD BGCOLOR=#777777>
    <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=10>    
<?php
    // alternating background colors for blocks in form
    $bgColor = "#CCCCCC";
    $altColor = "#EEEEEE";
    
    global $loggedInID;
    if( strcmp( $loggedInID, "" ) == 0 ) {
        // no one logged in, allow them to provide a name
?>
    <TR><TD ALIGN=LEFT BGCOLOR=<?php echo $bgColor;?>>
    Your Name:
    </TD>
    <TD ALIGN=RIGHT BGCOLOR=<?php echo $bgColor;?>>
    <INPUT TYPE="text" MAXLENGTH=60 SIZE=30 NAME="author_name"
         VALUE="<?php echo $author_name; ?>">
    </TD>
    </TR>
<?php
        $tempColor = $bgColor;
        $bgColor = $altColor;
        $altColor = $tempColor;
        }
?>
    <TR><TD ALIGN=LEFT BGCOLOR=<?php echo $bgColor;?>>
    Headline:
    </TD>
    <TD ALIGN=RIGHT BGCOLOR=<?php echo $bgColor;?>>
    <INPUT TYPE="text" MAXLENGTH=60 SIZE=30 NAME="subject_line"
         VALUE="<?php echo $subject_line; ?>">
    </TD>
    </TR>
<?php
    $tempColor = $bgColor;
    $bgColor = $altColor;
    $altColor = $tempColor;

    $introTextName = "Intro Text:";

    if( preg_match( "/_comments/", $blog_name ) ) {
        // only one block of text for comments
        $introTextName = "Text:";
        }
?>
    <TR><TD COLSPAN=2 BGCOLOR=<?php echo $bgColor;?>>
    <?php echo $introTextName; ?><BR>
    <TEXTAREA NAME="intro_text" COLS=50 ROWS=10><?php echo htmlspecialchars( $intro_text ); ?></TEXTAREA>
    </TD></TR>
<?php
    $tempColor = $bgColor;
    $bgColor = $altColor;
    $altColor = $tempColor;


    // hide body text block for comments
    if( ! preg_match( "/_comments/", $blog_name ) ) {
?>
        <TR><TD COLSPAN=2 BGCOLOR=<?php echo $bgColor;?>>
        Body Text:<BR>
        <TEXTAREA NAME="body_text" COLS=50 ROWS=10><?php echo htmlspecialchars( $body_text ); ?></TEXTAREA>
        </TD></TR>
                 
<?php
    
                                                                                       $tempColor = $bgColor;
        $bgColor = $altColor;
        $altColor = $tempColor;
        }
    else {
        // force blank body text
        echo '<INPUT TYPE="hidden" NAME="body_text" VALUE="">';
        }

    
    if( ! preg_match( "/_comments/", $blog_name ) ) {
        // no expiration dates allowed on comments
?>
    <TR>
    <TD ALIGN=LEFT BGCOLOR=<?php echo $bgColor;?>>
    Expires:
    </TD>
    <TD ALIGN=LEFT BGCOLOR=<?php echo $bgColor;?>>
<?php

    $fillWithCurrentTime = 0;

    // unchecked
    $neverExpiresCheckedState = "";
    
    if( $expiration_date == NULL ) {
        $fillWithCurrentTime = 1;

        $neverExpiresCheckedState = "CHECKED";
        }
    sb_printDateTimeFormFromTimestamp( "expire_",
                                    $fillWithCurrentTime, $expiration_date );
?>
    <INPUT TYPE="checkbox" NAME="never_expires" VALUE=1
         <?php echo $neverExpiresCheckedState;?> > Never Expires
    </TD>
    </TR>
<?php
        $tempColor = $bgColor;
        $bgColor = $altColor;
        $altColor = $tempColor;
        }

                                                       
    // should we show comment-enabling widgets?

    // no comments allowed on comments
    if( ! preg_match( "/_comments/", $blog_name ) ) {
        $allowCommentsCheckedState = "";
    
        if( $allow_comments ) {
            $allowCommentsCheckedState = "CHECKED";
            }
?>
    <TR><TD BGCOLOR=<?php echo $bgColor;?>></TD>
    <TD BGCOLOR=<?php echo $bgColor;?>>
    <INPUT TYPE="checkbox" NAME="allow_comments" VALUE=1
           <?php echo $allowCommentsCheckedState;?> > Allow Comments
    </TD>
    </TR>
<?php
        $tempColor = $bgColor;
        $bgColor = $altColor;
        $altColor = $tempColor;
        }


    // only give permalink option for non-comments
    if( ! preg_match( "/_comments/", $blog_name ) ) {
        // permalink-enabling widget

        $showPermalinkCheckedState = "";
        if( $show_permalink ) {
            $showPermalinkCheckedState = "CHECKED";
            }
?>
    <TR><TD BGCOLOR=<?php echo $bgColor;?>></TD>
    <TD BGCOLOR=<?php echo $bgColor;?>>
    <INPUT TYPE="checkbox" NAME="show_permalink" VALUE=1
           <?php echo $showPermalinkCheckedState;?> > Show Permanent Link
    </TD>
    </TR>
<?php
        $tempColor = $bgColor;
        $bgColor = $altColor;
        $altColor = $tempColor;
        }

    
    // should we show approval widgets?
    if( $isExistingPost &&
        sb_isAdministrator() &&
        $approved == 0 ) {
?>
    <TR><TD BGCOLOR=<?php echo $bgColor;?>></TD>
    <TD BGCOLOR=<?php echo $bgColor;?>>
    <INPUT TYPE="checkbox" NAME="approve" VALUE=1> Approve Post
    </TD>
    </TR>
<?php
        $tempColor = $bgColor;
        $bgColor = $altColor;
        $altColor = $tempColor;
        }

    // should show removal widgets?
    if( $isExistingPost &&
        sb_canEdit( $inPostID ) ) {
?>
    <TR><TD BGCOLOR=<?php echo $bgColor;?>></TD>
    <TD BGCOLOR=<?php echo $bgColor;?>>
    <INPUT TYPE="checkbox" NAME="remove" VALUE=1> Remove Post
    </TD>
    </TR>
<?php
        }
?>
    </TABLE>
    </TD></TR>
    <TR><TD COLSPAN=2 ALIGN=RIGHT BGCOLOR=#FFFFFF>
    <INPUT TYPE="Submit" VALUE="<?php echo $buttonName;?>">
    </TD></TR>
    </TABLE>
<?php
    if( preg_match( "/_comments/", $blog_name ) ){
        // comments never expire
        echo '<INPUT TYPE="hidden" NAME="never_expires" VALUE=1>';
        }
?>
         
    </FORM> 

<?php
    // end the page with our footer
    include_once( $footer );

         
    }




/**
 * Updates a post from values submitted through editor form.
 *
 * @param $inBlogName the name of the blog to edit.
 * @param $inPostID the post to update, or NULL to
 *   insert a new post.
 */
function sb_updatePost( $inBlogName, $inPostID ) {
    global $return_url, $tableNamePrefix, $loggedInID;
    
    $author_name = sb_getRequestVariableSafe( "author_name" );
    $subject_line = sb_getRequestVariableSafe( "subject_line" );

    $return_url = sb_getRequestVariableSafe( "return_url" );
    
    // will encode illegal HTML tags whenver we display the post
    $intro_text = sb_getRequestVariableRaw( "intro_text" );
    $body_text = sb_getRequestVariableRaw( "body_text" );

    $expire_month = sb_getRequestVariableSafe( "expire_month" );
    $expire_day = sb_getRequestVariableSafe( "expire_day" );
    $expire_year = sb_getRequestVariableSafe( "expire_year" );
    $expire_hour = sb_getRequestVariableSafe( "expire_hour" );
    $expire_minute = sb_getRequestVariableSafe( "expire_minute" );
    $expire_ampm = sb_getRequestVariableSafe( "expire_ampm" );
    $never_expires = sb_getRequestVariableSafe( "never_expires" );

    // optional fields
    $allow_comments = 0;
    $show_permalink = 0;
    $approve = 0;
    $remove = 0;
    if( isset( $_REQUEST[ "allow_comments" ] ) ) {
        $allow_comments = sb_getRequestVariableSafe( "allow_comments" );
        }
    if( isset( $_REQUEST[ "show_permalink" ] ) ) {
        $show_permalink = sb_getRequestVariableSafe( "show_permalink" );
        }
    if( isset( $_REQUEST[ "approve" ] ) ) {
        $approve = sb_getRequestVariableSafe( "approve" );
        }
    if( isset( $_REQUEST[ "remove" ] ) ) {
        $remove = sb_getRequestVariableSafe( "remove" );
        }

    if( preg_match( "/_comments/", $inBlogName ) ) {
        // this is a comment post
        
        // comments never allowed on comments
        // permalinks always forced visible on comments

        // make this check here (instead of on editor end) to prevent
        // users from doctoring POST variables

        $allow_comments = 0;
        $show_permalink = 1;
        }

    
    $expiration_date = "NULL";
    
    if( $never_expires != 1 ) {
        // convert to date stamp
        $expiration_date = sb_formatTime( $expire_year,
                                       $expire_month,
                                       $expire_day,
                                       $expire_hour,
                                       $expire_minute,
                                       0,   // ignore seconds
                                       $expire_ampm );
        $expiration_date = "'$expiration_date'";
        }

    
    
    $post_id = $inPostID;

    $query = "";
    
    /*
      "CREATE TABLE posts(" .
            "post_id VARCHAR(255) NOT NULL PRIMARY KEY," .
            "blog_name VARCHAR(255) NOT NULL," .
            "user_id VARCHAR(20) NOT NULL," .
            "creation_date DATETIME NOT NULL," .
            "change_date DATETIME NOT NULL," .
            "expiration_date DATETIME," .
            "allow_comments TINYINT NOT NULL," .
            "show_permalink TINYINT NOT NULL," .
            "approved TINYINT NOT NULL," . 
            "removed TINYINT NOT NULL," . 
            "subject_line VARCHAR(60) NOT NULL," .
            "intro_text LONGTEXT," .
            "body_text LONGTEXT );";
    */
    global $header, $footer, $allowSubmissionsFromPublic;
    
    $postAllowed = true;
    $postApproved = 0;

    $editingExisting = false;
    
    if( $post_id == NULL ) {
        if( strcmp( $loggedInID, "" ) == 0 &&
            ! $allowSubmissionsFromPublic &&
            ! preg_match( "/_comments/", $inBlogName ) ) {
            // no one logged in, public posting forbidden, and this is not
            // a comment list
            $postAllowed = false;

            // display failure page
            sb_messagePage( "You must log in to submit posts." );
            }
        else if( strcmp( $author_name, "" ) != 0 &&
                 sb_doesUserExist( $author_name ) ) {
            // a public (not logged in) user has specified an
            // existing user's ID as their author name

            $postAllowed = false;
            sb_messagePage( "The name <B>$author_name</B> is already in".
                         " use by a registered user." );
            }
        else {
            $post_id = sb_getUniquePostID();

            global $autoApprovePosts, $loggedInID;

            if( strcmp( $loggedInID, "" ) != 0 ) {
                if( sb_getUserDatabaseField( $loggedInID,
                                          "administrator" ) == 1 ) {
                    // admin posts auto-approved
                    $postApproved = 1;
                    }
                else {
                    if( $autoApprovePosts ) {
                        // approve all posts from logged-in users
                        $postApproved = 1;
                        }
                    }
                }

            $user_id = "Anonymous";
            
            if( strcmp( $loggedInID, "" ) != 0 ) {
                $user_id = $loggedInID;
                }
            else {
                // no one logged in, use author name if it is set
                if( strcmp( $author_name, "" ) != 0 ) {
                    $user_id = $author_name;
                    }
                }
            
            // this query is processed below, outside this if block
            $query = "INSERT INTO $tableNamePrefix"."posts VALUES ( " .
                "'$post_id', '$inBlogName', '$user_id', CURRENT_TIMESTAMP, " .
                "CURRENT_TIMESTAMP, $expiration_date, '$allow_comments',".
                "'$show_permalink', '$postApproved', ".
                "\"0\", '$subject_line', " .
                "'$intro_text', '$body_text' );";

            
            // update the map
            // lock to ensure our update is atomic
            $mapQuery = "SELECT map FROM $tableNamePrefix"."order_map ".
                "WHERE blog_name = '$inBlogName' LOCK IN SHARE MODE;";

            sb_connectToDatabase();

            $result = sb_queryDatabase( $mapQuery );

            if( mysql_numrows( $result ) == 1 ) {
                $map = mysql_result( $result, 0, 0 );
                
                // stick this post at the top of the list
                $map = $post_id . "\n" . $map;

                $mapQuery = "UPDATE $tableNamePrefix"."order_map SET ".
                    "map = '$map' WHERE blog_name = '$inBlogName';";
                }
            else {
                // insert a new map containing only this post_id
                $mapQuery = "INSERT INTO $tableNamePrefix"."order_map ".
                    "VALUES ( " .
                    "'$inBlogName', '$post_id' );"; 
                }
            sb_queryDatabase( $mapQuery );

            sb_closeDatabase();
            }
        }
    else {
        // editing an existing post
        $editingExisting = true;
        
        if( !sb_canEdit( $post_id ) ) {
            $postAllowed = false;

            // display failure page
            sb_messagePage( "You are not allowed to edit this post." );
            }
        else {
            // deal with approval and removal
            $removedDataString = "removed = \"0\",";
            if( $remove == 1 ) {
                $removedDataString = "removed = \"1\",";
                }
            // default to not changing approval status
            $approvedDataString = "";
            if( $approve == 1 &&
                sb_isAdministrator() ) {
                $approvedDataString = "approved = \"1\",";
                }
        
            $query = "UPDATE $tableNamePrefix"."posts SET " .
                "change_date = CURRENT_TIMESTAMP, " .
                "expiration_date = $expiration_date, " .
                "allow_comments = '$allow_comments', ".
                "show_permalink = '$show_permalink', ".
                "$removedDataString " .
                "$approvedDataString " .
                "subject_line = '$subject_line', " .
                "intro_text = '$intro_text', body_text = '$body_text' " .
                "WHERE post_id = '$post_id';";
            }
        }
        
    if( $postAllowed ) {
        sb_connectToDatabase();
        
        sb_queryDatabase( $query );
        
        sb_closeDatabase();

        if( $remove != 1 ) {
            if( $postApproved == 1 ||
                sb_isAdministrator() ||
                $editingExisting ) {

                // display the updated post

                // redirect
                header( "Location: $return_url" );
                }
            else {
                // let the user know the post has been submitted
                sb_messagePage( "The post has been submitted for approval." );

                
                global $fullSeedBlogsURL, $emailAdminsAboutPendingItems;

                if( $emailAdminsAboutPendingItems ) {
                    sb_sendAdminNotice(
                        "A new post is waiting for approval:\n\n".
                        "After you log in, check the following link for ".
                        "details:\n".
                        "$fullSeedBlogsURL?action=show_post_queue".
                        "&blog_name=$inBlogName" );
                    }
                }
            }
        else {
            // let the user know the post was removed
            sb_messagePage( "The post has been removed." );
            }
        }
    }



/**
 * Moves a post up in the order map.
 *
 * @param $inBlogName the name of the blog.
 * @param $inPostID the post to move up.
 */
function sb_moveUp( $inBlogName, $inPostID ) {
    sb_movePost( $inBlogName, $inPostID, -1 );
    }



/**
 * Moves a post down in the order map.
 *
 * @param $inBlogName the name of the blog.
 * @param $inPostID the post to move down.
 */
function sb_moveDown( $inBlogName, $inPostID ) {
    sb_movePost( $inBlogName, $inPostID, 1 );
    }



/**
 * Moves a post in the order map.
 *
 * @param $inBlogName the name of the blog.
 * @param $inPostID the post to move.
 * @param $inMoveDirection -1 for up, or 1 for down.
 */
function sb_movePost( $inBlogName, $inPostID, $inMoveDirection ) {
    global $return_url, $tableNamePrefix, $loggedInID;
    // update the map
    // lock to ensure our update is atomic
    $mapQuery = "SELECT map FROM $tableNamePrefix"."order_map ".
        "WHERE blog_name = '$inBlogName' LOCK IN SHARE MODE;";

    sb_connectToDatabase();
    
    $result = sb_queryDatabase( $mapQuery );

    if( mysql_numrows( $result ) == 1 ) {
        $map = mysql_result( $result, 0, 0 );


        $mapArray = preg_split( "/\s+/", $map );
        // move this post up in the list, skipping over expired, removed,
        // or unapproved posts

        // first, find the index of our post
        $postIndex = -1;

        for( $i=0; $i<count( $mapArray ) && $postIndex == -1; $i++ ) {
            if( strcmp( $mapArray[$i], $inPostID ) == 0 ) {
                $postIndex = $i;
                }
            }

        if( $postIndex == -1 ) {
            sb_fatalError( "Could not find post $inPostID in $inBlogName " .
                        "order map." );
            }
        
        
        // move post, ignoring invisible posts, until it passes
        // one visible post
        $limit = 0;
        if( $inMoveDirection == 1 ) {
            // moving down
            $limit = count( $mapArray ) - 1;
            }
        
        $doneMoving = false;

        if( $postIndex == $limit ) {
            $doneMoving = true;
            }

        $passedOneVisible = false;
        
        while( ! $doneMoving ) {
            
            $nextHigherID = $mapArray[ $postIndex + $inMoveDirection ];

            $nextVisible = sb_isPostVisible( $nextHigherID ); 

            // move post up one spot
            $mapArray[ $postIndex ] = $nextHigherID;
            $mapArray[ $postIndex + $inMoveDirection ] = $inPostID;
            
            $postIndex += $inMoveDirection;
            
            if( $nextVisible || $postIndex == $limit ) {
                // we've passed at least one visible, or we've hit the limit
                $doneMoving = true;
                }
            }
        $map = implode( $mapArray, "\n" );
        
        $mapQuery = "UPDATE $tableNamePrefix"."order_map SET ".
            "map = '$map' WHERE blog_name = '$inBlogName';";
        }
    else {
        // insert a new map containing only this post_id
        $mapQuery = "INSERT INTO $tableNamePrefix"."order_map ".
            "VALUES ( " .
            "'$inBlogName', '$post_id' );"; 
        }
    sb_queryDatabase( $mapQuery );
    
    sb_closeDatabase();

    // redirect to return URL
    header( "Location: $return_url" );
    }




/**
 * Displays a full post.
 *
 * @param $inPostID the postID to display.
 */
function sb_displayPost( $inPostID ) {
    global $tableNamePrefix;

    $show_author = sb_getRequestVariableSafe( "show_author" );
    $show_date = sb_getRequestVariableSafe( "show_date" );

    global $return_url;

    //if( $return_url == NULL ) {
        // the display page should be the return destination after edits
        $return_url = sb_getReturnURL();
        $return_url = urlencode( $return_url );
        //    }
    
    $query =
        "SELECT * " .
        "FROM $tableNamePrefix"."posts " .
        "WHERE post_id = '$inPostID';";

    sb_connectToDatabase();
    
    $result = sb_queryDatabase( $query );
    
    if( mysql_numrows( $result ) != 1 ) {
        sb_closeDatabase();
        sb_fatalError( "Post $inPostID does not exist in database." );
        }

    $row = mysql_fetch_array( $result, MYSQL_ASSOC );

    $subject_line = $row[ "subject_line" ];
    $intro_text = $row[ "intro_text" ];
    $body_text = $row[ "body_text" ];
    $blog_name = $row[ "blog_name" ];

    $allow_comments = $row[ "allow_comments" ];
    $show_permalink = $row[ "show_permalink" ];
    $approved = $row[ "approved" ];

    $user_id = $row[ "user_id" ];
    $date = $row[ "creation_date" ];
        
    sb_closeDatabase();
    
    // trim leading/trailing whitespace
    $subject_line = trim( $subject_line );
    $intro_text = trim( $intro_text );
    $body_text = trim( $body_text );


    global $header, $footer;
    include_once( $header );

    
    global $storyBlockFormatOpen, $storyBlockFormatClose,
        $headlineFormatOpen, $headlineFormatClose,
        $textBlockFormatOpen, $textBlockFormatClose;

    if( ! $show_author ) { 
        $user_id = NULL;
        }
    if( ! $show_date ) {
        $date = NULL;
        }

    $showLinkToComments = 0;

    $commentCount = sb_countComments( $inPostID, 1 );

    if( $allow_comments && $commentCount == 0 ) {

        // show link to comments, which will be a "submit comment" link
        $showLinkToComments = 1;
        }

    
    sb_generateStoryBlock( $blog_name,
                        $inPostID,
                        $subject_line,
                        $user_id,
                        $date,
                        // hide up and down widgets
                        0,
                        0,
                        $intro_text,
                        $body_text,
                        1,  // embed body text
                        $showLinkToComments,
                        $show_permalink,
                        $return_url,
                        // formatting options:
                        $storyBlockFormatOpen,
                        $storyBlockFormatClose,
                        $headlineFormatOpen,
                        $headlineFormatClose,
                        $textBlockFormatOpen,
                        $textBlockFormatClose );


    
    if( $allow_comments ) {
        if( $commentCount > 0 ) {
            echo "<TABLE WIDTH=100% CELLSPACING=0 CELLPADDING=0 BORDER=0>";
            echo "<TR><TD ALIGN=LEFT COLSPAN=2>";
            echo "<A NAME=\"comments\">";
            sb_showComments( $inPostID );
            echo "</TD></TR></TABLE>";
            }
        }

    //echo "</TD></TR></TABLE>";

    echo "<BR>";
    
    include_once( $footer );
    }



/**
 * Displays an archive for a blog using posted values to specify the range
 * of posts to list.
 *
 * @param $inBlogName the name of the blog to show an archive for.
 */
function sb_showArchive( $inBlogName ) {

    $offset = sb_getRequestVariableSafe( "offset" );
    $count = sb_getRequestVariableSafe( "count" );
    $order = sb_getRequestVariableSafe( "order" );
    $show_intro = sb_getRequestVariableSafe( "show_intro" );
    $show_authors = sb_getRequestVariableSafe( "show_authors" );
    $show_dates = sb_getRequestVariableSafe( "show_dates" );
    $show_submit_link_to_public =
        sb_getRequestVariableSafe( "show_submit_link_to_public" );
    
    // this archive page should be the return destination after edits
    global $return_url;
    $return_url = sb_getReturnURL();
    $return_url = urlencode( $return_url );

    // now simply display a seedBlog with the appropriate offset

    global $header, $footer;

    include( $header );

    echo "<TABLE BORDER=0 WIDTH=100%><TR><TD>";
    seedBlog( $inBlogName,
              $show_intro,
              $show_authors,
              $show_dates,
              $order,
              $count,
              $offset,
              1, // show the archive
              $show_submit_link_to_public );
    echo "</TD></TR></TABLE>";
    include( $footer );
    }



/**
 * Approves a post that is waiting in the admin queue.
 *
 * @param $inPostID the post to update, or NULL to
 *   insert a new post.
 */
function sb_approvePost( $inPostID ) {
    global $return_url, $tableNamePrefix;


    $post_id = $inPostID;

    $query = "";

    global $header, $footer;

    $approvalAllowed = true;
    
    if( $post_id == NULL ) {

        $approvalAllowed = false;
        
        // display failure page
        sb_messagePage( "No post_id field given." );
        }
    else {

        if( !sb_isAdministrator() ) {

            $approvalAllowed = false;

            // display failure page
            sb_messagePage( "You must be an administrator to approve posts." );
            }
        else {
            $query = "UPDATE $tableNamePrefix"."posts SET " .
                "approved = '1' " .
                "WHERE post_id = '$post_id';";
            }
        }
        
    if( $approvalAllowed ) {
        sb_connectToDatabase();
        
        sb_queryDatabase( $query );
        
        sb_closeDatabase();
        
        // redirect to return URL
        header( "Location: $return_url" );
        }
    }



/**
 * Approves an account that is waiting in the admin queue according to POSTed
 * values
 */
function sb_approveAccount() {
    global $return_url, $tableNamePrefix;

    $user_id = sb_getRequestVariableSafe( "user_id" );
    $admin = sb_getRequestVariableSafe( "admin" );


    $query = "";

    global $header, $footer;

    $approvalAllowed = true;
    
    if( $user_id == NULL ) {

        $approvalAllowed = false;
        
        // display failure page
        sb_messagePage( "No user_id field given." );
        }
    else {

        if( !sb_isAdministrator() ) {

            $approvalAllowed = false;

            // display failure page
            sb_messagePage( "You must be an administrator to approve accounts." );
            }
        else {
            $adminClause = "";

            if( $admin == 1 ) {
                $adminClause = ", administrator = '1' ";
                }
            
            $query = "UPDATE $tableNamePrefix" . "users SET " .
                "approved = '1' $adminClause" .
                "WHERE user_id = '$user_id';";
            }
        }
        
    if( $approvalAllowed ) {
        sb_connectToDatabase();
        
        sb_queryDatabase( $query );
        
        sb_closeDatabase();

        // send an email indicating approval
        global $siteName, $mainSiteURL, $siteEmailAddress;

        $email = sb_getUserDatabaseField( $user_id, "email" );
        
        $adminMessage = "";
        if( $admin ) {
            $adminMessage = "You have been designated as an administrator.";
            }
        
        $mailHeaders = "From: $siteEmailAddress";
        $result = mail( $email, "$siteName account approved",
                        "Your account request at $mainSiteURL has been ".
                        "approved.\n\n".
                        "$adminMessage\n",
                        $mailHeaders );
        
        // redirect to return URL
        header( "Location: $return_url" );
        }
    }



/**
 * Changes the admin status of an account according to POSTed values.
 */
function sb_changeAdminStatus() {
    global $return_url, $tableNamePrefix;

    $user_id = sb_getRequestVariableSafe( "user_id" );
    $admin = sb_getRequestVariableSafe( "admin" );


    $query = "";

    $approvalAllowed = true;
    
    if( $user_id == NULL || $admin == NULL ) {
        
        // display failure page
        sb_messagePage( "Required fields are missing." );
        }
    else {

        if( !sb_isAdministrator() ) {

            $approvalAllowed = false;

            // display failure page
            sb_messagePage( "You must be an administrator to change accounts." );
            }
        else {
            $adminClause = "administrator = '0'";

            if( $admin == 1 ) {
                $adminClause = "administrator = '1'";
                }

            $query = "UPDATE $tableNamePrefix" . "users SET" .
                " $adminClause " .
                "WHERE user_id = '$user_id';";
            }
        }
        
    if( $approvalAllowed ) {
        sb_connectToDatabase();
        
        sb_queryDatabase( $query );
        
        sb_closeDatabase();

        // send an email indicating the change
        global $siteName, $mainSiteURL, $siteEmailAddress;

        $email = sb_getUserDatabaseField( $user_id, "email" );
        
        $adminMessage = "";
        if( $admin ) {
            $adminMessage = "You have been designated as an administrator.";
            }
        else {
            $adminMessage = "Your administrator status has been revoked.";
            }
        
        $mailHeaders = "From: $siteEmailAddress";
        $result = mail( $email, "$siteName account changed",
                        "Your request at $mainSiteURL has been ".
                        "changed.\n\n".
                        "$adminMessage\n",
                        $mailHeaders );
        
        // redirect to return URL
        header( "Location: $return_url" );
        }
    }



/**
 * Removes an account according to POSTed values.
 */
function sb_removeAccount() {
    global $return_url, $tableNamePrefix;

    $user_id = sb_getRequestVariableSafe( "user_id" );
    

    $query = 0;

    global $header, $footer;

    $removalAllowed = true;
    
    if( $user_id == NULL ) {

        $removalAllowed = false;
        
        // display failure page
        sb_messagePage( "No user_id field given." );
        }
    else {

        if( !sb_isAdministrator() ) {

            $removalAllowed = false;

            // display failure page
            sb_messagePage( "You must be an administrator to remove accounts." );
            }
        else {            
            $query = "DELETE FROM $tableNamePrefix" . "users " .
                "WHERE user_id = '$user_id';";
            }
        }
        
    if( $removalAllowed ) {
        sb_connectToDatabase();
        
        sb_queryDatabase( $query );
        
        sb_closeDatabase();
        
        // redirect to return URL
        header( "Location: $return_url" );
        }
    }



/**
 * Displays the admin queue for a given blog.
 *
 * @param $inBlogName the name of the blog to show a queue for, or "*" to
 *   show queue for all blogs together.
 */
function sb_showPostQueue( $inBlogName ) {
    global $header, $footer, $tableNamePrefix;

    if( ! sb_isAdministrator() ) {
        sb_messagePage( "You must be an administrator to view the queue." );
        return;
        }


    $displayBlogName = "";
    $blogNameQueryLine = "";
    if( strcmp( $inBlogName, "*" ) != 0 ) {
        $displayBlogName = "from <EM>$inBlogName</EM> ";
        $blogNameQueryLine = "AND blog_name = '$inBlogName' ";
        }
    
    include( $header );
    echo "<BR><TABLE BORDER=0 CELLSPACING=0 CELLPADDING=5><TR><TD COLSPAN=4>";
    echo "<FONT SIZE=4>Posts $displayBlogName".
        "waiting for approval:</FONT></TD></TR>";
    
    // get pending blog posts from the database

    $orderClause = "ORDER BY creation_date DESC";
    
    
    $query =
        "SELECT * " .
        "FROM $tableNamePrefix"."posts " .
        "WHERE approved = '0' AND removed = '0' ".
        "$blogNameQueryLine".
        "AND ( expiration_date > CURRENT_TIMESTAMP OR " .
              "expiration_date IS NULL ) " .
        "$orderClause;";

    sb_connectToDatabase();
    
    $result = sb_queryDatabase( $query );
    
        
    $numRows = mysql_numrows( $result );

    global $currentColor, $altColor;

    /**
     * Resets the value of the bg colors.
     */
    function sb_resetBGColors() {
        global $currentColor, $altColor;
        
        $currentColor = "#CCCCCC";
        $altColor = "#EEEEEE";
        }

    
    /**
     * Prints and alternating BGCOLOR attribute.
     */
    function sb_printNextBGColor() {
        global $currentColor, $altColor;

        echo "BGCOLOR=$currentColor";
        $tempColor = $currentColor;
        $currentColor = $altColor;
        $altColor = $tempColor;
        }

    
    if( $numRows == 0 ) {
        echo "<TR><TD>[none]</TD></TR>";
        }
    else {
        // table headers
        echo "<TR><TD><B>Context:</B></TD>";
        echo "<TD><B>Author:</B></TD>";
        echo "<TD><B>Headline:</B></TD>";
        echo "<TD></TD></TR>";
        }

    // this queue should be the return destination after edits
    $return_url = sb_getReturnURL();
    $return_url = urlencode( $return_url );
    
    for( $i=0; $i<$numRows; $i++ ) {
        // restart color cycling
        sb_resetBGColors();
        
        $blog_name = mysql_result( $result, $i, "blog_name" );
        $subject_line = mysql_result( $result, $i, "subject_line" );
        $post_id = mysql_result( $result, $i, "post_id" );
        $author = mysql_result( $result, $i, "user_id" );

        $context = $blog_name;

        if( preg_match( "/_comments/", $blog_name ) ) {

            preg_match( "/(.*)_comments/", $blog_name, $matches );

            // matches[0] contains full matched string
            // matches[1] contains first parenthesized subpattern
            $parentPostID = $matches[1];


            // fetch subject line of parent post
            $query =
                "SELECT * " .
                "FROM $tableNamePrefix"."posts " .
                "WHERE post_id = '$parentPostID';";
            
            $contextResult = sb_queryDatabase( $query );

            $context_subject_line =
                mysql_result( $contextResult, 0, "subject_line" );

            $context = "Comment to <A HREF=\"".
                "seedBlogs.php?action=display_post" .
                "&show_author=1&show_date=1".
                "&post_id=$post_id&return_url=$return_url\">".
                "$context_subject_line</A>";
            }

        echo "<TR><TD "; sb_printNextBGColor(); echo ">$context</TD>";
        echo "<TD "; sb_printNextBGColor(); echo ">$author</TD>";
        echo "<TD "; sb_printNextBGColor(); echo ">$subject_line</TD>";
        
        echo "<TD NOWRAP "; sb_printNextBGColor();
        echo ">[<A HREF=\"seedBlogs.php?action=display_post" .
            "&show_author=1&show_date=1".
            "&post_id=$post_id&return_url=$return_url\">View</A>]";

        echo " - [<A HREF=\"seedBlogs.php?action=edit_post" .
            "&post_id=$post_id&return_url=$return_url\">".
            "Edit</A>]";

        echo " - [<A HREF=\"seedBlogs.php?action=approve_post" .
            "&post_id=$post_id&return_url=$return_url\">".
            "Approve</A>]</TD></TR>";

        // blank space
        echo "<TR><TD COLSPAN=3 ALIGN=CENTER></TD></TR>";
        }

    echo "</TD></TR></TABLE><BR><BR>";
    sb_closeDatabase();
    
    include( $footer );
    }



/**
 * Displays the admin queue of pending account requests.
 */
function sb_showAccountQueue() {
    global $header, $footer, $tableNamePrefix;

    if( ! sb_isAdministrator() ) {
        sb_messagePage( "You must be an administrator to view the queue." );
        return;
        }

    
    include( $header );
    echo "<BR><TABLE BORDER=0 CELLSPACING=0 CELLPADDING=5><TR><TD COLSPAN=3>";
    echo "<FONT SIZE=4>Account requests ".
        "waiting for approval:</FONT></TD></TR>";
    
    // get pending accounts from the database
    
    $query =
        "SELECT * " .
        "FROM $tableNamePrefix"."users " .
        "WHERE approved = '0';";

    sb_connectToDatabase();
    
    $result = sb_queryDatabase( $query );
    
    sb_closeDatabase();
    
    $numRows = mysql_numrows( $result );

    global $currentColor, $altColor;

    /**
     * Resets the value of the bg colors.
     */
    function sb_resetBGColors() {
        global $currentColor, $altColor;
        
        $currentColor = "#CCCCCC";
        $altColor = "#EEEEEE";
        }

    
    /**
     * Prints and alternating BGCOLOR attribute.
     */
    function sb_printNextBGColor() {
        global $currentColor, $altColor;

        echo "BGCOLOR=$currentColor";
        $tempColor = $currentColor;
        $currentColor = $altColor;
        $altColor = $tempColor;
        }

    
    if( $numRows == 0 ) {
        echo "<TR><TD>[none]</TD></TR>";
        }
    else {
        // table headers
        echo "<TR><TD><B>User ID:</B></TD>";
        echo "<TD><B>Email:</B></TD>";
        echo "<TD></TD></TR>";
        }

    // this queue should be the return destination after edits
    $return_url = sb_getReturnURL();
    $return_url = urlencode( $return_url );
    
    for( $i=0; $i<$numRows; $i++ ) {
        // restart color cycling
        sb_resetBGColors();
        
        $user_id = mysql_result( $result, $i, "user_id" );
        $email = mysql_result( $result, $i, "email" );
        

        echo "<TR><TD "; sb_printNextBGColor(); echo ">$user_id</TD>";
        echo "<TD "; sb_printNextBGColor(); echo ">$email</TD>";
        
        echo "<TD NOWRAP "; sb_printNextBGColor();
        echo ">[<A HREF=\"seedBlogs.php?action=remove_account" .
            "&user_id=$user_id&return_url=$return_url\">reject</A>]";

        echo " - [<A HREF=\"seedBlogs.php?action=approve_account" .
            "&user_id=$user_id&admin=0&return_url=$return_url\">".
            "approve</A>]";

        echo " - [<A HREF=\"seedBlogs.php?action=approve_account" .
            "&user_id=$user_id&admin=1&return_url=$return_url\">".
            "approve and make admin</A>]</TD></TR>";

        // blank space
        echo "<TR><TD COLSPAN=3 ALIGN=CENTER></TD></TR>";
        }

    echo "</TD></TR></TABLE><BR><BR>";
    
    include( $footer );
    }



/**
 * Displays the admin list of all approved accounts in the system.
 */
function sb_showAccountList() {
    global $header, $footer, $tableNamePrefix;

    if( ! sb_isAdministrator() ) {
        sb_messagePage( "You must be an administrator to ".
                     "view the account list." );
        return;
        }

    
    include( $header );
    echo "<BR><TABLE BORDER=0 CELLSPACING=0 CELLPADDING=5><TR><TD COLSPAN=4>";
    echo "<FONT SIZE=4>Active Accounts:</FONT></TD></TR>";
    
    // get pending accounts from the database
    
    $query =
        "SELECT * " .
        "FROM $tableNamePrefix"."users " .
        "WHERE approved = '1' ".
        "ORDER BY user_id ASC;";

    sb_connectToDatabase();
    
    $result = sb_queryDatabase( $query );
    
    sb_closeDatabase();
    
    $numRows = mysql_numrows( $result );

    global $currentColor, $altColor;

    /**
     * Resets the value of the bg colors.
     */
    function sb_resetBGColors() {
        global $currentColor, $altColor;
        
        $currentColor = "#CCCCCC";
        $altColor = "#EEEEEE";
        }

    
    /**
     * Prints and alternating BGCOLOR attribute.
     */
    function sb_printNextBGColor() {
        global $currentColor, $altColor;

        echo "BGCOLOR=$currentColor";
        $tempColor = $currentColor;
        $currentColor = $altColor;
        $altColor = $tempColor;
        }

    
    if( $numRows == 0 ) {
        echo "<TR><TD>[none]</TD></TR>";
        }
    else {
        // table headers
        echo "<TR><TD><B>User ID:</B></TD>";
        echo "<TD><B>Email:</B></TD>";
        echo "<TD><B>Status:</B></TD>";
        echo "<TD></TD></TR>";
        }

    // this queue should be the return destination after edits
    $return_url = sb_getReturnURL();
    $return_url = urlencode( $return_url );
    
    for( $i=0; $i<$numRows; $i++ ) {
        // restart color cycling
        sb_resetBGColors();
        
        $user_id = mysql_result( $result, $i, "user_id" );
        $email = mysql_result( $result, $i, "email" );
        $administrator = mysql_result( $result, $i, "administrator" );
        

        echo "<TR><TD "; sb_printNextBGColor(); echo ">$user_id</TD>";
        echo "<TD "; sb_printNextBGColor(); echo ">$email</TD>";

        if( $administrator ) {
            echo "<TD "; sb_printNextBGColor(); echo ">admin</TD>";
            }
        else {
            echo "<TD "; sb_printNextBGColor(); echo "></TD>";
            }
        
        echo "<TD NOWRAP "; sb_printNextBGColor();
        echo ">[<A HREF=\"seedBlogs.php?action=remove_account" .
            "&user_id=$user_id&return_url=$return_url\">remove</A>]";

        if( $administrator == 1 ) {
            echo " - [<A HREF=\"seedBlogs.php?action=change_admin_status" .
                "&user_id=$user_id&admin=0&return_url=$return_url\">".
                "revoke admin status</A>]";
            }
        else {
            echo " - [<A HREF=\"seedBlogs.php?action=change_admin_status" .
                "&user_id=$user_id&admin=1&return_url=$return_url\">".
                "make admin</A>]";
            }

        // blank space
        echo "<TR><TD COLSPAN=4 ALIGN=CENTER></TD></TR>";
        }

    echo "</TD></TR></TABLE><BR><BR>";
    
    include( $footer );
    }



/**
 * Performs search using posted variables and displays a results page.
 */
function sb_search() {
    global $tableNamePrefix;
    
    $key_words = sb_getRequestVariableSafe( "key_words" );
    
    // this result page should be the return destination after edits
    $return_url = sb_getReturnURL();
    $return_url = urlencode( $return_url );

    
    $keywordArray = explode( " ", $key_words );
    $keywordWhereClause = "";

    foreach( $keywordArray as $name => $word ) {
        $keywordWhereClause = $keywordWhereClause .
            "AND ( subject_line LIKE '%$word%' " .
                  "OR intro_text LIKE '%$word%' ".
                  "OR body_text LIKE '%$word%' )";
        }

    $query =
        "SELECT * " .
        "FROM $tableNamePrefix"."posts " .
        "WHERE approved = '1' AND removed = '0' ".
        "AND ( expiration_date > CURRENT_TIMESTAMP OR " .
              "expiration_date IS NULL ) " .
        "$keywordWhereClause;";

    sb_connectToDatabase();
    
    $result = sb_queryDatabase( $query );
    
    sb_closeDatabase();
    
    $numRows = mysql_numrows( $result );

    global $header, $footer;

    include( $header );

    echo "<TABLE BORDER=0 WIDTH=100%><TR><TD>";


    echo "<FONT SIZE=5>Search for <EM>$key_words</EM>:</FONT><BR><BR>";
    
    if( $numRows == 0 ) {
        echo "[no results]<BR>";
        }

    global $storyBlockFormatOpen, $storyBlockFormatClose,
        $headlineFormatOpen, $headlineFormatClose,
        $textBlockFormatOpen, $textBlockFormatClose, $storySeparator;

    
    for( $i=0; $i<$numRows; $i++ ) {

        $post_id = mysql_result( $result, $i, "post_id" );
        $blog_name = mysql_result( $result, $i, "blog_name" );
        $user_id = mysql_result( $result, $i, "user_id" );
        $subject_line = mysql_result( $result, $i, "subject_line" );
        $intro_text = mysql_result( $result, $i, "intro_text" );
        $body_text = mysql_result( $result, $i, "body_text" );
        $creation_date = mysql_result( $result, $i, "creation_date" );
        $allow_comments = mysql_result( $result, $i, "allow_comments" );
        $show_permalink = mysql_result( $result, $i, "show_permalink" );

        sb_generateStoryBlock( $blog_name,
                            $post_id,
                            trim( $subject_line ),
                            $user_id,
                            $creation_date,
                            // hide up and down widgets
                            0,
                            0,
                            trim( $intro_text ),
                            trim( $body_text ),
                            0,  // show link to body text
                            $allow_comments,
                            $show_permalink,
                            $return_url,
                            // formatting options:
                            $storyBlockFormatOpen,
                            $storyBlockFormatClose,
                            $headlineFormatOpen,
                            $headlineFormatClose,
                            $textBlockFormatOpen,
                            $textBlockFormatClose );

        if( $i < $numRows - 1 ) {
            // separate from next story
            echo "$storySeparator";
            }
        }

    echo "</TD></TR></TABLE>";
    include( $footer );    
    }



/**
 * Generates HTML for a story block with intro text visible.
 *
 * The point of this function is to abstract out the basic story block
 * rendering code so that seedBlogFormatted() and sb_search() can both use it.
 *
 * @param $inBlogName the name of the blog in the database.
 * @param $inPostID the post ID.
 * @param $inSubjectLine the whitespace trimmed subject line.
 * @param $inUserID the author of the post, or NULL to hide the author byline.
 * @param $inDateString the MySQL creation date string of this post, or NULL
 *   to hide the date from the display.
 * @param $inShowUpWidget 1 to show up widgets for this post, or
 *   0 to hide it.
 * @param $inShowDownWidget 1 to show down widget for this post, or
 *   0 to hide it.
 * @param $inIntroText the raw intro text from the database, whitespace
 *   trimmed.
 * @param $inBodyText the raw body text from the database, whitespace
 *   trimmed, or NULL if there is no body.
 * @param $inEmbedBodyText 1 to include the body text in the story block,
 *   or 0 to show a "read more" link.
 * @param $inAllowComments 1 to allow comments, or 0 to forbid them.
 * @param $inShowPermalink 1 to show a permanent link, or 0 to hide it.
 * @param $inReturnURL the URL of the page that this block is part of.
 *
 * Other parameters (formatting options) are identical to those passed into
 * seedBlogFormatted.
 */
function sb_generateStoryBlock( $inBlogName,
                             $inPostID,
                             $inSubjectLine,
                             $inUserID,
                             $inDateString,
                             $inShowUpWidget,
                             $inShowDownWidget,
                             $inIntroText,
                             $inBodyText,
                             $inEmbedBodyText,
                             $inAllowComments,
                             $inShowPermalink,
                             $inReturnURL,
                             // formatting options:
                             $inStoryBlockFormatOpen,
                             $inStoryBlockFormatClose,
                             $inHeadlineFormatOpen,
                             $inHeadlineFormatClose,
                             $inTextBlockFormatOpen,
                             $inTextBlockFormatClose ) {

    // open story block
    echo "$inStoryBlockFormatOpen\n";
    // formatted subject line (no link)
    echo "$inHeadlineFormatOpen$inSubjectLine$inHeadlineFormatClose\n";
    echo "$inTextBlockFormatOpen";
    $show_author = 0;
    $show_date = 0;
    
    if( $inUserID != NULL || $inDateString != NULL ) {
        echo "<TABLE WIDTH=100% CELLPADDING=0 CELLSPACING=0><TR>";
    
        if( $inUserID != NULL ) {
            echo "<TD>by $inUserID</TD>";
            $show_author = 1;
            }
        if( $inDateString != NULL ) {
            $timestamp = strtotime( $inDateString );
            // format as in    Sunday, July 7, 2005 [4:52 pm]
            $dateString = date( "l, F j, Y [g:i a]", $timestamp );

            if( $inUserID == NULL ) {
                echo "<TD>";
                }
            else {
                echo "<TD ALIGN=RIGHT>";
                }
            echo "<EM>$dateString</EM></TD>";

            $show_date = 1;
            }
        echo "</TR></TABLE>";
        }
    
    if( sb_canEdit( $inPostID ) ) {
        // Edit link next to subject
        echo "[<A HREF=\"seedBlogs.php?action=edit_post".
            "&blog_name=$inBlogName".
            "&post_id=$inPostID&return_url=$inReturnURL".
            "&show_author=$show_author&show_date=$show_date\">" .
            "Edit</A>]";

        if( sb_isAdministrator() ) {
            // show an approve link, if post is pending approval

            global $tableNamePrefix;
            
            $query =
                "SELECT * " .
                "FROM $tableNamePrefix"."posts " .
                "WHERE post_id = '$inPostID';";

            sb_connectToDatabase();
            
            $result = sb_queryDatabase( $query );
            
            sb_closeDatabase();

            $approved = mysql_result( $result, 0, "approved" );
            
            if( $approved == 0 ) {
                echo "[<A HREF=\"seedBlogs.php?action=approve_post" .
                    "&post_id=$inPostID&return_url=$inReturnURL\">".
                    "Approve</A>]";
                }
            }

        if( $inShowUpWidget ) {
            echo "[<A HREF=\"seedBlogs.php?action=move_up".
                "&blog_name=$inBlogName".
                "&post_id=$inPostID&return_url=$inReturnURL\">" .
                "Move Up</A>]";
            }
        if( $inShowDownWidget ) {
            echo "[<A HREF=\"seedBlogs.php?action=move_down".
                "&blog_name=$inBlogName".
                "&post_id=$inPostID&return_url=$inReturnURL\">" .
                "Move Down</A>]";
            }
        }
    if( $inIntroText != NULL ) {
        // intro text
        $formattedIntro = sb_rcb_blog2html( $inIntroText );
        echo "<BR>$formattedIntro";
        }
    if( $inBodyText != NULL && $inEmbedBodyText ) {
         $formattedBody = sb_rcb_blog2html( $inBodyText );
        echo "<BR><BR>$formattedBody";
        }

    // only open a table for the links if we are going to show some links
    if( $inShowPermalink ||
        ( $inBodyText != NULL && ! $inEmbedBodyText ) ||
        $inAllowComments ) {
        
        // links under text
        echo
         "<BR><BR><TABLE BORDER=0 WIDTH=100% CELLSPACING=0 CELLPADDING=0><TR>";
        if( $inBodyText != NULL && ! $inEmbedBodyText ) {
            // a read-more link
            echo "<TD ALIGN=LEFT>".
                "<A HREF=\"seedBlogs.php?action=display_post" .
                "&post_id=$inPostID".
                "&show_author=$show_author&show_date=$show_date".
                "\" TITLE=\"View the entire post\">Read more...</A></TD>";
            }
        else if( $inShowPermalink ) {
            // a perma link
            echo "<TD ALIGN=LEFT>".
                "[<A HREF=\"seedBlogs.php?action=display_post" .
                "&post_id=$inPostID".
                "&show_author=$show_author&show_date=$show_date".
                "\" TITLE=\"Permanent link for this post\">Link</A>]</TD>";
            }
        if( $inAllowComments ) {
            $approvedCount = sb_countComments( $inPostID, 1 );
            $queuedCount = sb_countComments( $inPostID, 0 );

            $isAdmin = sb_isAdministrator();

            echo "<TD ALIGN=RIGHT>";

            if( $approvedCount > 0 ||
                ( $isAdmin && $queuedCount > 0 ) ) {
                echo "[<A HREF=\"seedBlogs.php?action=display_post" .
                    "&post_id=$inPostID".
                    "&show_author=$show_author&show_date=$show_date".
                    "#comments\" TITLE=\"View and add comments\">".
                    "$approvedCount Comment";

                if( $approvedCount != 1 ) {
                    echo "s";
                    }
                echo "</A>";
                    
                if( $isAdmin && $queuedCount > 0 ) {
                    echo ", $queuedCount in <A HREF=\"seedBlogs.php?".
                        "action=show_post_queue".
                        "&blog_name=$inPostID"."_comments".
                        "&return_url=$inReturnURL\">Queue</A>";
                    }
                echo "]";
                }
            else {
                // no comments yet, but show link for submission

            
                $postLinkName = "Submit Comment";
                $allowPost = false;
    
                global $autoApprovePublicComments, $loggedInID;
            
                if( $autoApprovePublicComments ||
                    strcmp( $loggedInID, "" ) != 0 ) {

                    // post directly, don't submit
                    $postLinkName = "Post Comment";
                    }
                    
                echo "[<A HREF=\"seedBlogs.php?action=edit_post".
                    "&blog_name=$inPostID"."_comments".
                    "&return_url=$inReturnURL\" ".
                    "TITLE=\"Submit a comment into the approval queue\">" .
                    "$postLinkName</A>]";
                    
                }
            echo"</TD>";
            }
            
        echo "</TR></TABLE>";
        }
    
    // close text block
    echo "$inTextBlockFormatClose";
    
    // close story block
    echo "$inStoryBlockFormatClose";
    }



/**
 * Generates RSS 2.0 XML for a blog, using posted variables to select the
 * blog and configure the RSS feed.
 *
 * The following RSS 2.0 spec was followed:
 * http://blogs.law.harvard.edu/tech/rss
 */
function sb_rssFeed() {
    global $tableNamePrefix;
    
    $blog_name = sb_getRequestVariableSafe( "blog_name" );
    $channel_title =
        sb_stripMagicQuotes( sb_getRequestVariableSafe( "channel_title" ) );
    $channel_description =
        sb_stripMagicQuotes( sb_getRequestVariableSafe( "channel_description" ) );
    $max_number = sb_getRequestVariableSafe( "max_number" );
    $show_authors = sb_getRequestVariableSafe( "show_authors" );
    $show_dates = sb_getRequestVariableSafe( "show_dates" );
    
    
    // for now, only order by creation date in RSS feed
    $orderClause = "ORDER BY creation_date DESC";

    
    $limitNumber = $max_number;
    
    if( $max_number == -1 ) {
        // use a large number, as suggested in the MySQL docs, to cause
        // limit to be ignored
        $limitNumber = 99999;
        }
 
    
    
    // LIMIT is only supported by MySQL
    $query =
        "SELECT * " .
        "FROM $tableNamePrefix"."posts " .
        "WHERE approved = '1' AND removed = '0' ".
        "AND blog_name = '$blog_name' ".
        "AND ( expiration_date > CURRENT_TIMESTAMP OR " .
              "expiration_date IS NULL ) " .
        "$orderClause LIMIT 0, $limitNumber;";

    sb_connectToDatabase();
    
    $result = sb_queryDatabase( $query );
    
    sb_closeDatabase();
    
    $numRows = mysql_numrows( $result );

    global $mainSiteURL, $fullSeedBlogsURL;

    header( "Content-type: application/xml" );
    
    // echo this to avoid problems with <?
    echo "<?xml version=\"1.0\"?>\n";

    // now inline the rest of the XML

    // tested this with a validator, and it is valid RSS
?>

<rss version="2.0">
    <channel>
        <title><?php echo $channel_title;?></title>
        <link><?php echo $mainSiteURL;?></link>
        <description><?php echo $channel_description;?></description>
<?php

    for( $i=0; $i<$numRows; $i++ ) {
        $subject_line = "";
        $post_id = "";
        $intro_text = "";
        $date = "";

        $subject_line = mysql_result( $result, $i, "subject_line" );
        $post_id = mysql_result( $result, $i, "post_id" );
        $intro_text = mysql_result( $result, $i, "intro_text" );
        
        $date = mysql_result( $result, $i, "creation_date" );

        // trim leading/trailing whitespace
        $subject_line = trim( $subject_line );
        $intro_text = trim( $intro_text );

        // convert bbcode into HTML
        // then encode the HTML for insertion into an XML document
        $intro_text =
            htmlspecialchars( sb_rcb_blog2html( strip_tags( $intro_text ) ) );

        // & is forbidden in an XML document
        $post_url =
            $fullSeedBlogsURL .
            "?action=display_post&amp;post_id=$post_id".
            "&amp;show_author=$show_authors&amp;show_date=$show_dates";

        $timestamp = strtotime( $date );
        // format as in    Sun, 19 May 2002 15:21:36 GMT
        // format copied from RSS 2.0 spec, cited above
        // Spec points to RFC822 for date format.
        // Found this format string for RFC822 in the PHP 5.1 source code
        $formatString_RFC822 = "D, d M Y H:i:s T";
        $dateString = date( $formatString_RFC822, $timestamp );
?>        

            <item>
                 <title><?php echo $subject_line;?></title>
                 <link><?php echo $post_url;?></link>
                 <description><?php echo $intro_text?></description>
                 <pubDate><?php echo $dateString?></pubDate>
            </item>
<?php
                                           
        // end for loop over posts
        }
  
?>

    </channel>
</rss>
          
<?php
                                                               
    }



/**
 * Generates a page showing comments for a given post.
 *
 * @param $inPostID the post to show comments for.
 */
function sb_showComments( $inPostID ) {

    global $tableNamePrefix;
    
    // first, get the post's subject line from the database
    $query =
        "SELECT * " .
        "FROM $tableNamePrefix"."posts " .
        "WHERE post_id = '$inPostID';";

    sb_connectToDatabase();
    
    $result = sb_queryDatabase( $query );
    
    if( mysql_numrows( $result ) != 1 ) {
        sb_closeDatabase();
        sb_fatalError( "Post $inPostID does not exist in database." );
        }

    $row = mysql_fetch_array( $result, MYSQL_ASSOC );

    $subject_line = $row[ "subject_line" ];
        
    sb_closeDatabase();

    

    // now display the comments for this post

    // the name of the comment-holding blog in the database
    $commentBlogName = $inPostID . "_comments";

    
    global $headlineFormatOpen, $headlineFormatClose,
        $commentListOpen, $commentListClose;
    
    echo $commentListOpen;
        
    // use a seedBlog to display the comments
    // order oldest-first, so we can follow thread of discussion
    seedBlog(
        // name of this seed blog in the database
        $commentBlogName,
        // 1 = show intro text below headlines
        // 0 = show only headlines
        1,
        // 1 = show creation date for each post
        // 0 = hide dates
        1,
        // show authors
        1,
        // 2 = allow custom order tweaking with up/down widgets
        // 1 = order by creation date (newest first)
        // 0 = order by expiration date (oldest first)
        // -1 = order by creation date (oldest first)
        -1,
        // show an unlimited number of comments
        -1,
        // skip none of them (start with first post)
        0,
        // show the archive link
        0,
        // show the submission link to public
        1,
        // never allow sub-comments
        0
        );

    echo $commentListClose;    
    }


/**
 * Counts the comments associated with a given post.
 *
 * @param $inPostID the post to count comments for.
 * @param $inApproved set to 1 to count only approved comments, or
 *   0 to count comments in the approval queue.  Defaults to 1.
 *
 * @return the number of comments.
 */
function sb_countComments( $inPostID, $inApproved = 1 ) {
    global $tableNamePrefix;

    // the name of the comment-holding blog in the database
    $commentBlogName = $inPostID . "_comments";


    $query =
            "SELECT COUNT(*) FROM $tableNamePrefix"."posts ".
            "WHERE approved = \"$inApproved\" AND removed = \"0\" AND ".
            "blog_name = \"$commentBlogName\";";

    sb_connectToDatabase();
    
    $result = sb_queryDatabase( $query );
    $count = mysql_result( $result, 0, 0 );
        
    sb_closeDatabase();

    return $count;
    }



// general-purpose functions down here, many copied from NCN

/**
 * Connects to the database according to the database variables.
 */  
function sb_connectToDatabase() {
    global $databaseServer,
        $databaseUsername, $databasePassword, $databaseName;
    
    
    mysql_connect( $databaseServer, $databaseUsername, $databasePassword )
        or sb_fatalError( "Could not connect to database server: " .
                       mysql_error() );
    
	mysql_select_db( $databaseName )
        or sb_fatalError( "Could not select $databaseName database: " .
                       mysql_error() );
    }


 
/**
 * Closes the database connection.
 */
function sb_closeDatabase() {
    mysql_close();
    }



/**
 * Queries the database, and dies with an error message on failure.
 *
 * @param $inQueryString the SQL query string.
 *
 * @return a result handle that can be passed to other mysql functions.
 */
function sb_queryDatabase( $inQueryString ) {

    $result = mysql_query( $inQueryString )
        or sb_fatalError( "Database query failed:<BR>$inQueryString<BR><BR>" .
                       mysql_error() );

    return $result;
    } 


/**
 * Checks whether a table exists in the currently-connected database.
 *
 * @param $inTableName the name of the table to look for.
 *
 * @return 1 if the table exists, or 0 if not.
 */
function sb_doesTableExist( $inTableName ) {
    // check if our table exists
    $tableExists = 0;
    
    $query = "SHOW TABLES";
    $result = sb_queryDatabase( $query );

    $numRows = mysql_numrows( $result );


    for( $i=0; $i<$numRows && ! $tableExists; $i++ ) {

        $tableName = mysql_result( $result, $i, 0 );
        
        if( strcmp( $tableName, $inTableName ) == 0 ) {
            $tableExists = 1;
            }
        }
    return $tableExists;
    }



/**
 * Displays the error page and dies.
 *
 * @param $message the error message to display on the error page.
 */
function sb_fatalError( $message ) {
    //global $errorMessage;

    // set the variable that is displayed inside error.php
    //$errorMessage = $message;
    
    //include_once( "error.php" );

    // for now, just print error message
    echo( "<B>Fatal error:</B>  $message<BR>" );
    die();
    }



/**
 * Displays a message page.
 *
 * @param $message the message to display.
 */
function sb_messagePage( $message ) {
    global $header, $footer;
    include( $header );
    echo( "<BR>$message<BR><BR>" );
    include( $footer );
    }



/**
 * Prints form elements for selecting a date and time.
 *
 * @param $namePrefix the prefix to use in each form element
 *   name.  For example, if $namePrefix is "my_", then the
 *   form elements will have the following names:
 *   my_month, my_day, my_year, my_hour, my_minute, my_ampm
 *   All fields have numerical posted values, except ampm, which
 *   is either "am" or "pm".
 * @param $fillWithCurrentTime set to 1 to fill with current time.
 * @param $selected____ indicates values that should be pre-selected.
 */
function sb_printDateTimeForm( $namePrefix,
                            $fillWithCurrentTime = 0,
                            $selectedMonth = NULL, $selectedDay = NULL,
                            $selectedYear = NULL, $selectedHour = NULL,
                            $selectedMinute = NULL,
                            $selectedAMPM = NULL ) {
                                       
    if( $fillWithCurrentTime ) {
        $currentDateAndTime = getdate();

        $selectedHour = $currentDateAndTime[ "hours" ];
        $selectedAMPM = "am";

        if( $selectedHour > 11 ) {
        
            if( $selectedHour < 24 ) {
                $selectedAMPM = "pm";
                }
            $selectedHour = $selectedHour - 12;
            }

        $selectedMonth = $currentDateAndTime[ "mon" ];
        $selectedDay = $currentDateAndTime[ "mday" ];
        $selectedYear = $currentDateAndTime[ "year" ];

        $selectedMinute = $currentDateAndTime[ "minutes" ];
        }
                                           
    $months = array( "January", "February", "March", "April",
                     "May", "June", "July", "August", "September",
                     "October", "November", "December" );

    echo "<TABLE BORDER=0><TR>";
    echo "<TD>Date:</TD><TD><SELECT NAME=\"$namePrefix" . "month\">\n";
    foreach( $months as $i => $monthName ) {
        $monthNumber = $i + 1;

        $selectedState = "";
        if( $selectedMonth == $monthNumber ) {
            $selectedState = "SELECTED";
            }
        echo "<OPTION VALUE=\"$monthNumber\" " .
             "$selectedState>$monthName</OPTION>\n";
        }
    echo "</SELECT>\n";

    echo "<SELECT NAME=\"$namePrefix" . "day\">\n";
    for( $day=1; $day<=31; $day++ ) {
        $selectedState = "";
        if( $selectedDay == $day ) {
            $selectedState = "SELECTED";
            }
        echo "<OPTION $selectedState>$day</OPTION>\n";
        }
    echo "</SELECT>\n";

    echo "<SELECT NAME=\"$namePrefix" . "year\">\n";
    for( $year=2005; $year<=2015; $year++ ) {
        $selectedState = "";
        if( $selectedYear == $year ) {
            $selectedState = "SELECTED";
            }
        echo "<OPTION $selectedState>$year</OPTION>\n";
        }
    echo "</SELECT></TD></TR>\n";

           // start new line
    echo "<TR>";
           
    echo "<TD>Time:</TD><TD><SELECT NAME=\"$namePrefix" . "hour\">\n";
    for( $hour=1; $hour<=12; $hour++ ) {
        $selectedState = "";
        if( $selectedHour == $hour ) {
            $selectedState = "SELECTED";
            }
        echo "<OPTION $selectedState>$hour</OPTION>\n";
        }
    echo "</SELECT>\n";

    echo "<SELECT NAME=\"$namePrefix" . "minute\">\n";
    for( $minute=0; $minute<=9; $minute++ ) {
        $selectedState = "";
        if( $selectedMinute == $minute ) {
            $selectedState = "SELECTED";
            }
        echo "<OPTION VALUE=\"$minute\" $selectedState>" .
             "0$minute</OPTION>\n";
        }
           
    for( $minute=10; $minute<=59; $minute++ ) {
        $selectedState = "";
        if( $selectedMinute == $minute ) {
            $selectedState = "SELECTED";
            }
        echo "<OPTION $selectedState>$minute</OPTION>\n";
        }
    echo "</SELECT>\n";

           // radio for am/pm
    $amCheckedState = "";
    $pmCheckedState = "";

    if( strcmp( $selectedAMPM, "am" ) == 0 ) {
        $amCheckedState = "checked";
        }
    if( strcmp( $selectedAMPM, "pm" ) == 0 ) {
        $pmCheckedState = "checked";
        }
               
    echo "<INPUT TYPE=\"radio\" NAME=\"$namePrefix" .
         "ampm\" VALUE=\"am\" $amCheckedState>am ";
    echo "<INPUT TYPE=\"radio\" NAME=\"$namePrefix" .
         "ampm\" VALUE=\"pm\" $pmCheckedState>pm ";

    echo "</TD></TR></TABLE>";
    }



/**
 * Prints form elements for selecting a date and time, preselecting a time
 * using an SQL timestamp.
 *
 * @param $namePrefix the prefix to use in each form element
 *   name.  For example, if $namePrefix is "my_", then the
 *   form elements will have the following names:
 *   my_month, my_day, my_year, my_hour, my_minute, my_ampm
 *   All fields have numerical posted values, except ampm, which
 *   is either "am" or "pm".
 * @param $fillWithCurrentTime set to 1 to fill with current time.
 * @param $selectedTimestamp the SQL timestamp to pre-select.
 */
function sb_printDateTimeFormFromTimestamp( $namePrefix,
                                         $fillWithCurrentTime = 0,
                                         $selectedTimestamp ) {

    if( $fillWithCurrentTime ) {
        // ignore selectedTimestamp
        sb_printDateTimeForm( $namePrefix, $fillWithCurrentTime );
        }
    else {
        $unixTimeInSeconds = strtotime( $selectedTimestamp );

        // get array of separated time values
        $timeValues = getdate( $unixTimeInSeconds );

        $hours = $timeValues[ "hours" ];

        // convert to 12-hour time

        $ampm = "am";
        
        if( $hours > 11 ) {
        
            if( $hours < 24 ) {
                $ampm = "pm";
                }
            $hours = $hours - 12;
            }

        sb_printDateTimeForm( $namePrefix,
                           $fillWithCurrentTime,
                           $timeValues[ "mon" ], $timeValues[ "mday" ],
                           $timeValues[ "year" ],
                           $hours,
                           $timeValues[ "minutes" ],
                           $ampm );
        }
    }


/**
 * Formats time data as an SQL timestamp.
 * An example timestamp:   "2005-01-19 17:22:50"
 *
 * Most parameters are self-explanatory, except:
 * @param $ampm one of "am", "pm", or NULL to indicate 24-hour time.
 */
function sb_formatTime( $year, $month, $day, $hour, $minute, $second, $ampm ) {

    $formattedHour = $hour;
    if( $ampm != NULL ) {
        if( strcmp( $ampm, "pm" ) == 0) {
            $formattedHour += 12;
            }
        }
    if( $formattedHour < 10 ) {
        $formattedHour = "0$formattedHour";
        }
    
    $formattedMinute = $minute;
    if( $formattedMinute < 10 ) {
        $formattedMinute = "0$formattedMinute";
        }

    $formattedSecond = $second;
    if( $formattedSecond < 10 ) {
        $formattedSecond = "0$formattedSecond";
        }

    $formattedDay = $day;
    if( $formattedDay < 10 ) {
        $formattedDay = "0$formattedDay";
        }

    $formattedMonth = $month;
    if( $formattedMonth < 10 ) {
        $formattedMonth = "0$formattedMonth";
        }
    
    
    return "$year-$formattedMonth-$formattedDay " .
           "$formattedHour:$formattedMinute:$formattedSecond";
    }



/**
 * Gets a post ID that is guaranteed to be unique.
 *
 * A user must be logged in for this to work properly.
 * In other words, the global $loggedInID must be set.
 *
 * This function queries the database to ensure that the ID is actually
 * unique and tries IDs until a uniqe one is found
 *
 * The correctness of this function depends on the fact that a given
 * user will only be inserting one item at a time into the database.
 * If multiple items are being inserted, each INSERT querie must be
 * performed before the next getUniqueListingID call.
 *
 * @return a unique ID.
 */
function sb_getUniquePostID() {
    global $loggedInID, $tableNamePrefix;

    // use current time as part of the ID string
    $currentTime = time();

    // keep trying until we create an ID that is unique in the database
    // use counter in case more than one new item is inserted by
    // a user in the same second (in which case, $currentTime will be
    // the same for both items).
    $uniqueListingCounter = 0;

    sb_connectToDatabase();

    $foundUnique = 0;
    $uniqueID = "";
    
    while( ! $foundUnique ) {

        $uniqueID = "$loggedInID" . "_$currentTime" . "_$uniqueListingCounter";
        
        $query = "SELECT * FROM $tableNamePrefix"."posts WHERE post_id = '$uniqueID';";

        $result = sb_queryDatabase( $query );

        $numRows = mysql_numrows( $result );

        if( $numRows == 0 ) {
            // found a unique ID
            $foundUnique = 1;
            }
        else {
            // collision with existing ID
            
            // increment counter and try again
            $uniqueListingCounter ++;
            }
        }
    sb_closeDatabase();
        
    return $uniqueID;
    }



/**
 * Computes cryptographic hash on a password.
 *
 * @param $user_id the user's ID.
 * @param $password the user's password.
 *
 * @return the 32-character, hex-encoded MD5 hash.
 */
function sb_computePasswordHash( $user_id, $password ) {
    global $siteShortName;
    
    $currentTime = time();
        
    $stringToHash = "$siteShortName$user_id$password";
    $password_md5 = md5( $stringToHash );

    return $password_md5;
    }



/**
 * Computes a session ID for a user.
 *
 * @param $user_id the user's ID.
 * @param $password the user's password.
 *
 * @return the 32-character session ID.
 */
function sb_computeSessionID( $user_id, $password ) {
    global $siteShortName;
    
    $currentTime = time();
        
    $session_id_string = "$siteShortName$user_id$password$currentTime";
    $session_id = md5( $session_id_string );

    return $session_id;
    }



/**
 * Refreshes a user's cookie.
 *
 * @param $user_id the user's ID.
 * @param $session_id the session ID.
 */
function sb_refreshCookie( $user_id, $session_id ) {
    global $cookieName;

    // expire in 24 hours
    $expireTime = time() + 60 * 60 * 24;
    
    setcookie( $cookieName ."_user_id", sb_stripMagicQuotes( $user_id ),
               $expireTime, "/" );
    setcookie( $cookieName ."_session_id", $session_id, $expireTime, "/" );
    }



/**
 * Clears a user's cookie.
 *
 */
function sb_clearCookie() {
    global $cookieName;
    
    // expire an hour ago
    $expireTime = time() - 60 * 60;
    
    setcookie( $cookieName ."_user_id", "", $expireTime, "/" );
    setcookie( $cookieName ."_session_id", "", $expireTime, "/" );
    }



/**
 * Gets the ID of the user that is logged in.
 *
 * @return the user ID, or "" if no user is logged in.
 */
function sb_getLoggedInUser() {
    global $cookieName;

    $cookie_user_id = "";
    if( isset( $_COOKIE[ $cookieName ."_user_id" ] ) ) {
        $cookie_user_id = $_COOKIE[ $cookieName ."_user_id" ];
        }
    $cookie_session_id = "";
    if( isset( $_COOKIE[ $cookieName ."_session_id" ] ) ) {
        $cookie_session_id = $_COOKIE[ $cookieName ."_session_id" ];
        }

    global $justLoggedOut;

    if( ! $justLoggedOut &&
        strcmp( $cookie_user_id, "" ) != 0 &&
        strcmp( $cookie_session_id, "" ) != 0 &&
        // some versions of IE change cookie value to "deleted" upon deletion
        // instead of clearing the cookie
        strcmp( $cookie_user_id, "deleted" ) != 0 &&
        strcmp( $cookie_session_id, "deleted" ) != 0) {

        // check that session ID matches ID in database
        $trueSessionID = sb_getUserDatabaseField( $cookie_user_id, "session_id" );
    

        // session ID in database is set and
        // it matches the cookie session ID
        if( strcmp( $trueSessionID, "" ) != 0 &&
            strcmp( $trueSessionID, $cookie_session_id ) == 0 ) {

            return $cookie_user_id;
            }
        }

    // else
    return "";
    }



/**
 * Gets whether a user exists in the database.
 *
 * @param $user_id the user's ID.
 */
function sb_doesUserExist( $user_id ) {
    global $tableNamePrefix;
    sb_connectToDatabase();

    $query = "SELECT * FROM $tableNamePrefix"."users " .
             "WHERE user_id = '$user_id';"; 

    $result = sb_queryDatabase( $query );

    sb_closeDatabase();
        
    
    $numRows = mysql_numrows( $result );

    if( $numRows == 1 ) {
        return 1;
        }
    else {
        return 0;
        }
    }

 

/**
 * Gets whether the currently logged-in user has administrator status.
 *
 * @return true if user is an admin, or false otherwise.
 */
function sb_isAdministrator() {

    global $loggedInID;
    if( strcmp( $loggedInID, "" ) == 0 ) {
        // public can never edit
        return false;
        }

    if( sb_getUserDatabaseField( $loggedInID, "administrator" ) == 1 ) {
        // admins can always edit
        return true;
        }

    return false;
    }
 
 

/**
 * Gets the value of a user's field from the database.
 *
 * @param $user_id the user's ID.
 * @param $fieldName the name of the field to get.
 */
function sb_getUserDatabaseField( $user_id, $fieldName ) {
    global $tableNamePrefix;
    
    sb_connectToDatabase();

    $query = "SELECT $fieldName FROM $tableNamePrefix"."users " .
             "WHERE user_id = '$user_id';"; 

    $result = sb_queryDatabase( $query );

    sb_closeDatabase();
        
    
    $numRows = mysql_numrows( $result );

    if( $numRows == 1 ) {

        $fieldValue = mysql_result( $result, 0, $fieldName );
        return $fieldValue;
        }
    else {
        sb_fatalError(
            "Could not get database field $fieldName for user $user_id" );
        }
    }


/**
 * Gets the value of a post's field from the database.
 *
 * @param $post_id the post's ID.
 * @param $fieldName the name of the field to get.
 */
function sb_getPostDatabaseField( $post_id, $fieldName ) {
    global $tableNamePrefix;
    
    sb_connectToDatabase();

    $query = "SELECT $fieldName FROM $tableNamePrefix"."posts " .
             "WHERE post_id = '$post_id';"; 

    $result = sb_queryDatabase( $query );

    sb_closeDatabase();
        
    
    $numRows = mysql_numrows( $result );

    if( $numRows == 1 ) {

        $fieldValue = mysql_result( $result, 0, $fieldName );
        return $fieldValue;
        }
    else {
        sb_fatalError(
            "Could not get database field $fieldName for post $post_id" );
        }
    }



/**
 * Sets the value of a user's field in the database.
 *
 * @param $user_id the user's ID.
 * @param $fieldName the name of the field to set.
 * @param $fieldValue the value to set, or NULL to set the field to NULL.
 * @param $autoQuote set to 1 to automatically add quotes to the fieldValue.
 *   Defaults to 1.
 */
function sb_setUserDatabaseField( $user_id, $fieldName, $fieldValue,
                               $autoQuote = 1 ) {
    global $tableNamePrefix;
    
    sb_connectToDatabase();

    $fieldData = $fieldValue;

    if( $autoQuote ) {
        $fieldData = "'$fieldValue'";
        }
    
    if( $fieldValue == NULL ) {
        $fieldData = "NULL";
        }
    
    $query =
        "UPDATE $tableNamePrefix"."users SET $fieldName = $fieldData ".
        "WHERE user_id = '$user_id';";
    
    $result = sb_queryDatabase( $query );

    sb_closeDatabase();
    }



/**
 * Strips any magically escaped quotes from a string.
 *
 * Deals with PHP magic quotes (either on or off) automatically in conjunction
 * with this script's $use_magic_quotes variable.
 *
 * This function is useful for user-submitted strings that are *not* destined
 * for the SQL database (for example, when setting cookies or displaying
 * such strings to the user).
 *
 * @param the string to strip.
 *
 * @return the stripped string, with any escaped quotes fixed into normal
 * quotes.
 */
function sb_stripMagicQuotes( $string ) {
    global $use_magic_quotes;
    if( $use_magic_quotes ) {
        // magic quotes on
        // need to strip slashes
        return stripSlashes( $string );
        }
    else {
        // magic quotes off
        // do nothing
        return $string;
        }
    }



/**
 * Recursively applies the addslashes function to arrays of arrays.
 * This effectively forces magic_quote escaping behavior, eliminating
 * a slew of possible database security issues. 
 *
 * @inValue the value or array to addslashes to.
 *
 * @return the value or array with slashes added.
 */
function sb_addslashes_deep( $inValue ) {
    return
        ( is_array( $inValue )
          ? array_map( 'sb_addslashes_deep', $inValue )
          : addslashes( $inValue ) );
    }



/**
 * Recursively applies the stripslashes function to arrays of arrays.
 * This effectively disables magic_quote escaping behavior. 
 *
 * @inValue the value or array to stripslashes from.
 *
 * @return the value or array with slashes removed.
 */
function sb_stripslashes_deep( $inValue ) {
    return
        ( is_array( $inValue )
          ? array_map( 'sb_stripslashes_deep', $inValue )
          : stripslashes( $inValue ) );
    }



/**
 * Gets the raw contents of a variable from the HTTP request.  This will
 * include escaped quotes if magic quotes are enabled.
 *
 * @param $inVariableName the name of the variable.
 *
 * @return the value of the variable.
 */
function sb_getRequestVariableRaw( $inVariableName ) {
    return $_REQUEST[ $inVariableName ];
    }



/**
 * Gets the filtered of a variable from the HTTP request.  This will
 * include escaped quotes if magic quotes are enabled.
 * Example filtering behavior:  HTML tags are removed.
 *
 * @param $inVariableName the name of the variable.
 *
 * @return the filtered value of the variable.
 */
function sb_getRequestVariableSafe( $inVariableName ) {
    if( isset( $_REQUEST[ $inVariableName ] ) ) {
        return strip_tags( $_REQUEST[ $inVariableName ] );
        }
    else {
        return "";
        }
    }



/**
 * Counts the number of users in the database.
 *
 * @return the number of users.
 */
function sb_getUserCount() {
    global $tableNamePrefix;
    
    sb_connectToDatabase();
    $result =
        sb_queryDatabase( "SELECT COUNT(*) FROM $tableNamePrefix"."users;" );
    $userCount = mysql_result( $result, 0, 0 );
    sb_closeDatabase();
    
    return $userCount;
    }



/**
 * Tests whether the currently logged-in user can edit a post.
 * Works even if no user is logged in.
 *
 * @param $inPostID the post ID to test edit powers for.
 *
 * @return true if editing is allowed, or false if editing is forbidden.
 */
function sb_canEdit( $inPostID ) {

    global $loggedInID;
    if( strcmp( $loggedInID, "" ) == 0 ) {
        // public can never edit
        return false;
        }

    if( sb_getUserDatabaseField( $loggedInID, "administrator" ) == 1 ) {
        // admins can always edit
        return true;
        }

    if( strcmp( $loggedInID,
                sb_getPostDatabaseField( $inPostID, "user_id" ) ) == 0 ) {
        // rest of users can only edit their own posts
        return true;
        }
    else {
        return false;
        }
    }



/**
 * Tests whether a post is visible (approved, not removed, and not expired).
 *
 * Must be connected to database before calling.
 *
 * @param $inPostID the ID to check.
 *
 * @return true if visible, or false if not.
 */
function sb_isPostVisible( $inPostID ) {
    global $tableNamePrefix;
    $query =
        "SELECT COUNT(*) " .
        "FROM $tableNamePrefix"."posts " .
        "WHERE approved = '1' AND removed = '0' ".
        "AND post_id = '$inPostID' ".
        "AND ( expiration_date > CURRENT_TIMESTAMP OR " .
              "expiration_date IS NULL );";

    $result = sb_queryDatabase( $query );
            
    if( mysql_result( $result, 0, 0 ) == 1 ) {
        return true;
        }
    else {
        return false;
        }
    }



/**
 * Gets the full URL that was called to invoke this script, including
 * all GET query parameters.
 *
 * @return the full return URL.
 */
function sb_getReturnURL() {
    $return_url =
        "http://" . $_SERVER['HTTP_HOST'] . $_SERVER[ "SCRIPT_NAME" ];

    $queryString = $_SERVER[ "QUERY_STRING" ];
    if( strcmp( $queryString, "" ) != 0 ) {
        $return_url = $return_url . "?" . $_SERVER[ "QUERY_STRING" ];
        }
    return $return_url;
    }



/**
 * Strips HTML tags from data, preparing them for presentation as pure
 * text in the browser.
 *
 * This function written by Noah Medling <noah.medling@gmail.com> as part
 * of RCBlog.
 *
 * @param $inData the data to strip.
 *
 * @return the stripped data.
 */
function sb_rcb_striphtml( $inData ){
    $patterns = array( '/</', '/>/', '/"/' );
    $replace = array( '&lt;', '&gt;', '&quot;' );
    return preg_replace( $patterns, $replace, $inData );
	}



/**
 * Renders text containing BBCode as HTML for presentation in a browser.
 *
 * This function written by Noah Medling <noah.medling@gmail.com> as part
 * of RCBlog.
 *
 * @param $inData the data to convert.
 *
 * @return the stripped data.
 */
function sb_rcb_blog2html( $inData ){
    $patterns = array(
        "@(\r\n|\r|\n)?\\[\\*\\](\r\n|\r|\n)?(.*?)(?=(\\[\\*\\])|(\\[/list\\]))@si",
			
        // [b][/b], [i][/i], [u][/u], [mono][/mono]
        "@\\[b\\](.*?)\\[/b\\]@si",
        "@\\[i\\](.*?)\\[/i\\]@si",
        "@\\[u\\](.*?)\\[/u\\]@si",
        "@\\[mono\\](.*?)\\[/mono\\]@si",
			
        // [color=][/color], [size=][/size]
        "@\\[color=([^\\]\r\n]*)\\](.*?)\\[/color\\]@si",
        "@\\[size=([0-9]+)\\](.*?)\\[/size\\]@si",
			
        // [quote=][/quote], [quote][/quote], [code][/code]
        "@\\[quote=&quot;([^\r\n]*)&quot;\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/quote\\](\r\n|\r|\n)?@si",
        "@\\[quote\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/quote\\](\r\n|\r|\n)?@si",
        "@\\[code\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/code\\](\r\n|\r|\n)?@si",
			
        // [center][/center], [right][/right], [justify][/justify],
        // [centerblock][/centerblock] (centers a left-aligned block of text)
        "@\\[center\\](\r\n|\r|\n)?(.*?)(\r\n|\r|\n)?\\[/center\\](\r\n|\r|\n)?@si",
        "@\\[right\\](\r\n|\r|\n)?(.*?)(\r\n|\r|\n)?\\[/right\\](\r\n|\r|\n)?@si",
        "@\\[justify\\](\r\n|\r|\n)?(.*?)(\r\n|\r|\n)?\\[/justify\\](\r\n|\r|\n)?@si",
        "@\\[centerblock\\](\r\n|\r|\n)?(.*?)(\r\n|\r|\n)?\\[/centerblock\\](\r\n|\r|\n)?@si",
			
        // [list][*][/list], [list=][*][/list]
        "@\\[list\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/list\\](\r\n|\r|\n)?@si",
        "@\\[list=1\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/list\\](\r\n|\r|\n)?@si",
        "@\\[list=a\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/list\\](\r\n|\r|\n)?@si",
        "@\\[list=A\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/list\\](\r\n|\r|\n)?@si",
        "@\\[list=i\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/list\\](\r\n|\r|\n)?@si",
        "@\\[list=I\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/list\\](\r\n|\r|\n)?@si",
        //			"@(\r\n|\r|\n)?\\[\\*\\](\r\n|\r|\n)?([^\\[]*)@si",
			
        // [url=][/url], [url][/url], [email][/email]
        "@\\[url=([^\\]\r\n]+)\\](.*?)\\[/url\\]@si",
        "@\\[url\\](.*?)\\[/url\\]@si",
        "@\\[urls=([^\\]\r\n]+)\\](.*?)\\[/urls\\]@si",
        "@\\[urls\\](.*?)\\[/urls\\]@si",
        "@\\[email\\](.*?)\\[/email\\]@si",
        "@\\[a=([^\\]\r\n]+)\\]@si",
			
        // [img][/img], [img=][/img], [clear]
        "@\\[img\\](.*?)\\[/img\\](\r\n|\r|\n)?@si",
        "@\\[imgl\\](.*?)\\[/imgl\\](\r\n|\r|\n)?@si",
        "@\\[imgr\\](.*?)\\[/imgr\\](\r\n|\r|\n)?@si",
        "@\\[img=([^\\]\r\n]+)\\](.*?)\\[/img\\](\r\n|\r|\n)?@si",
        "@\\[imgl=([^\\]\r\n]+)\\](.*?)\\[/imgl\\](\r\n|\r|\n)?@si",
        "@\\[imgr=([^\\]\r\n]+)\\](.*?)\\[/imgr\\](\r\n|\r|\n)?@si",
        "@\\[clear\\](\r\n|\r|\n)?@si",
			
        // [hr], \n
        "@\\[hr\\](\r\n|\r|\n)?@si",
        "@(\r\n|\r|\n)@");
		
    $replace  = array(
        '<li>$3</li>',
			
		// [b][/b], [i][/i], [u][/u], [mono][/mono]
        '<b>$1</b>',
        '<i>$1</i>',
        '<span style="text-decoration:underline">$1</span>',
        '<span class="mono">$1</span>',
		
        // [color=][/color], [size=][/size]
        '<span style="color:$1">$2</span>',
        '<span style="font-size:$1px">$2</span>',

        // [quote][/quote], [code][/code]
        '<div class="quote"><span style="font-size:0.9em;font-style:italic">$1 wrote:<br /><br /></span>$3</div>',
        '<div class="quote">$2</div>',
        '<div class="code">$2</div>',
			
        // [center][/center], [right][/right], [justify][/justify],
        // [centerblock][/centerblock]
        '<div style="text-align:center">$2</div>',
        '<div style="text-align:right">$2</div>',
        '<div style="text-align:justify">$2</div>',
        '<CENTER><TABLE BORDER=0><TR><TD>$2</TD></TR></TABLE></CENTER>',
			
        // [list][*][/list], [list=][*][/list]
        '<ul>$2</ul>',
        '<ol style="list-style-type:decimal">$2</ol>',
        '<ol style="list-style-type:lower-alpha">$2</ol>',
        '<ol style="list-style-type:upper-alpha">$2</ol>',
        '<ol style="list-style-type:lower-roman">$2</ol>',
        '<ol style="list-style-type:upper-roman">$2</ol>',
        //			'<li />',
			
        // [url=][/url], [url][/url], [email][/email]
        '<a href="$1" rel="external">$2</a>',
        '<a href="$1" rel="external">$1</a>',
        '<a href="$1">$2</a>',
        '<a href="$1">$1</a>',
        '<a href="mailto:$1">$1</a>',
        '<a name="$1"></a>',
			
        // [img][/img], [img=][/img], [clear]
        '<img border=0 src="$1" alt="$1" />',
        '<img border=0 align="left" src="$1" alt="$1" />',
        '<img border=0 align="right" src="$1" alt="$1" />',
        '<img border=0 src="$1" alt="$2" title="$2" />',
        '<img border=0 align="left" src="$1" alt="$2" title="$2" />',
        '<img border=0 align="right" src="$1" alt="$2" title="$2" />',
        '<div style="clear:both"></div>',
			
        // [hr], \n
        '<hr />',
        '<br />');
    return preg_replace($patterns, $replace, sb_rcb_striphtml( $inData ) );
	}


?>