<?php

/*
Copyright © 2001 Nick Gammon.

  Author: Nick Gammon <nick@gammon.com.au>
  Web:    http://www.gammon.com.au/
  Date:   February 2001

  This program is free software; you can redistribute it and/or modify 
  it under the terms of the GNU General Public License as published by 
  the Free Software Foundation; either version 2 of the License, 
  or (at your option) any later version. 

  This program is distributed in the hope that it will be useful, 
  but WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
  See the GNU General Public License for more details. 

  You should have received a copy of the GNU General Public License 
  along with this program; if not, write to 

  The Free Software Foundation, Inc., 
  59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


  The Free Software Foundation maintains a web page at: http://www.fsf.org

  See the file gpl.txt for the full GNU General Public License.
*/

// common routines

function ShowError ($theerror)
  {
  echo "<table border=0 cellpadding=5> <tr bgcolor=\"#A52A2A\"> <td><font color=\"#FFFFFF\"><b>\n";
  echo (htmlspecialchars ($theerror) . "\n");
  echo "</b></font></td></tr></table>\n";
  } // end of ShowError
  
// Use this before database opened or if we cannot read styles
function MajorProblem ($why)
  {
  global $WEBMASTER;
  echo "<html><head><title>System error</title></head>\n";
  echo "<h3>We apologise that there has been a problem with the web server ...</h3>\n";
  ShowError ($why);
  echo "<p>Error occurred at " . strftime ("%Y-%m-%d %H:%M:%S", time()) . "</p>\n";
  echo "<p>Please notify <a href=\"mailto:$WEBMASTER\">$WEBMASTER</a> of the above message and time.</p>";
  echo "</body></html>\n";
  die ();
  } // end of MajorProblem

function GetDatabaseName (&$thename)
  {
  global $current_database_name;
  
  $thename = $current_database_name;
  } // end of GetDatabaseName

//----------------------------------------------------------------------------
// Open database, load control values
//----------------------------------------------------------------------------
  
function OpenDatabase ($dbserver, $dbuser, $dbname, $dbpassword)
  {
  global $current_database_name;
  
  // save database name in case needed later
  $current_database_name = $dbname;
    
  $link = mysql_pconnect ($dbserver, $dbuser, $dbpassword) 
      or MajorProblem ("Cannot connect to server $dbserver: " . mysql_error ());
      
  mysql_select_db ($dbname) 
      or MajorProblem ("Cannot select database $dbname: " . mysql_error ());
  } // end of OpenDatabase  

function OpenMailDatabase ()
  {
  global $DATABASE_SERVER, $MAIL_DATABASE_NAME, $MAIL_DATABASE_USER, $MAIL_DATABASE_PASSWORD;
    
  $link = mysql_pconnect ($DATABASE_SERVER, $MAIL_DATABASE_USER, $MAIL_DATABASE_PASSWORD) 
      or MajorProblem ("Cannot connect to server $DATABASE_SERVER: " . mysql_error ());
      
  mysql_select_db ($MAIL_DATABASE_NAME) 
      or MajorProblem ("Cannot select database $MAIL_DATABASE_NAME: " . mysql_error ());
  } // end of OpenMailDatabase  

function GetControlItems ()
  {
  global $control;
  
  $result = mysql_query ("SELECT * FROM control") 
    or MajorProblem ("Select of control table failed: " . mysql_error ());

  while ($row = mysql_fetch_array ($result))
    $control [$row ['item']] = $row ['contents'];
    
  mysql_free_result ($result);  
 
  }

/*

I am doing sessions my own way for a number of reasons. The main one is I want to know
what is happening, and I only want session id tags to appear on pages if you have logged
in, otherwise it isn't necessary.

*/

function CheckSessionID ()
  {
  global $control, $userinfo, $adminaction, $ADMIN_DIRECTORY;

  if (empty ($userinfo))
    return;   // no session for this guy
   
   
  if ($adminaction == "logoff")
    {
    LogOff ();
    $userinfo = "";   // don't use unset, it doesn't change the global version
    return;    
    }
   
  $userinfo ["logged_on"] = true;
  echo "<font size=1><p align=right>Logged on as <b>"
     . $userinfo ["username"]
     . "</b>&nbsp;&nbsp;";
  hLink ("(Menu)", $ADMIN_DIRECTORY . "logon.php");
  hLink ("(Log off)", $ADMIN_DIRECTORY . "logon.php", "adminaction=logoff");
  echo $control ['admin_links'];    // extra useful links
  echo "</p></font>\n";
    
  } // end of CheckSessionID  

// this is for an *adminstrative* logon, eg. to edit SQL tables etc.

function CheckAdminSession ()
  {
  global $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS; 
  global $userinfo;
  
  $adminsession = $HTTP_POST_VARS ['session'];
  if (empty ($adminsession))
    $adminsession = $HTTP_GET_VARS ['session'];
  if (empty ($adminsession))
    $adminsession = $HTTP_COOKIE_VARS ['session'];
  
  $userinfo = "";
    
  // if they are logging on, let them 
  
  $adminaction = trim ($HTTP_POST_VARS ['adminaction']);
  $username = trim ($HTTP_POST_VARS ['username']);
  $password = trim ($HTTP_POST_VARS ['password']);
  
  if ($adminaction == "logon")   
    {

    $md5_password = md5 ($password);
    
    $result = mysql_query ("SELECT * FROM user "
                         . "WHERE username = '$username' "
                         . "AND password = '$md5_password'") 
      or Problem ("Select of user failed: " . mysql_error ());
    
    $userinfo = mysql_fetch_array ($result);
    mysql_free_result ($result);  
    
    if ($userinfo)
      {
              
      // generate session
      srand ((double) microtime () * 1000000);
      $session = md5 (uniqid (rand ()));
      $userid = $userinfo ['userid'];
      
      $query = "UPDATE user SET session = '$session', "
             . "date_logged_on = "
             . "'" . strftime ("%Y-%m-%d %H:%M:%S", utctime()) . "' "
             . "WHERE userid = $userid";
      
      $result = mysql_query ($query)
        or Problem ("Update of user failed: " . mysql_error ());
  
      $userinfo ['session'] = $session; 
      $expiry = $userinfo ['cookie_expiry'];
      if (!$expiry)
        $expiry = 60 * 60 * 24 * 7;    // expire in 7 days as default
      if ($userinfo ['use_cookies'])   // only if wanted  
        setcookie ('session', $userinfo ['session'], utctime() + $expiry, "/");
      } // end of user on file

      return;   // end of logon process
    
    } // end of logon wanted    

  if (empty ($adminsession))    // not logged on yet
    return;   // no session, and not logging in
  
  $result = mysql_query ("SELECT * FROM user WHERE session = '$adminsession'") 
    or Problem ("Select of session failed: " . mysql_error ());
      
  if ($userinfo = mysql_fetch_array ($result)) // will be empty if no match
    {
        
    // if the user is found, and their session was the same one found in the cookie
    // we don't need to pass sessions on URLs, which makes them look better
    
    if ($userinfo ['session'] == $HTTP_COOKIE_VARS ['session'])
      $userinfo ['have_cookie_ok'] = true;   // don't need to pass sessions on links
    } // end of reading that user OK
    
  mysql_free_result ($result);  
    
  } // end of CheckAdminSession  

