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

// Names of the acounts.
define('act_A', "jellybeans");
define('Act_A', "Jellybeans");
define('act_B', "dustbunnies");
define('Act_B', "Dustbunnies");

// Use a session.
session_start();

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

// 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 contains only characters we allow in a password.  Mostly,
// not quotes or backtic.
function goodpw($n) {
	return preg_match('/^[ !#-&(-\[\]-~]*$/', $n);
}

// Open and return a connection to the database.
function opendb() {
	return pg_connect("host=localhost dbname=bennet user=bennet password=XXXX");
}

// 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');
	echo <<<END
		<div style="margin-left: 20px; $style">
		<img src="../imgs/frog.gif" 
			style="vertical-align: middle;">$msg</div>
END;
}

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

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

// Open a form which we will process.  Creates the form tag with the
// correct action, and generates a hidden field for the function.
function gen_form($func) {
	$me = $_SERVER['SCRIPT_NAME'];

	return "<form action=\"$me\" method=\"POST\">\n" .
		"<input type=\"hidden\" name=\"oper\" value=\"$func\">\n";
}

// Generate a form asking the amount and selecting an account.  These are used
// for adds, transfers, and purchases.  First arg is the oper value for the
// action URL, the rest fill in text:  The label for the amount, text for each
// radio button, text for the submit button.  
function amt_acct_form($act, $amtlab, $rad1, $rad2, $sub) {
	echo gen_form($act), <<<ENDTAB
		$amtlab: <input type="text" name="amt" size=6><br>
		<input type="radio" name="which" value="a" checked> $rad1<br>
		<input type="radio" name="which" value="b"> $rad2<br>
		<input type="submit" name="sub" value="$sub">
		</form>
ENDTAB;
}

// Update accounts.  Used by the functions which change balances to check
// and update.  Args are the amount of transaction from the web page (must
// be checked).  $delta_a and $delta_b are factors.  Each account is 
// incremented by the delta.
function update_account($acct, $amt, $delta_a, $delta_b) {
	// Check the number.
	if(!numeric($amt))
		return "Please Enter a Valid Number";

	// Convert deltas to absolute.
	$delta_a = $delta_a * $amt;
	$delta_b = $delta_b * $amt;

	// Attempt to read the account entry.
	$dbm = opendb();
	if(!$dbm) return "Database Update Failed";
	$dat = pg_exec($dbm,"SELECT jelly, dust " .
				"FROM accounts WHERE acctname = '$acct'");
	list($a, $b) = pg_fetch_row($dat, 0);

	// Check for sufficient funds.
	if($a + $delta_a < 0) {
		pg_close($dbm);
		return "Insufficient funds in ".act_A;
	}
	if($b + $delta_b < 0) {
		pg_close($dbm);
		return "Insufficient funds in ".act_B;
	}

	// Update
	$a += $delta_a;
	$b += $delta_b;
	pg_exec($dbm, "UPDATE accounts SET jelly = $a, dust = $b " .
		"WHERE acctname = '$acct'");
	pg_close($dbm);

	return '';
}

// Generate the start menu (the menu for logged out sessions).  The title
// can be sent, or defaults.
function gen_start_menu($msg = 'Welcome to Frogbreath', $error = FALSE) {
	frogstart($msg, $error);

	echo gen_link('login', 'Log in'), " to Frogbreath<br>";
	echo gen_link('create', 'Create'), " a Frogbreath account<br>";

	global $oper;
	$oper = 'menu';
}

// Generate the action menu (the menu for logged in sessions).  Title will
// default.
function gen_do_menu($acct, $name, $msg = FALSE, $error = FALSE) { 
	// Default message.  Can't be the default 'cause it needs a variable.
	if(!$msg) $msg = "$name at Frogbreath";
	frogstart($msg, $error);

	// Generate the links.
	echo gen_link('buy', 'Buy stuff'), 
		" you can't see from Frogbreath<br>";
	echo gen_link('add', 'Give'), " fake money to Frogbreath<br>";
	echo gen_link('xfer', 'Transfer'), " fake money between accounts.<br>";
	echo gen_link('query', 'Query'), " fake balances.<br>";
	echo gen_link('logout', 'Log out'), " from Frogbreath<br>";

	global $oper;
	$oper = 'menu';
}

