Store Administration
PHP Code Examples
[Download]   [Execute]
<?php include '../util.inc'; 

// The administrative password.
$admin_pwd = 'frog legs';
// Note:  I don't mind that you found out the administrative password by
// listing this file.  Feel free to use it to see how the program works.
// But keep in mind the perils of letting folks list your code.

// Specialized start function
function frogstart($msg, $red = FALSE) {
	if($red)
		$style = 'color: red; font-weight: bold;';
	else
		$style = 'color: green; font-style: italic;';

	start('Frogbreath Administration');
	echo <<<END
		<div style="margin-left: 20px; $style">
		<img src="../imgs/frog.gif" 
			style="vertical-align: middle;">$msg</div>
END;
}

// Open a form which we will process.  Creates the form tag with the
// correct action.
function start_form() {
	$me = $_SERVER['SCRIPT_NAME'];

	return "<form action=\"$me\" method=\"POST\">\n";
}

session_start();

// Take care of a logout first.
if($_REQUEST['oper'] == 'logout') $_SESSION['loggedin'] = 0;

// Check for a login request, and verify the password.
$badlog = 0;
if($_REQUEST['LOGIN']) {
	if($_REQUEST['apass'] == $admin_pwd)
		$_SESSION['loggedin'] = 1;
	else {
		$_SESSION['loggedin'] = 0;
		$badlog = 1;
	}
}

// See that the user is logged in, and generate a login page otherwise.
if(!$_SESSION['loggedin']) {
	if($badlog)
		frogstart('Password Incorrect', TRUE);
	else
		frogstart('Please Log In');

	echo start_form(), <<<LTAB
		Administrative Password: <input type="password" name="apass">
		<br><input type="submit" name="LOGIN" value="LOGIN">
		</form>
LTAB;

	exit;
}

// Tell if it's name-ish (letter then and alpha, _ is a flat letter).
function goodname($n) {
	return preg_match('/^[A-Za-z_][A-Za-z_0-9]*$/', $n);
}

// Tell if it's numeric.
function numeric($n) {
	return preg_match('/^[0-9]*$/', $n);
}

// Tell if it contains no quotes or backtic and all printable.
function quoteless($n) {
	return preg_match('/^[ !#-&(-\[\]-~]*$/', $n);
}

// Get the query variables for creating a new user.  Checks them and
// returns a non-empty string error message if there is a problem.
function getnewudata()
{
	$vars = array('newacct' => 'Account name',
		      'passwd' => 'Password',
		      'newname' => 'Human name',
		      'newdust' => 'New dustbunny balance', 
		      'newjelly' => 'New jellybean balance');
	foreach($vars as $v => $d) {
		global ${$v};
		${$v} = $_REQUEST[$v];
		if(!quoteless($v)) return "$d contains quotes.";
	}
	if(!goodname($newacct)) return "Bad account name.";
	if(!numeric($newdust) || !numeric($newjelly)) 
		return "Balances must be numeric and non-negative.";
	return '';
}

// Return a string which is a correct link back to ourselves, with the
// operation set to $func.
function gen_link($func, $content, $sel='') {
	$me = $_SERVER['SCRIPT_NAME'];

	if($sel) $sel = "&sel=$sel";

	return "<a href=\"$me?oper=$func$sel\">$content</a>";
}

// This class represents the set of records which is the main part of the
// displayed page.  The class keeps track of the current location in the
// database, scroll direction, number of records shown, and other such
// things.  It will generate the HTML table when needed.
class recd_viewer {
	// Table display settings and flags.  Mostly pass info from the
	// database search to the HTML display of the results.
	private $up_arrow = 1;		// Display the up arrow.
	private $down_arrow = 1;	// Display the down arrow.
	private $do_plus = 1;		// Display the plus sign.
	private $do_minus = 1;		// Display minus sign.
	private $no_records = 0;	// No records in search.
	private $num_recs;		// Number of records to display.
	private $db_error = 0;		// Database query error.
					// Other flags are meaningless.
	private $search_handle;		// Refers to the search result.
	private $rev = 0;		// Reverse row fetch numbers.
	private $top_line_rec_no = 0;	// Index of recd on top line.
	private $handle;		// Database handle.
	private $dir;			// Display direction, up or down.
	private $message = '';		// Error message.