function CheckForumToken ()
  {
  global $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS, 
         $HTTP_SERVER_VARS, $HTTP_ENV_VARS; 
  global $foruminfo, $blocked;
  
  $forumtoken = $HTTP_POST_VARS ['token'];
  if (empty ($forumtoken))
    $forumtoken = $HTTP_GET_VARS ['token'];
  if (empty ($forumtoken))
    $forumtoken = $HTTP_COOKIE_VARS ['token'];

  $foruminfo = "";
    
  // if they are logging on, let them 
  
  $action = trim ($HTTP_POST_VARS ['action']);

  // get rid of quotes so they can paste from the email like this: "Nick Gammon"
  $username = stripslashes ($HTTP_POST_VARS ['username']);
  $username = str_replace ("\"", " ", $username);
  $username = addslashes (trim ($username));  // in case their name is O'Grady

  $password = stripslashes ($HTTP_POST_VARS ['password']);
  $password = str_replace ("\"", " ", $password);
  $password = addslashes (trim ($password));
    
  if ($action == "logon")   
    {
    $result = mysql_query ("SELECT * FROM bbuser "
                         . "WHERE username = '$username' "
                         . "AND password = '$password'") 
      or Problem ("Select of user failed: " . mysql_error ());
    
    $foruminfo = mysql_fetch_array ($result);
    mysql_free_result ($result);  
    
    if ($foruminfo)
      {
      
      if ($foruminfo ['blocked'])
        {
        $blocked = true;    // can't do it
        $foruminfo = "";
        return; // give up
        }
        
      // generate token
      srand ((double) microtime () * 1000000);
      $token = md5 (uniqid (rand ()));
      $bbuser_id = $foruminfo ['bbuser_id'];
      
      // try and work out their IP address
      $remote_ip = $HTTP_SERVER_VARS ['REMOTE_ADDR'];
      if (!$remote_ip)
        $remote_ip = $HTTP_ENV_VARS ['REMOTE_ADDR'];
      
      $query = "UPDATE bbuser SET "
             . "  token = '$token', "
             . "  date_logged_on = "
             . "  '" . strftime ("%Y-%m-%d %H:%M:%S", utctime()) . "', "
             . "  last_remote_ip = '$remote_ip' "
             . "WHERE bbuser_id = $bbuser_id";
      
      $result = mysql_query ($query)
        or Problem ("Update of bbuser failed: " . mysql_error ());
  
      $foruminfo ['token'] = $token; 
      $expiry = $foruminfo ['cookie_expiry'];
      if (!$expiry)
        $expiry = 60 * 60 * 24 * 7;    // expire in 7 days as default
      if ($foruminfo ['use_cookies'])   // only if wanted  
        setcookie ('token', $foruminfo ['token'], utctime() + $expiry, "/");
      } // end of user on file

      return;   // end of logon process
    
    } // end of logon wanted    

  if (empty ($forumtoken))    // not logged on yet
    return;   // no token, and not logging in
 
  $result = mysql_query ("SELECT * FROM bbuser WHERE token = '$forumtoken'") 
    or Problem ("Select of forum token failed: " . mysql_error ());
    
  if ($foruminfo = mysql_fetch_array ($result)) // will be empty if no match
    {
    
    // if the user is found, and their token was the same one found in the cookie
    // we don't need to pass tokens on URLs, which makes them look better
    
    if ($foruminfo ['token'] == $HTTP_COOKIE_VARS ['token'])
      $foruminfo ['have_cookie_ok'] = true;   // don't need to pass tokens on links
    } // end of reading that user OK
    
  mysql_free_result ($result);  

  // check for a problem user logging in
  if ($foruminfo ['blocked'])
    {
    $blocked = true;    // can't do it
    $foruminfo = "";
    }
    
  } // end of CheckForumToken  

// log off by changing the session id
function LogOff ()
  {
  global $userinfo;
  // generate another random token - they won't know that one!
  srand ((double) microtime () * 1000000);
  $session = md5 (uniqid (rand ()));
  
  $query = "UPDATE user SET session = '$session' "
          . "WHERE userid = " . $userinfo ['userid'];
  $result = mysql_query ($query)
    or Problem ("Update of user failed: " . mysql_error ());

  $userinfo = "";    // user info is no good
    
  } // end of LogOff
  
function ShowSource ($filename)
  {
 
  Permission ('viewsource');
  
  $fd = @fopen ($filename, "r") 
    or Problem ("Cannot open file '$filename'");  
  $sourcedata = fread ($fd, filesize ($filename)) 
    or Problem ("Cannot read file '$filename'");  
  fclose ($fd);
  bTable ();
  bRow ("lightblue");
  tHead ("Source of: $filename");
  eRow ();
  bRow ("azure");
  echo "<td><pre><font size=3><code>\n"; 
  echo htmlspecialchars ($sourcedata);
  echo "</code></font></pre></td>"; 
  eRow ();
  eTable ();    
  $sourcedata = "";
  
  } // end of  ShowSource 
  
function Init ($title, 
               $keywords = "", 
               $mail=false,
               $dbserver = "", 
               $dbuser = "", 
               $dbname = "",
               $dbpassword = "",
               $otherheaderhtml = "")
  {
  global $userinfo, $logoff, $control, 
         $viewsource, $PATH_TRANSLATED, $pagestarttime, $doingMail;
         
  // default databases         
  global $DATABASE_SERVER, $GENERAL_DATABASE_USER, 
         $GENERAL_DATABASE_NAME, $GENERAL_DATABASE_PASSWORD;

  // take defaults if necessary
   if ($dbserver == "")
     $dbserver = $DATABASE_SERVER;
   if ($dbuser == "")
     $dbuser = $GENERAL_DATABASE_USER;
   if ($dbname == "")
     $dbname = $GENERAL_DATABASE_NAME;
   if ($dbpassword == "")
     $dbpassword = $GENERAL_DATABASE_PASSWORD;
  
  // note when we started, for timing purposes
  $pagestarttime = getmicrotime ();
 
  if ($doingMail = $mail)  // this assignment is intentional
    OpenMailDatabase ();
  else
    {
    OpenDatabase ($dbserver, $dbuser, $dbname, $dbpassword);
    // I am going to use cookies here, so I must do it before I do the header :)
    CheckAdminSession ();
    CheckForumToken ();
    }

  $control ['dateformat'] = "%e %b %Y";  // default date format
  $control ['shortdateformat'] = "%e %b";  // default short date format
  $control ['timeformat'] = "%r";  // default time format
  $control ['datetimeformat'] = "%e %b %Y %r";  // default date/time format
  $control ['shortdatetimeformat'] = "%e %b %r";  // default short date/time format
  
  // HTML control items
  GetControlItems ();
       
  MessageHead ($title, $keywords, $otherheaderhtml);
  
  // we check the session ID here because it outputs HTML which has to come
  // after the header (eg. the fact that you are logged on)
  
  CheckSessionID ();
    
  if ($viewsource == "yes")
    ShowSource ($PATH_TRANSLATED);
    
  } // end of Init
 