// Log out the user.  Changes the session state.
function do_logout($name) {
	// Change state and generate the login menu.
	$_SESSION['state'] = 'out';
	gen_start_menu("$name has left Frogbreath");
}

// Function for serious errors that we can't figure out.
function gen_err($msg) { 
	// Just in case.
	$_SESSION['state'] = 'out';

	whap("Internal Error: $msg",
	     "An internal error has occurred: $msg." .
	     "This may indicate a problem with our server or your " .
	     "network connection.  You may want to try again later.  " .
	     "We regret any inconvenience this may cause.<p>" .
	     "Thank you for visiting Frogbreath.");
}

// Output a table of the subaccount balances for the currently logged-on
// account.  Displays the header which defaults.  If there is problem,
// returns an error message.  Otherwise, returns ''.
function display_bal($acct, $msg = '', $error = FALSE) {
	$Act_A = Act_A;
	$Act_B = Act_B;

	// Attempt to read the balances.
	$dbm = opendb();
	if(!$dbm)
		return 'Database open failed.';
	$dat = pg_exec($dbm,"SELECT jelly, dust, humanname " .
				"FROM accounts WHERE acctname = '$acct'");
	list($n, $m, $name) = pg_fetch_row($dat, 0);
	pg_close($dbm);

	// Display.
	if(!$msg) $msg = "Balances for $name";
	frogstart($msg, $error);
	echo <<<DONE
		<p><table>
		<tr><td>$Act_A:</td><td>$n</td></tr>
		<tr><td>$Act_B:</td><td>$m</td></tr>
		</table><p>
DONE;
	return '';
}

// Generate the balance query page.
function gen_query($acct, $name) {
	$str = display_bal($acct);
	if($str != '')
		gen_do_menu($acct, $name, $str, TRUE);
}

// Produce the add additional funds menu.
function gen_add($acct, $name, $head, $error = FALSE) { 

	// Generate the current balance table
	if(($m = display_bal($acct,$head)) != '') {
		gen_do_menu($acct, $name, $m, TRUE);
		return;
	}
	echo "<p>";

	// Generate the transfer information form.
	amt_acct_form('doadd', 'Funds to Add', "Add to ".act_A,
			"Add to ".act_B, 'ADD');
}

// Process additional funds response.
function do_add($acct, $name) { 
	$amt = $_REQUEST['amt'];
	$which = $_REQUEST['which'];

	// Compute the delta factors for update_account based on the
	// account selection from the form.
	$da = $db = 0;
	if($which == 'a') $da = 1;
	else $db = 1;	

	// Attempt to update the account, then go to the index, or back to
	// account add form in case of error.
	if(($msg = update_account($acct, $amt, $da, $db)) != '')
		gen_add($acct, $name, $msg);
	else
		gen_do_menu($acct, $name, 
			    "Added $amt to " . ${"act_$which"} . " for $name");
}

// Generate transfer menu.
function gen_xfer($acct, $name, $head, $error = FALSE) { 

	// Display current balances.
	if(($m =  display_bal($acct, $head, $error)) != '') {
		gen_do_menu($acct, $name, $m);
		return;
	}
	echo "<p>";

	// Generate transfer form.
	amt_acct_form('doxfer', 'Funds to Transfer', 
			"Transfer from " . act_A . " to " . act_B,
			"Transfer from " . act_B . " to " . act_A,
			'TRANSFER');
}

// Process funds transfer.
function do_xfer($acct, $name) { 
	$amt = $_REQUEST['amt'];

	// Based on radio button results, choose deltas for update_account,
	// and a success message, in case we have some.
	if($_REQUEST['which'] == 'a') { 
		$da = -1;
		$db = 1;
		$succ = "Transferred $amt from " . act_A . " to " . act_B . 
			" for $name";
	} else {
		$da = 1;
		$db = -1;
		$succ = "Transferred $amt from " . act_B . " to " . act_A . 
			" for $name";
	}

	// Update the account, menu or back to form in case of error.
	if(($msg = update_account($acct, $amt, $da, $db)) != '')
		gen_xfer($acct, $name, $msg, TRUE);
	else
		gen_do_menu($acct, $name, $succ);
}

