admin.php - index

































































































































































































































































































































































































































































































































































































































































































































































































































































































































































<?php
/*
    "...almost everything I've ever heard associated with the term 
    'extreme programming' sounds like exactly the wrong way to go...
    with one exception."
      -- Donald Knuth
*/

/* ADMIN.PHP - provides a UI to do stuff, adminy stuff

This code finally has it's embarrassment quotient at a fairly low level! The 
code could be made more efficient, but will probably stay at this level for 
several more weeks (today is February 25), as it's not too bad anymore...

All Admin stuff is done via two main controlling variables:

    The GET variable "op".
    The POST variable "button".

with the GET variable of "arg" always the site's section that is being worked 
on. In typical PHP upside down fashion, the POST variables are handled first, 
which are like "major commands" such as "logout", "save" and "cancel". These 
generally "do something" and continue.

Then a few GET variables are handled, and these are like "sub commands" such 
as "edit preview". These do something and stop.

Then starts the HTML output, with either the output of any "minor commands" 
such as "delete" and then either the "main menu" or one of a possible "sub 
menus" like for when editing HTML files.

I detail this operation because once understood the code becomes easy to manage, 
whereas otherwise, it may appear a bit convoluted if not down right dumb.

There are sections here: (BOOKMARKS)

1. Includes and Data initialization. (INIT)
2. Login Rights check/Login HTML.    (LOGIN)
3. POST Input handler "buttons" clicked, like "Save", "Cancel"). (POST)
   Most of these "do something" internally and continue to the next section.
4. GET Operations handler that presents the interface for the Admin to 
   do "those somethings" handled by section 3. (GET)
5. (DISPLAY)
6. Support functions. (FUNCTIONS)

*/

define('THIS_EOL',"\n");

$GLOBALS['VERSION'] = "THIS Admin 1.3";    // gets put into configuration data

define('CONFIGNODUMP',1);        // disable shutdown function DISPLAY.PHP

include "error.php";            // always this one first
include "dat/defines.php";        // MySQL and Admin password defines

foreach (glob('inc/*.php') as $inc)    // all the "functions only" stuff
    
include($inc);

include 
"config.php";

include 
"testing.php";    // while in BETA we do this instead of one of these:
//include "mysql.php";            // database functions
//include "mysqli.php";            // database functions mysqli extension

include "html.php";
include 
"usercode.php";
include 
"display.php";
include 
"dat/messages.php";    // $messages array
include "dat/shit.php";        // Simple HTML In Templates "template engine"

if (config('nonotices'))
    
error_reporting(E_ALL E_NOTICE);

setmodule("");                // reset this (was used by debug)
_admin_config();            // set some data stuff (see near EOF)
dbstart();                // start db stuff

// (INIT)

$confirmdelete config('confirmdelete');
$testing config('testing');

// now we modify the html a bit for us, kind of a hack but we like the 
// extra visual feedback

html('confirmdelete',$confirmdelete "" " confirm delete is off");
html('testing',(config('dbtemporary')) ? "TEMPORARY MODE" '');
html('testing',($testing) ? "READONLY MODE" html('testing'));

$status '';                // what are we gonna do with all 
$html html();                //  this stuff?
$config config();
$user userstate();            // if 'testing' this returns 1
$formtitle "";            // a possisble debugging helper