//----------------------------------------------------------------------------
// Start, end of page
//----------------------------------------------------------------------------
 
function SetFont ()
  {
  global $control;
  echo $control ['font'];
  } // end of SetFont  


function MessageHead ($title, $keywords, $otherheaderhtml)
  {
global $control;

if ($title == "%FORUM_NAME%")
  $title = $control ['forum_name'];
  
$head = str_replace ("<%TITLE%>", htmlspecialchars ($title), $control ['head']);  
$head = str_replace ("<%KEYWORDS%>", htmlspecialchars ($keywords), $head);  
$head = str_replace ("<%FONT%>", $control ['font'], $head);  
echo $head;
echo $otherheaderhtml;    // eg. refresh
  }   // end of MessageHead

/*

Call this at the end of each page to do a standard page footer

*/

function MessageTail ()
  {
global $control, $pagestarttime, $userinfo, $doingMail, $foruminfo;

$endtime = getmicrotime ();
$diff = $endtime - $pagestarttime;

if (!empty ($userinfo) || $doingMail || 
    $foruminfo ['admin'] || 
    $foruminfo ['moderator_topic'] ||
    $foruminfo ['moderator_section'])
  {
  echo "<p><table border=0 cellpadding=5> <tr bgcolor=\"#008000\"> <td><font color=\"#FFFFFF\"><b>\n";
  printf ("<b>Page execution time: %6.3f seconds</b>\n", $diff);
  echo "</b></font></td></tr></table></p>\n";
  }
  
echo $control ['tail'];
  } // end of MessageTail

  
/*
Return a status name from an ID
*/  
  
function GetStatusName ($statusid, &$statusname)
  {

  $result = mysql_query ("SELECT longdescription FROM status "
                       . "WHERE statusid = $statusid"
                        ) 
      or die ("Select of status name failed: " . mysql_error ());
  
  if ($row = mysql_fetch_row ($result))
    $statusname = $row[0];
  else
    $statusname = "Unknown status";

  mysql_free_result ($result);
  } // end of GetstatusName
  
function Problem ($why)
  {
  echo "<h3>There is a problem ...</h3><p>\n";
  ShowError ($why);
  MessageTail (false); 
  die ();
  } // end of Problem

//----------------------------------------------------------------------------
// Debugging
//----------------------------------------------------------------------------

function ShowArray ($name, $thearray, $recurse = false)
  {
  echo "<p><b>$name</b></p><ul>\n";
  if (!is_array ($thearray))
    {
    echo "<li>Not an array\n";
    echo "</ul>\n";
    return;
    }
    
  reset ($thearray);
  while (list ($cellname, $value) = each ($thearray))
    {
    
    printf ("<li>[%s] = [%s]\n", 
            htmlspecialchars ($cellname),   // name
            htmlspecialchars ($value));  // value
    if ($recurse && is_array ($value))
      ShowArray ($cellname, $value);
    }
  echo "</ul>\n";
  
  } // end of showarray
  
function DebugVars ()
  { 
  global $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS; 
  echo "<hr>\n";
  ShowArray ("HTTP_GET_VARS", $HTTP_GET_VARS);
  ShowArray ("HTTP_POST_VARS", $HTTP_POST_VARS);
  ShowArray ("HTTP_COOKIE_VARS", $HTTP_COOKIE_VARS);
  ShowArray ("GLOBALS", $GLOBALS);
  echo "<hr>\n";
  } // end of DebugVars  
  
//----------------------------------------------------------------------------
// Table management
//----------------------------------------------------------------------------

// begin table
function bTable ($border=1, $cellpadding=5)
  {
  echo "<table border=$border cellpadding=$cellpadding>\n";
  } // end of bTable

// end table
function eTable ()
  {
  echo "</table>\n";
  } // end of eTable

// begin row
function bRow ($bgcolor="#F0FFFF", $valign="top")  // azure
  {
  if ($bgcolor == 'same')
    echo "<tr valign=$valign>\n";  
  else
    echo "<tr valign=$valign bgcolor=\"$bgcolor\">\n";
  } // end of bRow
  
// end row  
function eRow ()
  {
  echo "</tr>\n";
  } // end of eRow
  
// table heading  
function tHead ($text, $fontsize=-1, $align="left", $colspan=1)
  {
  echo "<th align=$align colspan=$colspan><font size=$fontsize>"
      . nl2br (htmlspecialchars ($text))
      . "</font></th>\n";
  }  // end of tHead

// table heading - HTML
function tHeadH ($text, $fontsize=-1, $align="left", $colspan=1)
  {
  echo "<th align=$align colspan=$colspan><font size=$fontsize>"
      . $text
      . "</font></th>\n";
  }  // end of tHeadH
  
// table data  
function tData ($text, $fontsize=-1, $align="left", $colspan=1)
  {
  echo "<td align=$align colspan=$colspan><font size=$fontsize>"
      . nl2br (htmlspecialchars ($text))
      . "</font></td>\n";
  } // end of tData 

// HTML table data  
function tDataH ($text, $fontsize=-1, $align="left", $colspan=1)
  {
  echo "<td align=$align colspan=$colspan><font size=$fontsize>"
      . $text
      . "</font></td>\n";
  } // end of tDataH 

// start unordered list
function bList ()
  {
  echo "<ul>\n";
  } // end of bList

// end unordered list
function eList ()
  {
  echo "</ul>\n";
  } // end of eList
 
// start ordered list
function bOList ()
  {
  echo "<ol>\n";
  } // end of bOList

// end ordered list
function eOList ()
  {
  echo "</ol>\n";
  } // end of eOList
   
// list item
function LI ()
  {
  echo "<li>";
  } // end of LI