// Generate the purchase menu.
function gen_buy($acct, $name, $head, $error = FALSE) { 
	// Display current balances.
	if(($m =  display_bal($acct, $head, $error)) != '') {
		gen_do_menu($acct, $name, $m);
		return;
	}
	echo "<p>";

	// The "market" prices set at random.  (The market is rather volatile)
	if($error) {
		$pri_pop = $_SESSION['pri_pop'];
		$pri_fame = $_SESSION['pri_fame'];
		$pri_sawdust = $_SESSION['pri_sawdust'];
	} else {
		list($u, $s) = explode(' ', microtime());
		srand((100000*$u) ^ $s);
		$_SESSION['pri_pop'] = $pri_pop = rand(1, 20);
		$_SESSION['pri_fame'] = $pri_fame = rand(15, 30);
		$_SESSION['pri_sawdust'] = $pri_sawdust = rand(6, 15);
	}

	// Generate purchase form.
	$Act_A = Act_A;
	$Act_B = Act_B;
	echo gen_form('dobuy'), <<<ENDTAB
		Which would you like to purchase?<br>
		<input type="text" size="2" name="sel_pop" value="0" 
			align="right">Popularity @ $pri_pop<br>
		<input type="text" size="2" name="sel_fame" value="0"
			align="right">Fame @ $pri_fame<br>
		<input type="text" size="2" name="sel_sawdust" value="0"
			align="right">Sawdust @ $pri_sawdust<p>
		From which account will you pay?<br>
		<input type="radio" name="which" value="a" checked> $Act_A<br>
		<input type="radio" name="which" value="b"> $Act_B<br>
		<input type="submit" name="sub" value="PURCHASE">
		</form>
ENDTAB;
}

// Process purchase.
function do_buy($acct, $name) {
	// Prices from session.
	$pri_pop = $_SESSION['pri_pop'];
	$pri_fame = $_SESSION['pri_fame'];
	$pri_sawdust = $_SESSION['pri_sawdust'];

	// Purchase selections from form.
	$sel_pop = $_REQUEST['sel_pop'];
	$sel_fame = $_REQUEST['sel_fame'];
	$sel_sawdust = $_REQUEST['sel_sawdust'];

	// Check that input counts are numeric.
	if(!numeric($sel_pop) || !numeric($sel_fame) || 
	   !numeric($sel_sawdust)) {
		gen_buy($acct, $name, 
			"Please give positive numeric quantities.", TRUE);
		return;
	}

	// Compute price.
	$price = $sel_pop*$pri_pop;
	$price += $sel_fame*$pri_fame;
	$price += $sel_sawdust*$pri_sawdust;

	// Set account to debit.
	$da = $db = 0;
	if($_REQUEST['which'] == 'a') $da = -1;
	else $db = -1;	

	// Update account, show menu or return to form on an error.
	if(($msg = update_account($acct, $price, $da, $db)) != '')
		gen_buy($acct, $name, $msg, TRUE);
	else
		gen_do_menu($acct, $name, 
			    "$name spent $price from " . ${"act_$which"});
}

// Generate the create account page.
function gen_create() {
	frogstart('Create A Frogbreath Account');	

	// Generate the form.
	echo gen_form('docreate'); ?>
	<table>
	<tr><td>Your Name:</td>
		<td><input type="text" name="newname" size="40"></td></tr>
	<tr><td>Account Name:</td>
		<td><input type="text" name="newacct" size="20"></td></tr>
	<tr><td>Password:</td>
		<td><input type="password" name="passwd" size="20"></td></tr>
	</table>
	<input type="submit" name="sub" value="CREATE">
	</form>
<?php }

// Process the account creation page.
function do_create() {
	$newacct = $_REQUEST['newacct'];
	$passwd = $_REQUEST['passwd'];
	$newname = $_REQUEST['newname'];

	// Vet the input.
	if(!goodname($newacct)) {
		gen_start_menu('User name must be alphanumeric and start' .
			       ' with a letter.', TRUE);
		return;
	}

	if(!goodpw($passwd) || !goodpw($newname)) {
		gen_start_menu('Names and passwords may not use quotes.', 
			       TRUE);
		return;
	}

	if(strlen($passwd) < 5) {
		gen_start_menu('Passwords should be at least five characters.',
			       TRUE);
		return;
	}

	// Attempt to open the database.
	$dbm = opendb();
	if(!$dbm) {
		gen_start_menu('Database open failed.', TRUE);
		return;
	}

	// Account already exists?
	$dat = pg_exec($dbm,"select acctname from accounts " .
					"where acctname = '$newacct'");
	if($dat && @pg_fetch_row($dat, 0)) {
		pg_close($dbm);
		gen_start_menu("Account $newacct exists.", TRUE);
		return;
	}

	// Create the account record.  (May fail if account is suddenly
	// created by someone else.)
	$res = pg_exec($dbm, "INSERT INTO accounts VALUES " .
				"('$newacct', '$passwd', '$newname', 0, 0)");
	pg_close($dbm);

	if($res) {
		// Log the session into the new account.
		$_SESSION['state'] = "in";
		$_SESSION['acct'] = $newacct;
		$_SESSION['name'] = $newname;
		gen_do_menu($newacct, $newname, "Account $newacct created");
	} else 
		// Failed.
		gen_start_menu("Account $newacct not created", TRUE);
}