if ($op == ""$op 'read';


/*
This is the system setup section; note that instead of carrying the full bulk 
of the setup code, that is used just once or twice, it is included here if 
needed here. We do something like this several more times for those functions 
that generally are rarely used. (Yes, we know such a thing is normally done by 
an INSTALL program, but as you may have guessed, we ain't normal.)
*/

if ($button == 'setup' || $button == 'write' || $button == 'test') {
    include 
'dat/setup.php';
    
setup();
    exit;
}


/* do the login/logout stuff in a logical place, like here */

// (LOGIN)

if ($user) {
    if (
$button == "logout") {
        
userlogout();
        if (!
config('testing'))            // kind of UGH!
            
$user NULL;            // must null-ify $user
    
}
    
// "We're in."
}
else {
    if (
$button == "login") {
        
$user userlogin($userid,$passwd,'admin');
        if (
$user)
            
redirect('?login');
    }
}

/* if not logged in display LOGIN template and stop */

if (!$user) {

    
$status config('dbtemporary') ? usererror($user,$userid) : "";
    if (isset(
$_GET['login']))
        
$status="<br>You need cookies enabled to login.";
    eval(
shit_show($templates['open']));
    eval(
shit_show($templates['login']));
    eval(
shit_show($templates['close']));
    exit;
}

// if testing mode, $user is 1 just so we can get in to, um, test
if ($user === 1$user '';


/* FORM input stuff that usually perform an "action" and continue */

// (POST)

if ($button == "done") {

// the done button always goes back to read

    
$op "read";
}

if (
$button == "cancel") {

// there are two kinds of cancels - the first goes back to the main page,
// the "read" op; the second goes back to the editing some things page, like 
// the "editcomments" op; we need to directly go back to the former, but need 
// to "stay in" the latter

// (multiple, or nested, modes did not exist during the first design, but 
// were added on later - the design changed and we poorly planned for that, 
// so we have to do this "known op" shuffling thing... 

// what we need is a "mode" variable that, if true, we simply stay in that 
// mode... but we have to make many changes to support that; and we could 
// not have started out with a "mode" because it did not occur to us.
// the funniest thing though? if we had used HTML BUTTONs instead of 
// INPUT SUBMITs it would be easier to make the change... and that was a 
// design planning error

    
if ($op == "export")
        
$expcmd "";
    else
    if (
$op == "editcomment")
        
$op = ($id) ? "editcomments" "read";
    else
    if (
$op == "visitors")
        ;
    else
        
$op "read";
}

if (
$button == "upload") {

    
$fileupload $_FILES['imagefile']['name'];

    if (!
$fileupload)
        
$status "please select a file to upload";

    else {
        
$dir imgdir();
        
$status uploadget("imagefile",$dir,"",$overwrite);
        if (
is_array($status)) $status $status['name']." uploaded";
    }

    
$op "editimages";
}


// save item being edited

if ($button == "save") {

    if (
$data == "")
        
$status "no data!";

    else {
        
$status "saved!";

        
$data $data THIS_EOL;

// any write fails are going to be because permissions do not allow it

        
if ($op == 'editini')
            
$r = @file_put_contents($file,$data);
        else
        if (
$op == 'editcomment')
            
$r dbputcomment($comment,$data);
        else
        if (
$op == 'editpage')
            
$r dbputpage($id,$data);
        else
        
// op == edit
            
$r dbputrecord($id,$data);

        if (!
$r$status "write failed $file";
    }
}

if (
$button == "submit") {

    if (
$data == "")
        
$status "no data!";

    else  {
        
$status "submitted!";
        
$r writerecord($data,false);
        if (!
$r$status "write failed";
    }
}

if (
$button == "page") {

    if (
$data == "")
        
$status "no data!";

    else  {
        
$status "new page!";
        
$r dbnewpage($data);
        if (
$r == -1$status "file exists error!";
        if (!
$r$status "write error!";
    }
}


if (
$button == "preview") {

    
define('ADMINNODUMP',1);            // stop dump stuff

    
$record readrecord("",$data);
    
_preview(NULL,$record,$comment);

    exit;    
// NOTE this one exits
}

if (
$button == "change password" || $button == "generate") {

    include 
'dat/setup.php';
    
password();
    exit;
}


/* URL parameter stuff that perform an "action" and then exit */

// (GET)

if ($op == "phpinfo") {

    
phpinfo();
    exit;
}

if (
$op == "edit" && $preview == 1) {

    
$record readrecord($id);
    
_preview($id,$record);
    exit;
}

if (
$op == "editcomment" && $preview == 1) {

    
$record dbreadcomment($comment);
    
_preview(NULL,$record,$comment);
    exit;
}

if (
$op == "editpage" && $preview == 1) {

    
$record readfilerecord($id);
    
_preview($page,$record,'',TRUE);
    exit;
}


/* URL parameter stuff that displays stuff and asks for things */

// (DISPLAY) (OPEN)

// THIS IS THE START OF THE OUTPUT

eval(shit_show($templates['open']));

if (
debug()) {
// this is for me to see the get/post data, #data is fixed position,
// collapse() collapses and truncates and htmlentities the arrays
// I get confused easily and seeing the data like that helps, a lot...
    
echo "<div id='data'>";
    echo 
'<tt>get: '.collapse($_GET).'</tt><br>';
    echo 
'<tt>post: '.collapse($_POST).'</tt><br>';
    echo 
"<b>op=</b>$op";
    echo 
"</div>\n";
}

if (
config('testing'))
    print 
_message('testing');
elseif (
config('dbtemporary'))
    print 
_message('temporary');


// (DELETE) item commands - all have common data so this can be collapsed 
// but only at the expense of "intertwined complexity"

if ($op == "delete") {

// FIX does not delete associated comments

    
if ($confirmdelete && !isset($_POST['yes'])) {
        
$preamble "Preparing to delete record $id...";
        
$action "delete record $id";
        eval(
shit_show($messages['confirm']));
    }
    else {
        
dbdelrecord($id);
        
$status "record $id was deleted";
        
$op "read";
    }
}

if (
$op == "deletepage") {

    if (
$confirmdelete && !isset($_POST['yes'])) {
        
$preamble "Preparing to delete page $id...";
        
$action "delete page $id";
        eval(
shit_show($messages['confirm']));
    }
    else {
        
dbdelpage($id);
        
$status "page $id was deleted";
        
$op "read";
    }
}

if (
$op == "delcomment") {

    if (
$confirmdelete && !isset($_POST['yes'])) {
        
// note that this one is different than the others as it has
        // it's own message template; we're not sure which way to go:
        // one generic 'confirm' template and extra strings here, or
        // many similar but different messages for each command and 
        // fewer strings here...
        
eval(shit_show($messages['confirm_comment']));
    }
    else {
        
dbdelcomment($comment);
        
$status "comment $comment was deleted";
        
$op = ($id) ? "editcomments" "read";
    }
}

if (
$op == "delimagefile") {

    if (
$confirmdelete && !isset($_POST['yes'])) {
        
$preamble "Preparing to delete file $file...";
        
$action "delete file $file";
        eval(
shit_show($messages['confirm']));
    }
    else {
        
$status "file $file was deleted";
        if (!
is_file($file))            // page "refresh"?
            
$status "$file not found";
        elseif (!
is_writeable($file))
            
$status "permissions do not permit";
        else
            
unlink($file);
        
$op "editimages";
    }
}


// these are the (EDIT) commands to edit an item; they return with $button 
// set to "save", "preview" or "done" (or in the case of comments, "cancel")

if ($op == "edit") {

    
$url "?arg=$arg&amp;op=edit&amp;id=$id";
    
$form['preview'] = "";
    
$data dbgetrecord($id);
    
$data htmlentities($data);

    eval(
shit_show($templates['edit']));
    eval(
shit_show($templates['helptxt']));
}

if (
$op == "editpage") {

    
$url "?arg=$arg&amp;op=editpage&amp;id=$id";
    
$form['preview'] = "";
    
$data dbgetpage($id);
    
$data htmlentities($data);

    eval(
shit_show($templates['edit']));
    eval(
shit_show($templates['helptxt']));
}

if (
$op == "editcomment") {

    
// note that op is different here; and id is the post id
    
$url "?arg=$arg&amp;op=editcomment&amp;comment=$comment&amp;id=$id";
    if (
$arg == ""$arg "(root)";
    
$form['preview'] = "";
    
$data dbgetcomment($comment);
    
$data htmlentities($data);

    eval(
shit_show($templates['editcomment']));
}

if (
$op == "editini") {

    
$url "?arg=$arg&amp;op=$op&amp;file=$file";

    
$data "";
    if (
$file) {
        
$data file_get_contents($file);
        
$data htmlentities($data);
    }

    
$HTML '';
    foreach(
_globinis() as $ini) {
        
$HTML .= "<a href='?arg=$arg&amp;op=$op&amp;file=$ini'>";
        
$HTML .= ($ini == $file) ? "<b class='sel'>$ini</b>" $ini;
        
$HTML .= "</a><br>".THIS_EOL;
    }

    eval(
shit_show($templates['inifiles']));
}

// these commands display lists of items to edit

if ($op == "editcomments") {

    
$HTML "<b>Entry $id comments</b>&nbsp;".
        
"<span id='status' style='color:red;'><b>$status</b></span>".
        
"<br><br>\n";

    
$URL "?arg=$arg&amp;op=editcomment&amp;id=$id";
    
$URLDEL "?arg=$arg&amp;op=delcomment&amp;id=$id";

    
$cmnts dblistcomments($id);
    foreach (
$cmnts as $comment) {
        
$record dbreadcomment($comment);
        
$url $URL "&amp;comment=$comment";
        
$urldel $URLDEL "&amp;comment=$comment";
        eval(
shit_html($templates['comment'],'HTML')); 
    }

    eval(
shit_show($templates['done']));
}

if (
$op == "editimages") {

    
$HTML "";
    
$template $templates['images'];
    eval(
shit_html($template));

    
$dir imgdir();
    
$files _globimages();
    
$template $templates['image'];
    foreach (
$files as $file) {
        
$urldel "?arg=$arg&amp;op=delimagefile&amp;file=$dir$file";
        list(
$w,$h) = getimagesize(imgdir().$file);
        
$size "$w x $h";
        eval(
shit_html($template));
    }

    eval(
shit_show($templates['done']));
}


// like the edit ones but more specialized

if ($op == "visitors") {
    include 
'dat/visitors.php';
}

if (
$op == 'export') {
    include 
'dat/export.php';
}


// (MAIN) (READ)

if ($op == "read") {

    
$setup '';
    if (!
defined('AD_NAME'))
        
$setup _message('setup');

    
// this sets up the data for the MENU template; the variables set
    // here in this block are referenced in the template 'menu'

    
$section = ($t config('section')) ? $t 'root';
    
$theme rtrim(config('themedir'),'/');
    if (
$theme == '')
        
$theme 'default';
    
$sections = ($arg) ? "<a href='?arg=&amp;type=$type' title='view root section'>..</a> " '';
    foreach (
config('sections') as $dir => $unused) {
        
$adir = ($arg == $dir) ? "<b>$dir</b>" $dir
        
$sections .= "<a href='?arg=$dir&amp;type=$type' title='view section'>$adir</a> ";
    }
    
$data createrecord();
     
$helptext $templates['help'];

    
$dispage $dissubmit $disgen '';        // to disable some
    
if ($type == 'page')                //  buttons
        
$dissubmit 'disabled';
    else
        
$dispage 'disabled';
    if (
config('testing'))
        
$disgen 'disabled';

    eval(
shit_show($templates['menu']));

// we are getting a little kludgey here, UGH!, but so far it's fairly easy 
// to manage

    
if (!$type$type 'all';

    
$types = array(
        
'all' => 'view all posts',
        
'draft' => 'view draft posts',
        
'page' => 'view pages'
    
);

    echo 
'<!-- admin.php  -->'.THIS_EOL;
    echo 
'<p style="float:left;margin-right:10px;"><b>Posts ';
    foreach (
$types as $a => $b) {
        echo 
"<a href='?arg=$arg&amp;type=$a' title='$b'>";
        echo (
$a == $type) ? "<u>$a</u>" $a;
        echo 
"</a> ";
    }
    echo 
'</b></p>';

    
$url "?arg=$arg&amp;op=bulkdelete";
    eval(
shit_show($templates['bulkdel']));
    echo 
'<br style="clear:left;">';

// THIS IS IMPORTANT
// there needs to be a "FROM/TO" construct added here so that NOT ALL POSTS 
// get listed! because right now, ALL POSTS are listed! this is not a problem 
// now, as the site does not have many posts, but maybe someday there will if 
// this code is actually going to be used! they are in an "auto overflow" div 
// so things are not too bad - there is code in DISPLAY.PHP/displayentries() 
// that does what we need to do so it will eventually get done...

    
echo '<!-- admin.php/POSTS  -->'.THIS_EOL;
    echo 
'<div id="posts">';
    if (
$type == 'page')
        
_pages();
    else
        
_entries();
    echo 
'</div>';

    
$HTML '';
    
$cmnts dblistcomments(0);
    if (
$cmnts) {
        
natsort($cmnts);
        
$template $templates['comment'];
        foreach(
$cmnts as $id) {
            
$comment $id;
            
$url "?arg=$arg&amp;op=editcomment&amp;comment=$id";
            
$urldel "?arg=$arg&amp;op=delcomment&amp;comment=$id";
            
$record dbreadcomment($id);
            eval(
shit_html($template));
        }
        eval(
shit_show($templates['comments']));
    }

    eval(
shit_show($templates['helptxt']));
}

if (
$op == "bulkdelete") {

    include 
'dat/bulk.php';
}

if (
$op == "showdata") {

    echo 
"<pre>";
    
ob_start('obentities');
    echo 
"\$config = "var_export($config);
    echo 
";\n\$html = "var_export($html);
    echo 
";\n\$error = "var_export(error(-1));
    echo 
";\n";
    
ob_end_flush();
    echo 
"</pre>";
}


eval(
shit_show($templates['close']));


// (FUNCTIONS)


/* _entries - display list of posts */

// $links if true means to list with links for edit and delete

function _entries($links 1) {

    
$html html();
    
$arg getvar('arg');
    
$type getvar('type');

    
$HTML '';
    
$recs dblistrecords();
    if (
$recs) {
        if (
config('sortfunc') == 'rsort' || config('reversesort'))
            
$recs array_reverse($recs);

// can't really do much to get around this url complexity, other than a 
// URL.PHP file that somehow "manages" all URLs...

        
$url "?arg=$arg&amp;type=$type&amp;op=edit&amp;id=";
        
$urldel "?arg=$arg&amp;type=$type&amp;op=delete&amp;id=";
        
$href "?arg=$arg&amp;op=editcomments&amp;id=";

        foreach (
$recs as $id) {
            
$record dbreadrecord($id);

            if (
$type == 'draft' && !isset($record['draft']))
                continue;

// need to "flag" that we found something
            
$found 1;

// UGH! count() will return 1 if argument is an integer!
            
$n count(dblistcomments($id));
            
$comments "$n comments ";
            if (
$n)
                
$comments "$n " .
                
"<a href='$href$id' title='edit comments'>" .
                
"comments</a>";

// moving to templates was a good thing as it forced us to separate all the 
// HTML from the code, but now that we have done that, the code has the 
// structure to inline the "template" like this (and its very reasonable to 
// manage "templates" this way)

// we do HTML many different ways, whichever way seems logical

            
if ($links) {
echo <<<HTML
<a href="$url$id" title="edit record $id">$id</a>
<a class="p" href="
$url$id&amp;preview=1" title="preview record $id"></a>
<a class="x" href="
$urldel$id" title="delete record $id{$html['confirmdelete']}"></a>&nbsp; $comments &nbsp;{$record['title']}
<br>

HTML;
            }
            else {
                echo 
"#$id&nbsp; <b>date:</b> {$record['date']}&nbsp; <b>title:</b> {$record['title']}<br>";

            }
        }
    }

    if (!isset(
$found))
        echo 
"no posts";
}

function 
_pages() {

    
$html html();
    
$arg getvar('arg');
    
$type getvar('type');

    if (
getvar('make') == 'pages')
        
makepagesdir();

    
$pages dblistpages();

    if (
$pages) {

        
$url "?arg=$arg&amp;type=$type&amp;op=editpage&amp;id=";
        
$urldel "?arg=$arg&amp;type=$type&amp;op=deletepage&amp;id=";

        foreach (
$pages as $page) {
            
$record dbreadpage($page);
echo <<<HTML
<a href="$url$page" title="edit page $page">$page</a>
<a class="p" href="
$url$page&amp;preview=1" title="preview page $page"></a>
<a class="x" href="
$urldel$page" title="delete page $page{$html['confirmdelete']}"></a>
{$record['title']}
<br>

HTML;
        }
    }

    else
    if (
$pages === FALSE) {
echo <<<HTML
<span style="float:left;">page directory does not exist&nbsp;</span>
<form>
<input type="hidden" name="arg" value="
$arg">
<input type="hidden" name="type" value="page">
<input type="hidden" name="make" value="pages">
<button title="make 
$arg page directory">make</button>
</form>

HTML;
    }

    else
        echo 
"no pages";
}


/* preview post (entry), page or comment */

function _preview($id$record$comment ''$page '') {

    
$html html();
    
$config config();
    
$arg getvar('arg');

    
// this uses the main code's normal display functions
    // instead of adding yet another argument in the $url used by 
    // the templates! we simulate that by:

    
$_GET['op'] = 'grok';

    
// so the preview is shown in full (so we don't have to modify 
    // anything in the display code)

    
html('menu','');            // make these go away
    
html('navmenu','');
    
html('loginout','');

    
displayhtml('head');

    if (
$page)
        
displayhtml('page','$record',$record);
    else
    if (
$comment)
        
displayhtml('comment','$record',$record);
    else
        
displayrecord($record,$id);

    if (!
config('nopreviewbox'))
        echo 
_message('preview');

    
displayhtml('end');
}


/* globules */

function _globimages() {

    
$t imgdir();
    
$files my_glob($t.'*.gif');
    
$files array_merge($files,my_glob($t.'*.jpg'));
    
$files array_merge($files,my_glob($t.'*.png'));
    return 
$files;
}

function 
_globinis() {

    
$files glob('*.ini');
    
$htm htmdir();
    
$files array_merge($files,glob($htm.'*.ini'));

    
$dirs glob($htm.'*',GLOB_ONLYDIR);
    foreach (
$dirs as $d) {
        if (
$d == 'admin' || $d == 'user') continue;
        
$files array_merge($files,glob($d.'/*.ini'));
    }
    return 
$files;
}


/* read and setup data for our use - "global" data but only used here */

// NOTE creates the global template array $templates

function _admin_config() {

    
// prepare HTML templates

    
$fs = ($htm config('htmdir')).'admin/*.html';
 
    foreach (
glob($fs) as $file) {
        
$name str_replace($htm.'admin/','',$file);
        
$name str_replace('.html','',$name);
        
// global $templates;
        
$GLOBALS['templates'][$name] = file_get_contents($file);
    }

    
// GET and POST variables used here

    
$GET = array('op','id','arg','page','comment','preview','theme',
    
'expcmd','type','file','comment','editcomment','editcomments');

    
$POST = array('button','userid','overwrite','data','passwd',
    
'deletewhere');

    
my_import_request_variables($GET,$POST);

// NOTE can also be done in a single line with our PHP Macros! (GET/POST.PHP)
}

/* my_import_request_variables - register known globals */

// This function is similar to PHP's import_request_variables() but different.
//
// Here, we have a list of known variable names (that may or may not be 
// supplied in the URL), and for those names not supplied we want them 
// created and set to empty (""). 
//
//     $gv    array of GET var names
//     $pv    array of POST var names
//    $gx    prefix for GET vars
//    $px    prefix for POST vars

function my_import_request_variables($gv$pv NULL$gx ''$px '') {

    if (
$gv)
        foreach (
$gv as $g)
            
$GLOBALS[$gx.$g] = getvar($g);
    if (
$pv)
        foreach (
$pv as $p)
            
$GLOBALS[$px.$p] = postvar($p);
}

// experimental - UGH! so much code for so little output!

function showhtml($theme '') {

    echo 
'<h4>Existing $html array data referred to by: {$html[\'value\']}</h4><pre>';

    
$html html();
    
ksort($html);
    foreach (
$html as $h => $v)
        if (!
is_array($v)) {
            
$v str_replace("\n","\n    ",wordwrap($v,120));
            echo 
"$h = ".htmlentities($v)."\n";
        }

    echo 
"\n[cached]\n";
    
$files array_keys($html['files']);
    foreach (
html('cached') as $h => $v) {
        if (
in_array($h,$files))
            continue;
        
$v str_replace("\n","\n    ",wordwrap($v,120));
        echo 
"$h = ".htmlentities($v)."\n";
    }

    echo 
'</pre>';
    echo 
'<h4>Existing CSS IDs</h4><pre>';
    
$ids = array();
    
$css file('htm/default.css');
    foreach (
$css as $s)
        if (
preg_match('/#([a-z]+) /',$s,$res))
            if (!
in_array($res[1],$ids))
                
$ids[] = $res[1];
    
sort($ids);
    foreach (
$ids as $id)
        echo 
"#$id\n";

    
$tids = array();
    if (
$theme) {
        echo 
"\ntheme $theme:\n\n";
        
$css = (array)@file('htm/'.$theme.'/default.css');
        foreach (
$css as $s)
            if (
preg_match('/#([a-z]+) /',$s,$res))
                if (!
in_array($res[1],$ids))
                    
$tids[] = $res[1];
        
sort($tids);
        foreach (
$tids as $id)
            echo 
"#$id\n";
    }

    echo 
'</pre>';
}


/*
    "The exception is the idea of working in teams and reading each 
    other's code. That idea is crucial, and it might even mask out all 
    the terrible aspects of extreme programming that alarm me."
      -- Donald Knuth
*/

?>