// returns an hlink in a string
function shLink (&$result, $description, $destination, $params="", $newwindow=false)
  {
  global $userinfo, $viewsource, $foruminfo;
    
  if (!$foruminfo ['have_cookie_ok'])
    $token = $foruminfo ['token'];
  if (!$userinfo ['have_cookie_ok'])
    $session = $userinfo ['session'];
    
  if ($session && $token)
    $session = "?session=$session&token=$token";
  else if ($session)
    $session = "?session=$session";
  else if ($token)
    $session = "?token=$token";
  else
    $session = "";
  
  if ($viewsource == "yes")
    $session .= "&viewsource=yes";
  
  if ($newwindow)
    $target = " target=\"_blank\"";
  else
    $target = "";
    
  // only show session ID if we have one, and we are going to a dynamic page
  if (!empty ($session) && stristr ($destination, ".php"))
    if (empty ($params))
      $result =  "<a href=\"$destination$session\"$target>$description</a>\n";
    else
      $result =  "<a href=\"$destination$session&$params\"$target>$description</a>\n";
  else
    if (empty ($params))
      $result =   "<a href=\"$destination\"$target>$description</a>\n";
    else
      $result =   "<a href=\"$destination?$params\"$target>$description</a>\n";
  
  } // end of shLink
  
// use this to hyperlink to another file, preserving session id
function hLink ($description, $destination, $params="", $newwindow=false)
  {
  shLink ($result, $description, $destination, $params, $newwindow);
  echo $result;          
  }   // end of hLink

// use this to send a form, preserving forum session id
function ForumSession ()
  {
  global $foruminfo;
  if (!$foruminfo ['have_cookie_ok'])
    if (!empty ($foruminfo))
      {
      $token = $foruminfo ['token'];
      echo "<input type=hidden name=token value=\"$token\">\n";  
      }
  } // end of ForumSession
  
// use this to send a form, preserving session id
function FormSession ()
  {
  global $userinfo, $viewsource;
  
  if (!$userinfo ['have_cookie_ok'])
    if (!empty ($userinfo))
    {
    $session = $userinfo ['session'];
    echo "<input type=hidden name=session value=\"$session\">\n";  
    }
  if ($viewsource == "yes")
    echo "<input type=hidden name=viewsource value=yes>\n"; 
  ForumSession (); 
  } // end of FormSession


// check we can do something
function Permission ($todo)
  {
  global $userinfo, $ADMIN_DIRECTORY;
  
  if (!$userinfo [$todo])
    {
    echo "<p>";
    hLink ("Log on", $ADMIN_DIRECTORY . "logon.php");
    echo "</p>\n";
    Problem ("Permission denied");
   }
  }  // end of  Permission  
   
/*

Shows a table, supplied in the $table argument (ie. an array)
Tables consist of a label, and contents. eg.

$table = array 
  (
  'Summary' => $row ["shortdescription"],  
  'Details' => $row ["longdescription"],   
  );  // end of array

You can specify parameters for the table, there are defaults if you omit them:

$params =  array 
  (     // table parameters
  'LH' => 'valign=top bgcolor="#ADD8E6" align=right',
  'RH' => 'bgcolor="#FAF0E6" align=left'
  );

You can specify one or more "special processing" for named labels:

$specials = array 
  (     // specials
  'Summary'     => array ('heading'),   // want this in bold
  'Details'     => array ('breaks'),    // has line breaks
  'Fixed in version' => array ('html', 'heading') // is in HTML and we want bold
  );

Currently specials are:

  description - description (LH column) - defaults to label
  heading - I want this line in bold (ie. TH rather than TD)
  breaks - I want to see newlines - implies not HTML
  html - The line is already in HTML
  input - field is input, argument is name (eg. input => 'myfield')
  type - type of input (text, multiline, password, combo, bool)
  size - size of input field on screen
  maxlength - max length of input field
  values - values array for a combo box etc.
  rows - number of rows in a multiline field
  cols - number of columns in a multiline field
  error - flag entry with a red asterisk
  comment - explanatory material to be shown in small type in the RH column
  htmlcomment - explanatory material in the RH column - HTML
  
*/
    
