This is the source code for the Horizon weblog system used by Beth.

Direct any questions to dmd@3e.org. Feel free to use this code for your own nefarious purposes; I'd appreciate it if you'd acknowledge my contribution and perhaps notify me of where and how you're using my code.

-- Daniel Drucker


SQL table creation

CREATE TABLE cats (
  catname varchar(30) DEFAULT '' NOT NULL,
  catid varchar(30) DEFAULT '' NOT NULL,
  catdesc text DEFAULT '' NOT NULL,
  PRIMARY KEY (catid)
);

CREATE TABLE tidbits (
  tidid varchar(30) DEFAULT '' NOT NULL,
  tdate datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
  entry text NOT NULL,
  PRIMARY KEY (tidid)
);

CREATE TABLE catstids (
  catid varchar(30) DEFAULT '' NOT NULL,
  tidid varchar(30) DEFAULT '' NOT NULL
);


index.php

<?
/* 
   Copyright (C) 2001 Daniel M. Drucker <dmd@3e.org>
   Distributed under the terms of the GNU General Public License,
   included here by reference.
*/
?>
<HTML>
<!-- all your meme are belong to us -->
<HEAD>
  <TITLE>Just a Log</TITLE>
  <LINK HREF="justalog.css" rel="stylesheet" type="text/css">
  
</HEAD>
<? 
   
include("otherinfo.php"); 
   include(
"horizon.php");
?>

<SCRIPT LANGUAGE="JavaScript">
<!--
function checkform ( form )
{
    if (form.searchword.value == "") {
        alert( "You can't search for nothing." );
        form.searchword.focus();
        return false ;
    }
    return true ;
}
//-->
</SCRIPT>


<BODY background="store/marsambg.gif">
<center>
<a title="main view" href="index.php"><img border=0 src="store/marble2.gif"></a><br>
<h1>Just a Log</h1>
by <a href="http://www.bethroberts.com/">Beth</a>

<P><? print $headline// (from otherinfo.php) ?><P>

<table width=85% border="0" valign=top>
<tr>
<td width="25%" valign=top>
  <font face="Verdana, sans-serif" size="2">
  <form method="post" action="index.php">
  <b>log archives</b><br>
  <!-- The way month subsets are selected is only good for ONE YEAR. When the months
       wrap around back to July (in 2001), this system will need to change. In the
       meantime, I'm too lazy. -->
    <select name="tmonth" onChange="this.form.submit()">
      <option value="">Go to month... </option>
      <option value="">current entries</option>
      <option value="7">July 2000</option>
      <option value="8">August 2000</option>
      <option value="9">September 2000</option>
      <option value="10">October 2000</option>
      <option value="11">November 2000</option>
      <option value="12">December 2000</option>
      <option value="1">January 2001</option>
      <option value="2">February 2001</option>
      <option value="3">March 2001</option>
    </select>
    <input type=submit value="Go">
  </form>
  <form method="post" action="index.php" onsubmit="return checkform(this);">
    <b>search</b><br>
    <input name="searchword" type=text size=12>
    <input type=submit value="Go">
  </form>

<?
// the BOX-o-DOOM
$addy "box@bethroberts.com";

// process the form just submitted
if ($action == "sendbox")
{
    
// add IP signature to outgoing message
    
$message .= "\n\n--\n" gethostbyaddr($REMOTE_ADDR) . " is DOOMED!"

    
mail($addy"box o doom"stripslashes($message), "From: box_o_doom@bethroberts.com");
    print 
"<font face=Verdana size=-1>
           <b>Your doomed input has been received, and awaits its
           unsavory fate. Thanks!</b></font>\n"
;
    
// generate the box-o-doom HTML
} else {
    print 
"<font face=Verdana size=-1>
           <form method=post> <input type=hidden name=action value=sendbox>\n
           <b>box o doom</b><br>\n
           <input type=text name=message size=12>\n
           <input type=submit value=send>\n
           </form></font>"
;
}
print 
$otherpages;                           // (from otherinfo.php)
?>
<p>
</font>
</td>
<td valign=top>
<font face="Verdana, sans-serif" size="2">
  
<?
  
    $count 