	// Information kept in the session.
	private $firstdisp;		// First key displayed last time.
	private $lastdisp;		// Last key displayed last time.
	private $dispwidth;		// Number of items to display.
	private $restriction;		// Display search restriction.

	// Construct.  Load information from the session, or defaults.
	function __construct($handle, $dir)
	{
		$this->handle = $handle;
		$this->dir = $dir;

		// If this is not a new session, set defaults.
		if(array_key_exists('firstdisp', $_SESSION)) {
			// Get stuff from the session.
			foreach(array('firstdisp', 'lastdisp', 
				      'dispwidth', 'restriction') as $v) 
				$this->{$v} = $_SESSION[$v];
		} else {
			// Defaults.
			$this->restriction = $this->firstdisp = 
				$this->lastdisp = '';
			$this->dispwidth = 5;
		}

		//echo "FROOB:";
		//foreach(array('firstdisp', 'lastdisp', 
		//	      'dispwidth', 'restriction') as $v) 
		//	echo " $v = [" . $this->{$v} . "]";
	}

	// Put the session things back.  This should be done in the destructor,
	// but that doesn't work.  Don't know if this is a bug or a
	// feature camouflaged as a bug.
	function over()
	{
		foreach(array('firstdisp', 'lastdisp', 'dispwidth', 
			      'restriction') as $v) 
			$_SESSION[$v] = $this->{$v};
	}

	// Get record $i from the top of the table.
	private function get_rec($i)
	{
		if($this->rev) $i = $this->top_line_rec_no - $i;
		return pg_fetch_row($this->search_handle, $i);
	}

	// Find the restriction.
	function getres()
	{
		return $this->restriction;
	}

	// Modify the restriction.
	function setres($r)
	{
		$this->restriction = $r;
	}

	// Get the ilike restriction.  Adds the indication conjunction, but
	// just returns empty string if there is no restriction
	private function get_restr($conn)
	{
		if(!$this->restriction) return '';
		return "$conn humanname ILIKE '%" . $this->restriction . "%'";
	}

	// Get the error message.
	function message()
	{
		return $this->message;
	}

	// Set the search from the $firstdisp in $dir
	private function set_search()
	{
		if($this->dir == 'up')
		{
			// Settings for upwards search.
			$sortdir = 'desc';
			$selcomp = '<=';
			$this->rev = 1;
		}
		else
		{
			$sortdir = 'asc';
			$selcomp = '>=';
			$this->rev = 0;
		}

		// Find the first (last) account name.
		$dat = @pg_exec($this->handle, 
				"SELECT acctname FROM accounts " .
				$this->get_restr('WHERE') .
				" ORDER BY acctname $sortdir LIMIT 1");
		if(!$dat) {
			$this->db_error = 1;
			return;
		}

		if(pg_numrows($dat) == 0) {
			$this->no_records = 1;
			$this->up_arrow = $this->down_arrow =
				$this->do_plus = $this->do_minus = 0;
			return;
		}

		// Save the first (last) key.
		list($extreme_key) = pg_fetch_row($dat, 0);

		// If there is no position specified, set it to the 
		// top of the file.
		if(!$this->firstdisp) $this->firstdisp = $extreme_key;

		// Now, do a search for the records we will display,
		// plus one more.  
		$dispp = $this->dispwidth + 1;

		// Do the select first time.
		$dat = @pg_exec($this->handle, 
			"SELECT * FROM accounts " .
			"WHERE acctname $selcomp '" . $this->firstdisp . "' " .
				$this->get_restr('AND') . ' ' .
			"ORDER BY acctname $sortdir " .
			"LIMIT $dispp");
		if(!$dat) {
			$this->db_error = 1;
			return;
		}

		// Set the flags based on the query results.

		// Number of records and flags based thereon.
		$numrec = pg_numrows($dat);
		$this->no_records = ($numrec == 0);
		$this->num_recs = min($numrec, $this->dispwidth);

		// See where to top of the display is, and get the key for 
		// that record.
		if($this->dir == 'up') 
			$this->top_line_rec_no = $this->num_recs - 1;
		else
			$this->top_line_rec_no = 0;

		// Check for the up and down arrows.
		list($search_start) = pg_fetch_row($dat, 0);
		if($this->dir == 'up') 
		{
			// If we started at the bottom, can't go down.
			$this->down_arrow = ($extreme_key != $search_start);

			// If we couldn't fill quota, don't go up.
			$this->up_arrow = ($numrec >= $dispp);
		}
		else
		{
			// If we started at top, can't go up.
			$this->up_arrow = ($extreme_key != $search_start);

			// If we couldn't fill quota, don't go up.
			$this->down_arrow = ($numrec >= $dispp);
		}

		$this->search_handle = $dat;
		list($this->firstdisp) = $this->get_rec(0);
		list($this->lastdisp) = $this->get_rec($this->num_recs - 1);
	}