function ShowTable ($table, $params, $specials)
  {
  global $WEBMASTER;

  if (!is_array ($table))
    {
    echo "<p><b>Not a table</b></p>\n";
    return;
    }

  $tableparam = $params ['table'];    // args for table
  if (!isset ($tableparam))
    $tableparam="border=0 cellpadding=5"; // default

  $rowparam = $params ['row'];    // args for each row
  if (!isset ($rowparam))
    $rowparam="valign=top";         // default

  $LHcolparam = $params ['LH'];    // args for LH column
  if (!isset ($LHcolparam))
    $LHcolparam="valign=top align=right";         // default

  $RHcolparam = $params ['RH'];    // args for RH column
  if (!isset ($RHcolparam))
    $RHcolparam="valign=top";         // default
      
  $bfont = $params ['font'];    // font definition
  if (!isset ($bfont))
    $bfont="<font size=-1>";         // default

  if (!empty ($bfont))    // must terminate the font definition
    $efont = "</font>";
  
  // sanity check - we can't have errors for non-input fields

  if (is_array ($specials))
    {
    $implementation_error = false;
    reset ($specials);
    while (list ($label, $contents) = each ($specials))
      {
      $error = $contents ['error'];
      if ($error && $error != '*' && !$contents ['input'])
        {
        ShowError ("Implementation error - error message \"$error\" for field \"$label\""
                 . " however this field is not an input field.");
        $implementation_error = true;
        }
      } // end of checking specials
  
    if ($implementation_error)
      echo "<p>Please notify the above message(s) to: <a href=\"mailto:$WEBMASTER\">$WEBMASTER</a></p>";
    } // end of specials being an array    
  
  echo "<table $tableparam>\n";
  
  $contents = "";	// in case no items
        
  reset ($table);
  while (list ($label, $contents) = each ($table))
    {
   
    // any special processing for this item?
    $special = $specials [$label];
    $html = false;
    $heading = false;
    $breaks = false;
    $inputname = "";
    $error = false;
    $type = 'text';
    $comment = "";
    $htmlcomment = "";
    $description = $label;
//    $bold = false;
        
    // if the word is in the array, then it is enabled
    if (is_array ($special))
      {
      $html = $special ['html'];                  // HTML encoded
      $heading = $special ['heading'];            // this row is heading
      $breaks = $special ['breaks'];              // line breaks wanted
      $inputname = $special ['input'];            // name of input field
      $size = $special ['size'];                  // size of it on screen
      $maxlength = $special ['maxlength'];        // max length of it
      $type = $special ['type'];                  // type of input (text, password, combo, multiline, bool)
      $values = $special ['values'];              // values for combo box
      $rows =   $special ['rows'];                // rows in multiline box
      $cols =   $special ['cols'];                // cols in multiline box
      $error = $special ['error'];                // this row is in error
      $comment = $special ['comment'];            // comment pertaining to this row
      $htmlcomment = $special ['htmlcomment'];    // HTML comment pertaining to this row
//      $bold = $special ['bold'];                  // is description in bold?
      if ($special ['description'])
        $description = $special ['description'];  // description of this row
      
      if (empty ($type))
        $type = 'text';
        
      }   // end of having specials
      
    // don't display NULL rows, provided we are not getting input from it
    if (!isset ($contents) && empty ($inputname))
      continue;

	 if (is_array ($contents))
	    {
			ShowArray ("error, contents is an array", $contents);
	    return;
	    }
    
    // if 'breaks' then they want to keep line breaks
    if ($breaks)
      $contents = nl2br (htmlentities ($contents));
    else if (!$html)
      $contents = htmlspecialchars ($contents);
      
    // this is a heading?
    if ($heading)
      $td = "th";
    else
      $td = "td";
                  
    echo "  <tr $rowparam>\n";
    echo "    <$td $LHcolparam>$bfont<b>" . htmlspecialchars ($description) . "</b>$efont</$td>\n";
    echo "    <$td $RHcolparam>$bfont";
    
    // do forms processing
    if (!empty ($inputname))
      {
      switch ($type)
        {
        case 'combo':
          echo "<select name=\"$inputname\" size=1>\n";
          reset ($values);
          while (list ($selectvalue, $selectdescription) = each ($values))
            {
            echo "<option value=\"$selectvalue\" ";
            if ($contents == $selectvalue)
              echo "selected ";
            echo ">$selectdescription\n";
            } // end of each item
          echo "</select>\n";
          break;    // end of combo box
        
        case 'multiline':
          echo "<textarea name=\"$inputname\" wrap=virtual ";
          if (isset ($rows))
            echo "rows=$rows ";
          if (isset ($cols))
            echo "cols=$cols ";
          echo ">";
          echo $contents;
          echo "</textarea>\n";
          break;    // end of multiline input area

        case 'bool':
          echo "<input type=checkbox name=\"$inputname\" value=1 ";
          if ($contents)
            echo "checked ";
          echo ">\n";
          break;    // end of boolean

        case 'latitude':
          $name1 = $inputname . "_direction";
          $name2 = $inputname . "_degrees";
          $name3 = $inputname . "_minutes";
          $deg = floor (abs ($contents));
          $min = round ((abs ($contents) - $deg) * 60 * 10) / 10;;
          
          echo "\n<select name=\"$name1\" size=1>\n";
          echo "<option value=\"1\"";
          if ($contents >= 0)
            echo " selected";
          echo ">North\n";
          echo "<option value=\"-1\"";
          if ($contents < 0)
            echo " selected";
          echo ">South\n";
          echo "</select>\n";
          echo "&nbsp;<input type=$type name=\"$name2\" value=\"$deg\" "
             . "size=3 maxlength=3> degrees\n";
          echo "&nbsp;<input type=$type name=\"$name3\" value=\"$min\" "
             . "size=7 maxlength=7> minutes\n";
          break;    // end of latitude

        case 'longitude':
          $name1 = $inputname . "_direction";
          $name2 = $inputname . "_degrees";
          $name3 = $inputname . "_minutes";
          $deg = floor (abs ($contents));
          $min = round ((abs ($contents) - $deg) * 60 * 10) / 10;;
          
          echo "\n<select name=\"$name1\" size=1>\n";
          echo "<option value=\"1\"";
          if ($contents >= 0)
            echo " selected";
          echo ">East\n";
          echo "<option value=\"-1\"";
          if ($contents < 0)
            echo " selected";
          echo ">West\n";
          echo "</select>\n";
          echo "&nbsp;<input type=$type name=\"$name2\" value=\"$deg\" "
             . "size=3 maxlength=3> degrees\n";
          echo "&nbsp;<input type=$type name=\"$name3\" value=\"$min\" "
             . "size=7 maxlength=7> minutes\n";
          break;    // end of longitude
          
        default:
          echo "<input type=$type name=\"$inputname\" value=\"$contents\" ";
          if (isset ($size))
            echo "size=$size ";
          if (isset ($maxlength))
            echo "maxlength=$maxlength ";
          echo ">\n";
          break;    // end of default input type
                
        }   // end of switch on input type
      if ($error)
        echo "<br><b><font color=\"#FF0000\">$error</font></b>";
      } // end of having an input field
    else    
      echo $contents;
  
    // comment
    
    if (!empty ($comment))
      {
      echo "<br><font size=-2>";
      echo (nl2br (htmlentities($comment)));
      echo ("\n</font>\n"); 
      }

    // HTML comment
    
    if (!empty ($htmlcomment))
      {
      echo "<br><font size=-2>";
      echo ($htmlcomment);
      echo ("\n</font>\n"); 
      }
    
    echo "$efont</$td>\n";
    echo "  </tr>\n";
    } // end of looping through each item
  echo "</table>\n";
  
  } // end of ShowTable
  
function getmicrotime ()
  { 
  $mtime = microtime(); 
  $mtime = explode(" ",$mtime); 
  $mtime = $mtime[1] + $mtime[0]; 
  return ($mtime); 
  }  // end of getmicrotime

  
function ShowList ($query,      // SQL query
                   $id,         // id of identity row (eq. faqid)
                   $other_args = "",  // other args for link, eg. &productid=0
                   $summary = "summary",
                   $show_count = true,  // show count of matches?
                   $block_preamble = "<UL>",  
                   $block_postamble = "</UL>",
                   $line_preamble = "<LI>",
                   $line_postamble = "</LI>",
                   $page = ""
                   )
{
global $PHP_SELF;
                      
$result = mysql_query ($query) 
    or Problem ("Select failed: " . mysql_error ());

$count = mysql_num_rows ($result);

if ($count)
  echo $block_preamble . "\n";

if (!$page)
  $page = $PHP_SELF;
  
while ($row = mysql_fetch_array ($result))
  {
  echo $line_preamble;
  $summarydata = $row [$summary];
  $iddata = $row [$id];
  hLink (htmlspecialchars ($summarydata), $page, "$id=$iddata$other_args");
  echo $line_postamble . "\n";

  // ------ excerpt -------

  $excerpt = $row ["excerpt"];
  
  if (!empty ($excerpt))
    {
    bList (); // indent
    echo "<font size=-2>";
    if ($row ['html'])
      $excerpt = strip_tags ($excerpt);
    echo (htmlentities($excerpt));
    echo ("\n</font><br>\n"); 
    eList ();   // unindent
    }
    
  } // end of reading each row

if ($count)
  echo $block_postamble . "\n";

if ($show_count)
  {
  echo "<p><b>";
  if ($count == 0)
    echo "No matches.";
  else if ($count == 1)
    echo "One match.";
  else
    echo $count . " matches.";
  echo "</b></p>";
  }  // end of searching for something

mysql_free_result ($result);

return $count;
} // end of ShowList   