10;   // how many entries per page
    
    /* 
        Now we're in the main part of the page. The first thing
        we need to check for is if we're in a special case, such
        as a view of a particular month (via the dropdown box),
        category (by clicking on one), a permalink, or a search
        (via the search box). If so, we give the user the option
        of returning to the main view:
    */
    
if ($catchoice or $tmonth or $onetid or $searchword)
        print (
'[ Viewing a subset. <a href="index.php">Go to main view</a>? ]<P>');

    
// if we haven't used the 'more entries' link, the start-value
    // for the SELECT won't be defined, so we need to define it.
    
if (! $start$start 0;
    
    
// all the real work happens here (see horizon.php)
    
getTidbits($start,$count,$catchoice,$tmonth,$onetid,$searchword);
    
    print 
"<p>&nbsp;</p>
           To see archives of this log by month, use the drop-down box
           in the left column (near the top of this page.)  In addition, 
           a <a href=allentries.php>complete archive</a> is available.<p>\n"
;
  
    print 
$ifyoucare;    // (from otherinfo.php)
  
  
?>
  <p>
  <a href="admin.php?action=create"><img src="create.gif" border=0></a>
  </font>
</td>
</tr>
</table>
</center>


<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;
... <a href="http://www.bethroberts.com/">beth@3e.org</a>
</BODY>
</HTML>

horizon.php

<?

/* 
   Copyright (C) 2001 Daniel M. Drucker <dmd@3e.org>
   Distributed under the terms of the GNU General Public License,
   included here by reference.
*/


require("/home/threee/3e.org/password.inc");
@
mysql_connect("localhost","threee",$password);
@
mysql_select_db("threee");

/* 
    Only these tags are permitted in an entry, to avoid breaking
    the entire page with one bad apple. (Consider what would happen
    if an entry contained a </TABLE> or </DIV> tag.)
*/

$allowedtags="<strike>,<s>,<a>,<img>,<i>,<br>,<b>,<u>,<em>,<strong>,<font>,<pre>,<ul>,<li>,<ol>,<blockquote>,<p>,<center>";

function 
getCats($tidid="")
{
    
/* 
       This function does double duty. If called with a $tidid
       argument, returns a list of categories that the tidid 
       belongs to. If called with no argument, returns ALL 
       category names.
    */
    
    
$categories = Array(); // be an array EVEN IF EMPTY, to avoid errors later
    
    // If we're just looking for the list of categories, this is all we need:
    
$query "SELECT COUNT(*)     AS count,
                     cats.catname AS catname,
                     cats.catid   AS catid,
                     cats.catdesc AS catdesc 
              FROM cats,catstids
              WHERE cats.catid = catstids.catid "
;
    
    
// If, OTOH, we're looking for the categories an entry belongs to,
    // we extend the query:
    
if ($tidid$query .= " AND catstids.tidid = '$tidid' ";

    
// In any case, we want the results to be in alphabetical order:
    
$query .= " GROUP BY cats.catid ORDER BY cats.catname";
    
$result mysql_query($query);
    
    
// dump the results of the SELECT into an array and return it.
    
while ($row mysql_fetch_array($result)) $categories[] = $row;
    return(
$categories);
}


function 
getTidbits($start=0,$count=10,$category="",$tmonth="",$onetid="",$searchword="")
{
    
// This is the meat of the entire system.

    
if (! $onetid// the normal case
    
{
        
$query "SELECT tidbits.tidid as tidid,tdate,entry FROM tidbits ";

        
// if looking at one category, we need this table too
        
if ($category$query .= " ,catstids "

                                  
// don't know which of next three lines
        
$query .= " WHERE 1=1 ";  // will be true, so need a base WHERE 
                                  // to allow the AND to come after it

        // month() is a MySQL function, 'LIKE' is for the searchbox
        
if ($tmonth)     $query .= " AND month(tdate) = $tmonth ";
        if (
$searchword$query .= " AND entry LIKE \"%$searchword%\" ";

        
/*
            The next line bears some examination. Understand this, and
            you understand how relational databases work. This is an
            SQL join. We have a category we're seeking ($category). 
            << catid = $category >> retrieves all the catid-tidid rows
            from catstids (the category-tidbit relationship table)...
            so now we have a list of ALL tidids in a particular category.
            Then, << catstids.tidid = tidbits.tidid >> establishes the
            relation (join) we wish to make. We want to retrieve all rows
            from tidbits where the tidid of that row is found in the list
            we just made from catstids. 
        */
        
if ($category$query .= " AND catid = '$category' AND catstids.tidid = tidbits.tidid ";

        
// reverse order by tidid, which is really just the date
        
$query .= " ORDER BY tidid DESC ";
        
        
// subset views aren't page-at-a-time
        
if (! ($tmonth or $category or $searchword)) $query .= " LIMIT $start,$count ";
    }
    
    else if (
$onetid == "everything"// archive edition (see allentries.php)
    

            
$query "SELECT tidid,tdate,entry FROM tidbits ORDER BY tidid DESC";
    } 
    
    else  
// permalink support (if we're here, $onetid is a tidid)
    
{
            
$query "SELECT tidid,tdate,entry FROM tidbits WHERE tidid = '$onetid'";
    }

    
$result mysql_query($query);
    
    while(list(
$e_tidid,$e_tdate,$e_entry) = mysql_fetch_row($result))
    {
        print 
"<div class=tidbit>[ ";
        foreach (
getCats($e_tidid) as $iter)
        {
            print 
'<a title="view all in category \''
                  
$iter['catname']
                  . 
'\'" href=".?catchoice='
                  
$iter['catid']
                  . 
'">'
                  
$iter['catname'] . '</a> ';
        }
        print 
"]\n&nbsp;<font size=-2>" 
              
substr($e_tdate,0,10)
              . 
'</font>
                 <a title="edit tidbit" 
                    style="TEXT-DECORATION: none" 
                    href="admin.php?action=change&tidid='
              
strip_tags($e_tidid,$allowedtags)
              . 
'">&#8230;</a>&nbsp;
                 <a title="permanent URL for this entry" 
                    style="TEXT-DECORATION: none"
                    href="index.php?onetid='
              
$e_tidid 
              
'"><IMG border=0 src=blink.gif></a><br>'
              
$e_entry
              
"\n</div>\n<p>\n\n";
    }
    
    if (! (
$tmonth or $category or $onetid or $searchword)) // subset views aren't page-at-a-time
    
{
        
// make the forward and back links
        
print '[<a href=".?start=' 
              
. ($start $count)
              . 
'">&lt;&lt;&lt;</a> more entries ';

        if (
$start $count >= 0)
        {
            print 
'<a href=".?start=' 
                  
. ($start $count
                  . 
'">&gt;&gt;&gt;</a> ';
        }
        
        print 
"]";
    }
    
}
 
?>





admin.php

<?

/* 
   Copyright (C) 2001 Daniel M. Drucker <dmd@3e.org> except where noted.
   Distributed under the terms of the GNU General Public License,
   included here by reference.
*/


/* 
   Code from http://www.zend.com/zend/tut/authentication.php
   Copyright (C) 2000 - 2001 by Zend Technologies Ltd. 
*/

$auth false// Assume user is not authenticated 
if (isset( $PHP_AUTH_USER ) && isset($PHP_AUTH_PW)) { 
    
$filename '/home/threee/.htpasswd'
    
$fp fopen$filename'r' ); 
    
$file_contents fread$fpfilesize$filename ) ); 
    
fclose$fp ); 
    
$lines explode "\n"$file_contents ); 
    foreach ( 
$lines as $line ) { 
        list( 
$username$password ) = explode':'$line ); 
        if ( 
$username == "$PHP_AUTH_USER" ) { 
            
$salt substr$password ); 
            
$enc_pw crypt$PHP_AUTH_PW$salt ); 
            if ( 
$password == "$enc_pw" ) { 
                
$auth true
                break; 
            } 
        } 
    } 