// Generate the login form.
function gen_login() {
	frogstart('Login to Frogbreath');	

	echo gen_form('dologin'); ?>
	<table>
	<tr><td>Account Name:</td>
		<td><input type="text" name="newacct" size="20"></td></tr>
	<tr><td>Password:
		<td><input type="password" name="passwd" size="20"></td></tr>
	</table>
	<input type="submit" name="sub" value="LOGIN">
	</form>
<?php }

// Process the login form.
function do_login() {
	// Relevant form data.
	$newacct = $_REQUEST['newacct'];
	$passwd = $_REQUEST['passwd'];

	if(!goodname($newacct) || !goodpw($passwd)) {
		gen_start_menu('Invalid credentials.', TRUE);
		return;
	}

	// Attempt to open the database.
	$dbm = opendb();
	if(!$dbm) {
		gen_start_menu('Database open failed.', TRUE);
		return;
	}

	// Get the human name.
	$dat = pg_exec($dbm,"select password, humanname from accounts " .
					"where acctname = '$newacct'");

	// Missing?
	if(!$dat || !@pg_fetch_row($dat, 0)) {
		pg_close($dbm);
		gen_start_menu("Account $newacct not found.", TRUE);
		return;
	}

	// Read account information.
	list($pw, $name) = @pg_fetch_row($dat, 0);
	pg_close($dbm);

	// Check the password, whine if bad, otherwise update the session.
	if($pw == $passwd) {
		// Log in the session.
		$_SESSION['state'] = "in";
		$_SESSION['acct'] = $newacct;
		$_SESSION['name'] = $name;
		gen_do_menu($newacct, $name, "$name logged on.");
	}
	else 
		// Nope.
		gen_start_menu("Bad password for $newacct.", TRUE);
}

// If there is no state value set, initialize to a logged-out state, with
// a request for the menu.
$state = $_SESSION['state'];
if(!$state) $_SESSION['state'] = $state = 'out';
$oper = $_REQUEST['oper'];
if(!$oper) $oper = 'menu';

// Take appropriate action based on the session state and the request from
// the url or form post.
if($state == 'in') {
	// Logged in.  Process appropriate requests.
	$name = $_SESSION['name'];
	$acct = $_SESSION['acct'];

	switch($oper) {
	case 'menu': 
		gen_do_menu($acct, $name);
		break;
	case 'logout':
		do_logout($name);
		break;
	case 'buy':
		gen_buy($acct, $name, "$name: Purchase from Frogbreath");
		break;
	case 'dobuy':
		do_buy($acct, $name);
		break;
	case 'add':
		gen_add($acct, $name, "$name: Add funds to Frogbreath");
		break;
	case 'doadd':
		do_add($acct, $name);
		break;
	case 'xfer':
		gen_xfer($acct, $name, "$name: Transfer funds at Frogbreath");
		break;
	case 'doxfer':
		do_xfer($acct, $name);
		break;
	case 'query':
		gen_query($acct, $name);
		break;
	default:
		gen_err('Illegal Request');
	}
	
} else {
	// Operations for a logged out session.
	switch($oper) {
	case 'menu':
		gen_start_menu();
		break;
	case 'create':
		 gen_create();
		 break;
	case 'docreate':
		 do_create();
		 break;
	case 'login':
		 gen_login();
		 break;
	case 'dologin':
		 do_login();
		 break;
	default:
		gen_err('Illegal Request');
	}
}

// For every page generate the logo image, and, if it is not a menu
// reguest, create a link to one.
if($oper != 'menu')
	echo "<p>&nbsp;&nbsp;", gen_link('menu', 
		'<i><font color="green">Menu</font></i></p>');
?>
</body>
</html>