// validate integers
function ValidateInt ($theint)
  {
  $theint = trim ($theint); // ensure no leading spaces etc.

  // look for leading sign
  if ($theint [0] == '+' || $theint [0] == '-')
    $theint = substr ($theint, 1);    // remove sign
  if (!strlen ($theint) || !ereg ("^[0-9]+$", $theint))
    return "Field must be numeric";
  } // end of ValidateInt

// validate booleans
function ValidateBool ($thebool)
  {
  if (!strlen ($thebool) || !ereg ("^[0-1]$", $thebool))
    return "Field must be '0' or '1'";
  } // end of ValidateBool

// validate real numbers  
function ValidateReal ($thereal)
  {
  $thereal = trim ($thereal); // ensure no leading spaces etc.
  
  // look for leading sign
  if ($thereal [0] == '+' || $thereal [0] == '-')
    $thereal = substr ($thereal, 1);    // remove sign
  
  $items = explode (".", trim ($thereal));  // don't want two decimal points
  if (count ($items) > 2 || !strlen ($thereal) || !ereg ("^[0-9.]+$", $thereal))
    return "Field must be numeric";
  } // end of ValidateReal

// validate degrees (eg. latitude/longitude)  
function ValidateDegrees ($thedegrees)
  {
  $error = ValidateReal ($thedegrees);
  if ($error)
    return $error;
  if ($thedegrees > 360 || $thedegrees < -360)
    return "Degrees must be in range 0 to 360";    
  } // end of ValidateDegrees
  
// simple date check
function ValidateDate ($thedate)
  {
  global $control;
  
  $thedate = trim ($thedate); // ensure no leading spaces etc.
  
  // don't let them slip in alphas or other stuff into the middle of a number
  if (!ereg ("^[0-9\-]+$", $thedate))
     return "Date must consist of YYYY-MM-DD";
    
  $items = explode ("-", trim ($thedate));
  
  if (count ($items) != 3)
     return "Date must consist of YYYY-MM-DD";
     
  if ($items [0] < 1900 || $items [0] > 2100)
     return "Year must be in range 1900 to 2100";
  
  if ($items [1] < 1 || $items [1] > 12)
     return "Month must be in range 1 to 12";
  
  if ($items [2] < 1 || $items [2] > 31)
     return "Month must be in range 1 to 31";
  
  return "";
  } // end of ValidateDate

function ValidateTime ($thetime)
  {
  $thetime = trim ($thetime); // ensure no leading spaces etc.
    
    
  // don't let them slip in alphas or other stuff into the middle of a number
  if (!ereg ("^[0-9\:]+$", $thetime))
     return "Time must consist of HH:MM or HH:MM:SS";

  $items = explode (":", trim ($thetime));
  
  if (count ($items) < 2 || count ($items) > 3)
     return "Time must consist of HH:MM or HH:MM:SS";
     
  if ($items [0] < 0 || $items [0] > 23)
     return "Hour must be in range 0 to 23";
  
  if ($items [1] < 0 || $items [1] > 59)
     return "Minute must be in range 0 to 59";
  
  if ($items [2])
    if ($items [2] < 0 || $items [2] > 59)
       return "Seconds must be in range 0 to 59";
  
  return "";
  } // end of ValidateTime

function ValidateField ($value, $type)
  {
  $error = "";  
  switch ($type)
    {
    case "int": 
                $error = ValidateInt ($value);
                 break;
    case "bool": 
                $error = ValidateBool ($value);
                 break;
    case "real": 
                $error = ValidateReal ($value);
                 break;
     case "date":
                $error = ValidateDate ($value);
                break;
     case "time":
                $error = ValidateTime ($value);
                break;
     case "latitude":
     case "longitude":
                $error = ValidateDegrees ($value);
                break;
     case "datetime":
                $temp = explode (" ", $value);
                if (count ($temp) < 1 || count ($temp) > 2)
                  $error = "Date/time must consist of YYYY-MM-DD [ HH:MM:SS ]";
                $error = ValidateDate ($temp [0]);
                // date OK? then check time
                if (!$error && $temp [1])
                  $error = ValidateTime ($temp [1]);
                break;                                    
    } // end of switch on type
  
  return $error;
    
  } // end of ValidateField

function ValidateOneField ($name, $type, $notnull, $maxsize, &$specials)
  {
  global $HTTP_POST_VARS, $have_error;
 
  // remove leading/trailing spaces
  $HTTP_POST_VARS [$name] = trim ($HTTP_POST_VARS [$name]);
  $value = stripslashes ($HTTP_POST_VARS [$name]); // get value

  // check for empty on NOT NULL fields
  if (!strlen ($value))
    {
    if ($notnull)
      {
      $have_error = true;
      $specials [$name] ['error'] = "Field cannot be empty";
      }
      return;   // enough validation, field is empty
    } // end of empty field

  // validate fields  

  $error = ValidateField ($value, $type);

  if (!$error && $type == "email")
    {
    if (strstr ($value, "\""))
      $error = "Email address should not contain a 'quote' character";
    else if (!strstr ($value, "@"))
      $error = "Email address should contain the '@' character";
    
    } // end of email address
      
  if (!$error)
    {
    if ($maxsize && strlen ($value) > $maxsize)
      $error = "You have entered too much data - maximum is $maxsize characters";
    } // end of checking size      

  if ($error)
    {
    $have_error = true;
    $specials [$name] ['error'] = $error;
    }
  
  } // end of ValidateOneField

function CheckField ($description, $id, $can_be_blank=true)
  {
  if ($id || !$can_be_blank)
    {
    $error = ValidateInt ($id);
    if ($error)
      Problem ("Error in $description ID - $error");
    } // end of not empty
  } // end of CheckField