if ( ! 
$auth ) { 
    
header'WWW-Authenticate: Basic realm="Horizon"' ); 
    
header'HTTP/1.0 401 Unauthorized' ); 
    echo 
'This is the administrative interface; authorization is required.'
    exit; 


/* 
   END code from Zend; remaining code (C) 2000 - 2001 Daniel M. Drucker
*/

?> 

<HTML>
<HEAD>
  <TITLE>Just a Log - Editing Interface</TITLE>
  <LINK HREF="justalog.css" rel="stylesheet" type="text/css">
</HEAD>
<BODY background="store/marsambg.gif">
<center>
<a title="main view" href="index.php"><img border=0 src="store/marble2.gif"></a><br>
<h1>Just a Log - Editing Interface</h1><p>
<?
include("horizon.php");
        
switch(
$action)
{

// I use the variable $iter as a placeholder for ITERating 
// over values of an array in foreach statements

///////////////////////////////////////////////////////////////
    
case "change":
        
// DISPLAY the form to edit or delete a tidbit
        
$result mysql_query("SELECT entry FROM tidbits WHERE tidid = '$tidid'");
        list(
$entry) = mysql_fetch_array($result);
        
$incats = Array();
        foreach (
getCats($tidid) as $iter)
        {
            
// make $incats array of categories tidbit is in
            
$incats[] = $iter['catid'];
        }
        
        print 
'<h3>Editing a tidbit.</h3>
               <a href="admin.php?action=do_delete&deletetid='
               
$tidid
               
'">delete</a>
                  <br>
                  <form method=post action="admin.php?action=do_edit&edittid='
               
$tidid
               
'"><font color=red size=+1>Editing '
               
$tidid
               
'</font><br><input type=hidden name=tidid value="'
               
$tidid
               
'"><select multiple size=24 name="category[]">';
        
        foreach (
getCats() as $iter)  // create the category select listbox
        
{
            print 
'<option ';
            
            
// here's what $incats was for; to retain current categorization:
            
if (in_array($iter['catid'],$incats)) print ' selected '
            
            print 
'value="' 
                  
$iter['catid'
                  . 
'">' 
                  
$iter['catname'
                  . 
"</option>\n";
        }
        print 
'</select>
               <textarea cols=60 rows=24 name="tidtext">'
               
$entry
               
'</textarea><input value="Save" type="submit"></form>';
        
        break; 
// end of case "change"

///////////////////////////////////////////////////////////////        
    
case "create":
        
// adding a new entry
        
print '<h3>Creating a new tidbit.</h3>
               <form action="admin.php?action=do_create" method="post">
               <select multiple size=24 name="category[]">'
;
              
        foreach (
getCats() as $iter)  // create the category select listbox
        
{
            print 
'<option value="' 
                  
$iter['catid'
                  . 
'">' 
                  
$iter['catname'
                  . 
"</option>\n";
        }
        print 
'</select><textarea cols=60 rows=24 name="tidtext"></textarea>
               <input type="submit" value="Add New Entry"></form>'
;
        break; 
// end of case "create"

///////////////////////////////////////////////////////////////        
    
case "do_edit":
        
// perform the edit operation
        
        // default cat is "random thoughts"
        
if (! $category$category[] = "2000-10-06T14:57:07Z"
        
        
// delete current categorization, create new ones
        
mysql_query("DELETE FROM catstids WHERE tidid = '$tidid'");
        foreach (
$category as $iter)
        {
            
mysql_query("INSERT INTO catstids (catid,tidid) VALUES ('$iter', '$tidid')");
        }
        
// alter the text
        
$query "UPDATE tidbits SET entry='$tidtext' WHERE tidid='$tidid'";
        
mysql_query($query);
        print 
"Update successful.";
        break; 
// end of case "do_edit"

///////////////////////////////////////////////////////////////
    
case "do_create":
        
$tidid date("Y-m-d\TH:i:s");
        
$tdate date("Y-m-d H:i:s");
           
        
        
// add to catstids
        
        // default cat is "random thoughts"
        
if (! $category$category[] = "2000-10-06T14:57:07Z"
        foreach (
$category as $iter)
        {
            
mysql_query("INSERT INTO catstids (catid,tidid) VALUES ('$iter', '$tidid')");
        }
        
        
// add to tidbits
        
$query "INSERT INTO tidbits (tidid,tdate,entry) VALUES ('$tidid', '$tdate', '$tidtext')";
        
mysql_query($query);
        print 
'Posted.';
        break; 
// end of case "do_create"

///////////////////////////////////////////////////////////////    
    
case "do_delete":
        
mysql_query("DELETE FROM tidbits  WHERE tidid = '$deletetid'");
        
mysql_query("DELETE FROM catstids WHERE tidid = '$deletetid'");
        print 
"$deletetid deleted";
        break; 
// end of case "do_delete"

///////////////////////////////////////////////////////////////        
    
default:
        print 
"something bad happened, here's what I know:<br><br>\n\n";
        
phpinfo();
}

?>

</center>


<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;
... <a href="http://www.bethroberts.com/">beth@3e.org</a>
</BODY>
</HTML>

allentries.php

<HTML>
<HEAD>
  <TITLE>Just a Log</TITLE>
  <LINK HREF="justalog.css" rel="stylesheet" type="text/css">
</HEAD>
<? 
   
include("horizon.php");
?>

<BODY background="store/marsambg.gif">
<center>
<a title="main view" href="index.php"><img border=0 src="store/marble2.gif"></a><br>
<h1>Just a Log - Archive Edition</h1>
by <a href="http://www.bethroberts.com/">Beth</a>

<P>Every entry, finish to start.<P>
</center>

  <font face="Verdana, sans-serif" size="2">
  <?  
    getTidbits
(0,99999,$catchoice,$tmonth,"everything");
    print 
"<p>&nbsp;<p>\n";
  
?>
  </font>

<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;
... <a href="http://www.bethroberts.com/">beth@3e.org</a>
</BODY>
</HTML>

category.php

<?
/* 
   Copyright (C) 2001 Daniel M. Drucker <dmd@3e.org>
   Distributed under the terms of the GNU General Public License,
   included here by reference.
*/
?>
<HTML>
<HEAD>
  <TITLE>Just a Log - Guide to Categories</TITLE>
  <LINK HREF="justalog.css" rel="stylesheet" type="text/css">
  
</HEAD>
<? 
   
include("horizon.php");
?>

<body background="store/marsambg.gif">
<center>
<img src="store/marble2.gif"><h2>Guide to Categories</h2>
<hr noshade>
</center>
Just a little bit of info to sort out what the categories that I use in 
<a href="index.php">my log</a> are supposed to represent. 
Many of these are self-explanatory.
<hr noshade>
<table border=0 cellpadding=10 cellspacing=0 width=75%>
<?
   
// for each category, display name as link, number of entries, description
   
   
foreach (getCats() as $iter)
   {
      print 
'<tr><td width=5%></td><td valign=top width=15%>
             <a href="index.php?catchoice='
            
$iter['catid'
            . 
'">' 
            
$iter['catname'
            . 
'</a><br>(' 
            
$iter['count'
            . 
" entries)</td>\n"
            
'<td valign=top>' 
            
$iter['catdesc'
            . 
"</td></tr>\n";
   }
   
?>
</table>

<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;
... <a href="http://www.bethroberts.com/">beth@3e.org</a>
</BODY>
</HTML>

otherinfo.php

<?

$headline 
"<i>Life does not consist mainly - or even largely of facts and happenings. <br>It
consists mainly of the storm of thoughts that is forever blowing through
one's head.</i><br><font size=-1>Mark Twain</font>"
;

$ifyoucare "<small>P.S. In case you care, the list of other weblogs above is mainly for me to just keep track of the ones I think I might want to go back and read some more. They are not necessarily all my favorites or anything (though some certainly are). Some of them I don't know enough about to really judge. I'm sure I'll shuffle them around periodically and remove some, and add more, and whatnot. 
</small>"
;

$otherpages '<p><b>My other pages:</b>
<br>
<a href="http://www.bethroberts.com/">Home</a><br>
<a href="http://www.bethroberts.com/about.html">About</a><br>
<a href="http://www.bethroberts.com/idiolect.html">Idiolect</a><br>
<a href="http://www.bethroberts.com/writings.html" >Writings</a><br>
<a href="category.php">Categories</a><br>
<a href="source.php">Weblog code</a><br>
<a href="http://www.bethroberts.com/cwhumor.html">Cow Orker Humor</a><br>
<p>
<b>Some other weblogs:</b>
<br>
<a href="http://www.davidchess.com/words/log.html">David Chess</a><br>
<a href="http://www.sevencrabrangoon.com/index.htm">Sevencrabrangoon</a><br>
<a href="http://www.eod.com/">An Entirely Other Day</a><br>
<a href="http://www.lileks.com/bleats/index.html">The Bleat</a><br>
<a href="http://www.stevewhite.org/log/current/index.htm">Plurp</a><br>
<a href="http://www.aigeek.com/entropy/">aigeek: Entropy</a><br>
<a href="http://www.girlhacker.com/log.html">Girlhacker</a><br>
<a href="http://world.std.com/~emg/blogger.html">Follow Me Here</a><br>
<a href="http://www.whalley.org/cgi-bin/blog.cgi">Whalley.org</a><br>
<a href="http://www.genehack.org/">Genehack</a><br>
<a href="http://www.almostcool.org/cs/index.html">Come to My Senses</a><br>
<a href="http://www.memepool.com/">Memepool</a><br>
<a href="http://www.larkfarm.com/weblog.asp">Mike (of Larkfarm)</a><br>
<a href="http://torrez.org/">Torrez</a><br>
<a href="http://www.ratbastard.org">Ratbastard</a><br>
<a href="http://www.syrup.org/">Mirror, Mirror</a><br>
<a href="http://www.metagrrrl.com/">Metagrrrl</a><br>
<a href="http://www.camworld.com/">Camworld</a><br>
<a href="http://nowthis.com/log/">Now This</a><br>
<a href="http://baylink.pitas.com/">Baylink</a><br>
<a href="http://www.cramper.com/log/log.html">Camper\'s log</a><br>
<a href="http://joel.editthispage.com/">Joel on Software</a><br>
<a href="http://www.kitschbitch.com/index.html">kitschbitch</a><br>
<a href="http://www.uncorked.org/medley/">Medley</a><br>
<a href="http://www.sixfoot6.com/">Sixfoot6</a><br>
<a href="http://rc3.org/">rc3.org</a><br>
<a href="http://rubberducky.nu/girl/breasts/">The Breast Chronicles</a><br>
<a href="http://www.xplane.com/xblog/">Xblog</a><br>
<a href="http://bitch.shutdown.com/">The Misanthropic Bitch</a><br>
<a href="http://twernt.com/weblog/index.php3">Twernt</a><br>
<a href="http://ptypes.pitas.com/">Personality Types</a><br>
<a href="http://www.frykitty.com/">Frytopia</a><br>
<a href="http://www.50cups.com/strange/default.asp">strange brew</a><br>
<a href="http://www.harrumph.com/">harrumph!</a><br>
<a href="http://www.robotwisdom.com/">Robot Wisdom</a><br>
<a href="http://www.evhead.com/">evhead</a><br>
<a href="http://booknotes.weblogs.com/">booknotes</a><br>
<a href="http://viewfromtheheart.editthispage.com/">ViewFromTheHeart</a><br>
<a href="http://www.loony.org/">Loony.org</a><br>
<a href="http://wmf.editthispage.com/">Hack the Planet</a><br>
<a href="http://www.wwa.com/~dhartung/weblog/">Lake Effect</a><br>
<a href="http://www.gumbopages.com/looka/">Looka</a><br>
<a href="http://www.slumberland.org/">Slumberland</a><br>
<a href="http://www.geekish.com/weblog/blogger.html">Geekish</a><br>

<p>
<b>Discussion sites:</b>
<br>
<a href="http://www.edgecase.org/">edgecase</a><br>
<a href="http://www.metafilter.com/">Metafilter</a><br>
<a href="http://boards2.parentsplace.com/cgi-bin/boards/atheist">Atheist Parenting</a><br>
<a href="http://www.kuro5hin.org/">kuro5hin</a><br>
<p>
<b>Other Reading:</b>
<br>
<a href="http://catless.ncl.ac.uk/Risks/index.21.html">Risks digest</a><br>
<a href="http://www.useit.com/alertbox/">Jakob Neilsen\'s Alertbox</a><br>
<a href="http://www.salon.com/">Salon</a><br>
<a href="http://commons.somewhere.com/rre/">Red Rock Eater Digest</a><br>'
;
?>


 
 
 
  ... webmaster@3e.org