	// Clear position.
	function clearpos()
	{
		$this->firstdisp = '';
	}

	// Scroll down
	function down()
	{
		$this->firstdisp = $this->lastdisp;
	}

	// Adjust the display size.
	function adjwidth($n)
	{
		$this->dispwidth += $n;
		if($this->dispwidth < 1) $this->dispwidth = 1;
	}

	// Generate the restriction message, if any.
	private function gen_restr()
	{
		if($this->restriction) {
			echo "<p>", gen_link('clrres', '[Unrestricted]'),
				'<span style="color: #ffa500; ',
				'font-style: italic;"> Display ',
				'restricted to names like ',
				htmlspecialchars($this->restriction),
				"</span><p>\n";
		}
	}

	// Find an indicate account and move the displayed location there.
	function find($srcacct) {
		// Find the name.
		$dat = @pg_exec($this->handle, 
				"SELECT acctname FROM accounts " .
				"WHERE acctname <= '$srcacct' " . 
				$this->get_restr('AND') . 
				" ORDER BY acctname DESC LIMIT 1");

		// See what happened
		if(!$dat)
			// Didn't work.
			$this->message = "Search for account $srcacct ".
				"failed: ". 
				htmlspecialchars(pg_errormessage($this->handle));
		else {
			// Did we find anything?
			$n = pg_numrows($dat);
			if($n == 0) {
				// We didn't
				$this->message = "Account $srcacct not found.";
				$this->firstdisp = '';
			}
			else {
				// Move to the record, or near it.
				list($k1) = pg_fetch_row($dat, 0);
				if($k1 != $srcacct)
					$this->message = "Account $srcacct ".
						"not found.";
				$this->firstdisp = $k1;
			}
		}
	}

	// Plus or minus link for the table.
	private function pml($sign)
	{
		return gen_link($sign, "<img src=\"../imgs/$sign.gif\" " .
				'border=0 align="middle">');
	}