function ShowTablesToEdit ()
  {
  global $userinfo, $ADMIN_DIRECTORY;
  
  echo "<form METHOD=\"post\" ACTION=\"" . $ADMIN_DIRECTORY . "edittable.php\"> \n";
  echo "<p>Edit table: &nbsp; <select name=table size=1>\n";

  // see if this user can edit *all* tables  
  
  $userid = $userinfo ["userid"];
  $result = mysql_query ("SELECT * FROM access "
                       . "WHERE userid = $userid AND tablename = '%'") 
    or Problem ("Select of access failed: " . mysql_error ());
  $row = mysql_fetch_array ($result);
  mysql_free_result ($result);  

  if ($row)
    {  
    // we can edit all tables, so get a list of them
    GetDatabaseName ($databasename);
                           
    $result = mysql_list_tables ($databasename) 
        or Problem ("List tables failed: " . mysql_error ());
      
    while ($row = mysql_fetch_row ($result))
      {
      $table = $row [0];
      echo "<option value=\"$table\">$table\n";
      } // end of doing each row
    
    mysql_free_result ($result);
    
    }  // end of being able to edit all tables
  else
    {
    // find the tables he can edit
    $result = mysql_query ("SELECT * FROM access "
                         . "WHERE userid = $userid AND can_select = 1") 
      or Problem ("Select of access failed: " . mysql_error ());
    while ($row = mysql_fetch_array ($result))
      {
      $table = $row ['tablename'];
      echo "<option value=\"$table\">$table\n";
      } // end of doing each row
      
    mysql_free_result ($result);  
    
    } // end of being able to edit *some* tables
    
  echo "</select>\n";
  
  FormSession ();
  echo "&nbsp; &nbsp; <input Type=submit name=dump Value=\"Edit\"> </p>\n";
  echo "</form>\n";
  } // end of ShowTablesToEdit
  
function MailAdmins ($subject, $message, $link, $condition, $bbuser_id = 0)
  {
  global $control, $username, $foruminfo, $subjectrow;

  // don't do it if they don't permit it
  if (!$control ['allow_notification'])
    return;

  $forum_name = $control ['forum_name'];
  $forum_url = $control ['forum_url'];
  
  // find all admins (except ourselves - we don't need to notify ourselves
  $query = "SELECT * from bbuser "
          . "WHERE admin <> 0 "
          . "  AND $condition <> 0 "
          . "  AND (bounced IS NULL OR bounced = 0) ";
        
  if ($bbuser_id)
    $query .= "AND bbuser_id <> " . $bbuser_id;
  else    
  if ($foruminfo ['bbuser_id'])
    $query .= "AND bbuser_id <> " . $foruminfo ['bbuser_id'];
  
  $result = mysql_query ($query) 
      or Problem ("Select of admins failed: " . mysql_error ());
       
  while ($row = mysql_fetch_array ($result))
    {
    $notifyname = $row ['username'];
    $notifyemail = $row ['email'];
    
    $removal = "To edit your notification settings, please click on the link below:\n\n"
             . "  $forum_url/bbuseredit.php?action=amend&bbuser_id=" . $row ['bbuser_id'];
    
    $removal .= "\n\n";
    
    // send mail message
    
    if ($subjectrow ['section_name'] && $subjectrow ['topic_name'])
      $section_note = "This was in the section: " .
                      $subjectrow ['section_name'] .
                      " -> " .
                      $subjectrow ['topic_name'] .
                      "\n\n";
    else
      $section_note = "";

    $mailresult = mail ($notifyname . " <" . $notifyemail . ">", 
          "$subject",
          "Hi $notifyname,\n\n" 
        . "$username has $message.\n\n"
        . $section_note
        . "You can view this at:\n\n  $forum_url$link\n\n\n\n"
        . $removal
        . $control ['email_signature'],
        // mail header
        "From: " . $control ['email_from'] . "\r\n"
      . "Content-type: text/plain\r\n"
      . "X-mailer: PHP/" . phpversion()
        );
    
    if (!$mailresult)
      Problem ("An error occurred sending an email message");

    } // end of having loop
    
  mysql_free_result ($result);
  
  } // end of MailAdmins
  
function utctime ()
  {
  global $control;
  if ($control ['minuteswest'])
    $minuteswest = $control ['minuteswest'];
  else
    {
    $thetime = gettimeofday ();
    $minuteswest = $thetime ['minuteswest'];
    }
  return time () + ($minuteswest * 60); // add in time-zone correction in minutes
  } // end of utctime  

// general function for getting a count of something

function GetSQLcount ($query, $select = "SELECT count(*) FROM ")
  {
    
  $result = mysql_query ($select . $query) 
      or Problem ("Select count failed: " . mysql_error ());
  $row = mysql_fetch_row ($result);    
  $count = $row [0];
  mysql_free_result ($result);
    
  return (number_format ($count));  // return comma-delimited
  } // end of GetSQLcount

// Added because Superb.Net recompiled PHP without --enable-track-vars

/*
reset ($HTTP_GET_VARS);
while (list ($name, $value) = each ($HTTP_GET_VARS))
    $HTTP_GET_VARS [$name] = addslashes ($HTTP_GET_VARS [$name]);
    
reset ($HTTP_POST_VARS);
while (list ($name, $value) = each ($HTTP_POST_VARS))
    $HTTP_POST_VARS [$name] = addslashes ($HTTP_POST_VARS [$name]);
    
reset ($HTTP_COOKIE_VARS);
while (list ($name, $value) = each ($HTTP_COOKIE_VARS))
    $HTTP_COOKIE_VARS [$name] = addslashes ($HTTP_COOKIE_VARS [$name]);

extract ($HTTP_GET_VARS, EXTR_OVERWRITE);
extract ($HTTP_POST_VARS, EXTR_OVERWRITE);
extract ($HTTP_COOKIE_VARS, EXTR_OVERWRITE);
*/

import_request_variables ("GPC");

// turns numbers into their ordinal versions (eg. 1 becomes 1st, 23 becomes 23rd)

function ordinal ($number)
  {
    
  // 12 is 12th not 12nd.
  
  if ($number >= 10 && $number <= 20)
    $ordinal = "th";
  else
    switch (substr ($number, -1, 1))
      {
      case 1: $ordinal = "st"; break;  
      case 2: $ordinal = "nd"; break;
      case 3: $ordinal = "rd"; break;  
      default:  $ordinal = "th";
      } // end of switch
    
  return $number . $ordinal;  
  } // end of ordinal

