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&op=edit&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&op=editpage&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&op=editcomment&comment=$comment&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&op=$op&file=$file";
$data = "";
if ($file) {
$data = file_get_contents($file);
$data = htmlentities($data);
}
$HTML = '';
foreach(_globinis() as $ini) {
$HTML .= "<a href='?arg=$arg&op=$op&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> ".
"<span id='status' style='color:red;'><b>$status</b></span>".
"<br><br>\n";
$URL = "?arg=$arg&op=editcomment&id=$id";
$URLDEL = "?arg=$arg&op=delcomment&id=$id";
$cmnts = dblistcomments($id);
foreach ($cmnts as $comment) {
$record = dbreadcomment($comment);
$url = $URL . "&comment=$comment";
$urldel = $URLDEL . "&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&op=delimagefile&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=&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&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&type=$a' title='$b'>";
echo ($a == $type) ? "<u>$a</u>" : $a;
echo "</a> ";
}
echo '</b></p>';
$url = "?arg=$arg&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&op=editcomment&comment=$id";
$urldel = "?arg=$arg&op=delcomment&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&type=$type&op=edit&id=";
$urldel = "?arg=$arg&type=$type&op=delete&id=";
$href = "?arg=$arg&op=editcomments&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&preview=1" title="preview record $id"></a>
<a class="x" href="$urldel$id" title="delete record $id{$html['confirmdelete']}"></a> $comments {$record['title']}
<br>
HTML;
}
else {
echo "#$id <b>date:</b> {$record['date']} <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&type=$type&op=editpage&id=";
$urldel = "?arg=$arg&type=$type&op=deletepage&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&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 </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
*/
?>