	// Show a table of the indicated display search.
	function gen() {
		// Perform the serch.

		// Set display in the indicated direction.  If that doesn't
		// fill the display, we'll try it again other way.
		$this->set_search();
		if($this->num_recs < $this->dispwidth)
		{
			if($this->dir == 'up')
				$this->dir = 'down';
			else {
				$this->firstdisp = $this->lastdisp;
				$this->dir = 'up';
			}
			$this->set_search();
		}

		$this->do_minus = ($this->num_recs > 3);
		$this->do_plus = ($this->num_recs >= $this->dispwidth);

		if($this->db_error)
			$this->message = "Query error: " . 
				htmlspecialchars
					(pg_errormessage($this->handle));
		else if($this->no_records) {
			if($this->restriction)
				$this->message = "No accounts contain " . 
					htmlspecialchars($this->restriction) .
					'.';
			else
				$this->message = "No accounts.";
		}

		echo "<table>\n";

		// Headers.
		echo "<tr>";
		foreach(array("Operations", "Account", "Passwd", "Name",
			      "Dustbunnies", "Jellybeans") as $head)
			echo "<td><b>$head</b></td><td></td>";
		echo "</tr>\n";
	
		if($this->up_arrow) {
			echo '<tr>';
			for($i = 12; $i--;) echo '<td></td>';

			echo '<td rowspan=2 align="center">', gen_link('up',
				'<img src="/~bennet/icons/up.gif" border=0>'),
				"</td></tr>";
		}

		// Data.
		$num = $this->num_recs;
		for($i = 0; $i<$num; ++$i) {
			$row = $this->get_rec($i);
			$ncol = count($row);

			echo "<tr>";
			echo "<td>", gen_link('del', '[Delete]', $row[0]), ' ',
				gen_link('upd', '[Update]', $row[0]), "</td>",
				'<td></td>';
			for($j = 0; $j < $ncol; ++$j) {
				$align = $j >= 3 ? 'right' :'left';
				echo "<td align=\"$align\">", 
					htmlspecialchars($row[$j]), 
					"</td>", "<td width=5></td>";
			}
			if($i == 1) {
				echo '<td align="center">';
				if($this->do_plus)
					echo $this->pml('plus'), ' ';
				if($this->do_minus)
					echo ' ', $this->pml('minus');
				echo '</td>';
			}
					
			if($i == $num - 1 && $this->down_arrow)
				echo '<td rowspan=2 align="center">', 
					gen_link('down', '<img src='.
						 '"/~bennet/icons/down.gif" ' .
						 'border=0>'), 
					'</td>';
			echo "</tr>\n";
		}

		echo "<tr></tr></table>\n";

		// Restriction message.
		$this->gen_restr();
	}
}

// Generate the search form.
function gen_search($restriction)
{
	if($_REQUEST['srcacct'])
		$srcacct = 'value="' . $_REQUEST['srcacct'] . '"';
	else 
		$srcacct = '';
	if($restriction)
		$resdef = "value=\"$restriction\"";
	else
		$resdef = '';
	//$rset = gen_link('top', '[Top]');
	//$rset = gen_link('top', '<button style="border: 0pt;">Top</button>');
	$rset = '<input type="submit" name="oper" value="Top">';

	echo start_form(), <<<SFORM
	<br><b>Search</b><br>
	<table>
	<tr><td>Move To Top:</td><td>$rset</td></tr>
	<tr><td>Search For Account:</td>
	<td><input type="text" name="srcacct" size="20" $srcacct>
	<input type="submit" name="oper" value="FIND"></td>
	</tr><tr>
	<td>Show Only Names Containing:</td>
	<td><input type="text" name="newname" size="40" $resdef>
	<input type="submit" name="oper" value="APPLY"></td>
	</tr></table></form>
SFORM;
}