// extended date functions
function DoExtendedDate (& $thedate)
{
// get rid of leading/trailing spaces, make lowercase
$thedate = trim (strtolower ($thedate));

// get rid of multiple spaces
$thedate = str_replace ("  ", " ", $thedate);

// if date has hyphens in it, assume already in format 2002-15-02
if (strstr ($thedate, "-"))
  return "";
  
// look for 'today' or 'tomorrow', or some shortened version of either
if (strlen ($thedate) > 2)
  {
  if ($thedate == substr ('today', 0, strlen ($thedate)))
    {
    $thedate = strftime ("%Y-%m-%d", utctime());
    return "";
    }   // end of today
  
  if ($thedate == substr ('tomorrow', 0, strlen ($thedate)))
    {
    $thedate = strftime ("%Y-%m-%d", utctime() + (60 * 60 * 24));
    return "";
    }   // end of tomorrow
  }  // end of string length > 2
  
if (strstr ($thedate, "/"))
  $items = explode ("/", $thedate);
else
  $items = explode (" ", $thedate);

if (count ($items) > 3)
  return "Too many fields in date, maximum of 3 (day/month/year)";
  
if (count ($items) < 1)
  return "Date must consist of at least the day (eg. 15)";
  
$day = trim($items [0]);

// look for alpha day name (eg. Monday)
if (ereg ("^[a-z]+$", $day) && count ($items) < 3)
  {
   $daynames = "";
   $seconds = utctime();
   // find the dates of the next 7 days
   for ($count = 1; $count <= 7; $count++) 
     {
     $daynames [strtolower (strftime ("%A", $seconds))] = strftime ("%Y-%m-%d", $seconds);
     
     // let them put in 'Thursday week'
     $daynames_week [strtolower (strftime ("%A", $seconds))] 
        = strftime ("%Y-%m-%d", $seconds + (60 * 60 * 24 * 7));
     $seconds += 60 * 60 * 24;  // onwards a day
     }
   
   // our array now has all 7 days indexed by the day name (eg. Monday)
   
    reset ($daynames);
    $count = 0;
    while (list ($dayname, $daydate) = each ($daynames))
      {
      // look for partial match - do whole lot in case of ambiguity (eg. t(hursday))
      if ($day == substr ($dayname, 0, strlen ($day)))
        {
        $founddate = $daydate;
        // and pull out a week later
        $founddate_week = $daynames_week [$dayname];
        $count++;  
        }  // end of found a match
      }  // end of trying each day
  
    if ($count == 0)
       return "Day name of \"$day\" not recognised, try 'Monday', 'Tuesday', etc.";
    if ($count > 1)
      return "Day named \"$day\" is ambiguous - please use longer name";
     
    // found one match - use the corresponding date
    
    // first see if they followed it by the word 'week'
    $word = trim($items [1]);
    if (count ($items) == 2 && $word != "")
      {
      if ($word == substr ('week', 0, strlen ($word)))
        {
        $thedate = $founddate_week;
        return "";
        }   // end of the word week (or an abbreviatio)        
      } // end of having a second word
      
    $thedate = $founddate;
    return "";
  }   // end of alpha day 

// don't let them slip in alphas or other stuff into the middle of a day
if (!ereg ("^[0-9]+$", $day))
   return "Day must consist of numbers (or 'Monday', 'Tuesday' etc.) - you supplied \"$day\"";

// get the month
if (count ($items) > 1)
  $month = trim($items [1]);
else  // no month? assume current month
  $month = strftime ("%m", utctime()); 

// get the year
if (count ($items) > 2)
  $year = trim($items [2]);
else  // no year? assume current year
  $year = strftime ("%Y", utctime()); 

// don't let them slip in alphas or other stuff into the middle of a year
if (!ereg ("^[0-9]+$", $year))
   return "Year must consist of numbers, you supplied \"$year\"";

// 2-digit year supplied? Assume current century
if ($year < 100)
  {
  $century = intval (floor (strftime ("%Y", utctime()) / 100)) * 100;
  $year = $year + $century; 
  } // end of 2-digit year

// if non-numeric month, see if we can recognise the month name, either in full or in part
if (!ereg ("^[0-9]+$", $month))
  {
  $months = array 
    (
     1 => 'january',  
     2 => 'february',  
     3 => 'march',  
     4 => 'april',  
     5 => 'may',  
     6 => 'june',  
     7 => 'july',  
     8 => 'august',  
     9 => 'september',  
    10 => 'october',  
    11 => 'november',  
    12 => 'december',  
    );  // end of array

  reset ($months);
  $count = 0;
  while (list ($monthnum, $monthname) = each ($months))
    {
    // look for partial match - do whole lot in case of ambiguity (eg. ju)
    if ($month == substr ($monthname, 0, strlen ($month)))
      {
      $foundmonth = $monthnum;
      $count++;  
      }  // end of found a match
    }  // end of trying each month

  if ($count == 0)
    return "Month named \"$month\" not a valid month name, or number";
  if ($count > 1)
    return "Month named \"$month\" is ambiguous - please use longer name";
    
  $month = $foundmonth;
  } // end of non-numeric month

$thedate = $year . "-" . $month . "-" . $day;

return "";  
} // end of DoExtendedDate

function DoExtendedDateTime (& $thedate)
{
$thedate = trim ($thedate);

// no date? given up
if ($thedate == "")
  return "";

// get rid of multiple spaces
$thedate = str_replace ("  ", " ", $thedate);
  
$items = explode (" ", $thedate);
$date = $items [0];

// extend the date
$error = DoExtendedDate ($date);
if ($error != "")
  return $error;
  
// see if we have a time, and if so, process that  
if (count ($items) > 1)
  {
  $time = $items [1];
  
  // extend the date
  $error = DoExtendedTime ($time);
  if ($error != "")
    return $error;
  } // end of having a time too
else 
  $time = "";
    
// assemble the fixed date/time    
$thedate = $date . " " . $time;
return "";  // no error
} // end of DoExtendedDateTime

function DoExtendedTime(& $thetime)
{
// get rid of leading/trailing spaces, make lowercase
$thetime = trim (strtolower ($thetime));

// no time? given up
if ($thetime == "")
  return "";

// get rid of multiple spaces
$thetime = str_replace ("  ", " ", $thetime);

// allow decimal places instead of colons
$thetime = str_replace (".", ":", $thetime);

// look for a space
$items = explode (" ", $thetime);

// first, let them enter 11 (convert to 11:00)
$time = $items [0];
if (!strstr ($time, ":"))
  $time .= ":00";

if (count ($items) > 2)
  return "Time must be in format '11', '11:45' or '11:45 am/pm'";
  
// look for am/pm  
if (count ($items) == 2)
  $am_pm = trim ($items [1]);
else
  $am_pm = "";
  
// don't let them slip in alphas or other stuff into the middle of a number
if (!ereg ("^[0-9\:]+$", $time))
   return "Time must consist of HH:MM or HH:MM:SS";

// put out hour:minute:second
$items = explode (":", trim ($time));

$hour = $items [0];
if (count ($items) > 1)
  $min = $items [1];
else
  $min = 0;
  
if (count ($items) > 2)
  $sec = $items [2];
else
  $sec = 0;
  
if ($am_pm)
  {    
  switch ($am_pm)
    {
    case "am":
    case "a":
      break;    // do nothing
      
    case "pm":
    case "p":
      if ($hour < 12) // make afternoon
        $hour += 12;
      break;  
      
    default: return "Time must consist of HH:MM am/pm or HH:MM:SS am/pm";
  } // end of switch 
  
} // end of am/pm
else
  if ($hour < 8)  // assume a time like 6:30 is 6:30 pm
    $hour += 12;

$time = $hour . ":" . $min . ":" . $sec;
$thetime = $time;
return "";  // no error
} // end of DoExtendedTime


?>