// Generate the add user form.  If Update is true, it attempts to load that
// record and generates a replace form rather than an insert.
function add_usr_form($handle, $update)
{
	if($update)
	{
		// Create value text for each thingie.
		$dat = pg_exec($handle,
			"SELECT * FROM accounts WHERE acctname = '$update'");
		list($acct, $pwd, $hname, $dust, $jelly) = 
			@pg_fetch_row($dat, 0);
		if($acct != $update) $update = '';
		$button = 'UPDATE';
		foreach(array('pwd', 'hname', 'dust', 'jelly') as $n)
			${$n} = " value = \"${$n}\"";
		$acct = htmlspecialchars($acct) . "<input type=\"hidden\" " .
			"name=\"newacct\" value=\"$update\">";
		$cancel = " <input type=\"submit\" " .
			"name=\"oper\" value=\"CANCEL\">";
		$title = '<p><b>Update User</b><br>';
	}
	if(!$update)
	{
		// Create empty/default values.
		$button = 'CREATE';
		$acct = "<input type=\"text\" name=\"newacct\" size=\"20\">";
		$cancel = $pwd = $hname = '';
		$dust = ' value = 0';
		$jelly = ' value = 0';
		$title = '<b>Add User</b><br>';
	}

	echo start_form(), <<<ADDFORM
	$title
	<table><tr><td>Account Name:</td><td colspan=2>$acct</td></tr>
	<tr><td>Human Name:</td>
	    <td colspan=2><input type="text" name="newname"
			size="40"$hname></td></tr>
	<tr><td>Password:</td>
	    <td colspan=2><input type="text" 
			name="passwd" size="20"$pwd></td></tr>
	<tr><td>Dustbunnies:</td>
	    <td><input type="text" name="newdust" size="5"$dust></td>
	    <td>Jellybeans:
	    <input type="text" name="newjelly" 
			size="5"$jelly></td></tr>
	</table>
	<input type="submit" name="oper" value="$button">$cancel
	</form>
ADDFORM;
}

// Open the database.
$handle = @pg_connect("user=bennet password=XXXX " .
		      "host=localhost dbname=bennet");
		      
if(!$handle)
{
	whap("DB Open Failed",
	     "Sorry, database won't open.  Probably gremlins.");
	exit;
}

// Get the operation and selected item.
$oper = $_REQUEST['oper'];
$sel = $_REQUEST['sel'];
if($sel && !goodname($sel)) $sel = '';

// Create the search object.
$show = new recd_viewer($handle, $oper == 'up' ? 'up' : 'down');

$message = '';

// Move down.
if($oper == 'down') {
	$show->down();
}

// Change number of records displayed.
if($oper == 'plus') {
	$show->adjwidth(1);
}

// Change number of records displayed.
if($oper == 'minus') {
	$show->adjwidth(-1);
}

// Top of list.
if($oper == 'Top') {
	$show->clearpos();
}

// Look for a delete operation.
if($oper == 'del' && $sel)
{
	$dat = pg_exec($handle, 
		"DELETE FROM accounts WHERE acctname = '$sel'");
	if(!$dat)
		$message = "Delete of account $sel failed.";
}

// Search for a specific name.
if($oper == 'FIND')
{
	$show->find($_REQUEST['srcacct']);
}

// Apply the filter.
if($oper == 'APPLY')
{
	$show->setres($_REQUEST['newname']);
}
if($oper == 'clrres')
{
	$show->setres('');
}

// Look for an add operation.
if($oper == 'CREATE')
{
	$message = getnewudata();

	if(!$message) {
		// Insert the new account, then search it in the
		// display so it shows the new account.
		$res = @pg_exec($handle, "INSERT INTO accounts VALUES " .
				"('$newacct', '$passwd', '$newname', ".
				"$newdust, $newjelly)");
		if(!$res)
			$message = "Add of account $sel failed.";
		else
			$show->find($newacct);
	}
}

// Look for an add operation.
if($oper == 'UPDATE')
{ 
	$message = getnewudata();

	if(!$message) {
		// Update the account, then search it in the
		// display so it shows the new account.
		$res = @pg_exec($handle, "UPDATE accounts SET " .
				"password = '$passwd', ".
				"humanname = '$newname', " .
				"dust = '$newdust', jelly = '$newjelly' " .
				"WHERE acctname = '$newacct'");
		if(!$res)
			$message = "Update of account $sel failed.";
		else
			$show->find($newacct);
	}
}

if(!$message) $message = $show->message();
if($message) 
	frogstart($message, TRUE);
else
	frogstart('Administrative Console');
echo '<div style="float: right;">[', gen_link('logout', 'logout'), ']</div>';
$show->gen();
if($oper != 'upd') gen_search();
add_usr_form($handle, $oper == 'upd' && $sel ? $sel : '');

$show->over();

?>
</body>
</html>