CSCI 2006 - Spring 2024 - Server-Side ProgrammingAssignment #3 - User Accounts & Manager ViewsSolution

Solution

index.php

<?php

session_start();
require_once('dbObjects.php');

function printCSS() {
	echo <<<__CSS__
	/*  CSS inspired by http://codepen.io/alassetter/full/cyrfB/ */

	@import url(http://fonts.googleapis.com/css?family=Roboto:400,500,700,300,100);

	body {
	  background-color: #F5F5F5;
	  font-family: "Roboto", helvetica, arial, sans-serif;
	  font-size: 14px;
	  font-weight: 400;
	}

	header {
	  width: 100%;
	  height: 3em;
	  background-color: #ef6c00a6;
	  max-width: 900px;
	  margin-left: auto;
	  margin-right: auto;
	}

	header > .site {
	  float: left;
	  color: white;
	  font-weight: bold;
	  font-size: 2.2em;
	  padding: 5px;
	}

	header > nav {
	  float: right;
	}

	header > nav > div {
	  display: inline-block;
	  font-size: 1.6em;
	  color: white;
	  padding: 10px;
	}

	header > nav > div:hover {
	  background-color: white;
	  color: #EF6C00;
	}
	
	header a {
	  text-decoration: none;
	  color: inherit;
	}

	main {
	  margin: auto;
	  max-width: 900px;
	}

	div.title {
	  padding:5px;
	  width: 100%;
	}

	.title h1 {
	   color: black;
	   font-size: 26px;
	   font-weight: 500;
	   font-family: "Roboto", helvetica, arial, sans-serif;
	   text-transform:uppercase;
	}

	.work {
	  clear: both;
	}

	.work img {
	  float: right;
	  padding: 20px;
	  width: min(40%,400px);
	}

	.work .name {
	  font-weight: bold;
	  font-size: 4em;
	  display: block;
	}

	.work .artist {
	  font-style: italic;
	  display: block;
	}

	/*** Grid Styles **/
	.grid {
	  width: 100%;
	  display: flex;
	  flex-wrap: wrap;
	  justify-content: space-evenly;
	  gap: 5px;
	}

	.card {
	  width: 250px;
	  padding: 8px;
	  border: 1px solid black;
	  border-radius: 10px;
	  flex-grow: 1;
	}

	.card a {
	  text-decoration: none;
	  color: inherit;
	}

	.card img {
	  max-width: min(100%, 250px);
	  margin: auto;
	  display: block;
	}

	.card .work {
	  font-weight: bold;
	  display: block;
	  text-align: center;
	}

	.card .artist {
	  font-style: italic;
	  display: block;
	  text-align: center;
	}

	/*** Table Styles **/

	.table-fill {
	  background: white;
	  border-radius:3px;
	  border-collapse: collapse;
	  height: 320px;
	  padding:5px;
	  width: 100%;
	  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
	}

	.table-fill img {
	  width: min(100%, 200px);
	}

	th {
	  color: white;
	  background: #EF6C00;
	  border-bottom:4px solid #9ea7af;
	  border-right: 1px solid #343a45;
	  font-size:20px;
	  font-weight: 400;
	  padding:24px;
	  text-align:left;
	  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
	  vertical-align:middle;
	}

	th:first-child {
	  border-top-left-radius:3px;
	}
	 
	th:last-child {
	  border-top-right-radius:3px;
	  border-right:none;
	}
	  
	tr {
	  border-top: 1px solid #C1C3D1;
	  border-bottom-: 1px solid #C1C3D1;
	  color:#666B85;
	  font-size:16px;
	  font-weight:normal;
	  text-shadow: 0 1px 1px rgba(256, 256, 256, 0.1);
	}
	 
	 
	tr:first-child {
	  border-top:none;
	}
	tr:last-child {
	  border-bottom:none;
	}
	tr:last-child td:first-child {
	  border-bottom-left-radius:3px;
	}
	 
	tr:last-child td:last-child {
	  border-bottom-right-radius:3px;
	}

	td {
	  background:#FFFFFF;
	  padding:10px;
	  text-align:left;
	  vertical-align:middle;
	  font-weight:300;
	  font-size:18px;
	  text-shadow: -1px -1px 1px rgba(0, 0, 0, 0.1);
	  border-right: 1px solid #C1C3D1;
	}

	td:last-child {
	  border-right: 0px;
	}

	tr.totals td {
	    background:#FFF3E0;
	    text-align: right;
	}

	td.right {
	  text-align: right;
	}
	td.center {
	  text-align: center;
	}

	.focus td {
	  color: #E65100;  
	  font-weight: 500;
	}
	
	form {
	  width: 400px;
	  margin-left: auto;
	  margin-right: auto;
	}
	
	label {
	  margin: 20px;
	}
	
	input {
	  margin: 20px;
	}
__CSS__;
}


function printHeader($title) {
	$userspace = '';
	if (isset($_SESSION['user'])) {
		if ($_SESSION['user']['user_type'] == 'admin') {
			/* Admin */
			$userspace .= '<div><a href="?pg=admin">Admin</a></div>';
		} else {
			$userspace .= '<div><a href="?pg=cart">Cart</a></div>';
		}
		$userspace .= '<div><a href="?pg=logout">Logout</a></div>';
	} else {
		/* Not logged in */
		$userspace .= '<div><a href="?pg=login">Login</a></div>';
	}
	echo <<<__HTML__
<html lang="en">
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="initial-scale=1.0,maximum-scale=1.0,width=device-width">

	<title>General Store - {$title}</title>
	<link href="?pg=css" rel="stylesheet" />
</head>
<body>
	<header>
		<div class="site">Art Shop</div>
		<nav>
			<div><a href="?pg=home">Home</a></div>
			{$userspace}
		</nav>
	</header>
	<main>
__HTML__;
}


function printFooter() { 
	echo <<<__HTML__
	</main>
</body>
</html>
__HTML__;
}

function printLogin($msg) {
	echo <<<__HTML__
<form method="POST" action="?pg=home">
	<h2>{$msg}</h2>
	<label for="username">Username</label>
	<input id="username" name="user" value="" type="text" /><br>
	<label for="password">Password</label>
	<input id="password" name="pass" value="" type="password" /><br>
	<input type="submit" value="Login"/>
</form>
__HTML__;
}

function printCarts() {
	$carts = getData('SELECT `user_display`,'.
		'  SUM(`ci_qty`*`variation_price`) AS total'.
		' FROM `Cart`'.
		'  INNER JOIN `CartItem` ON `ci_cart`=`cart_id`'.
		'  INNER JOIN `Variation` ON `variation_id`=`ci_variation`'.
		'  INNER JOIN `User` ON `user_id`=`cart_user`'.
		' WHERE `cart_status`=\'open\''.
		' GROUP BY `user_id`, `user_display`',[]);
	$variations = getData('SELECT `variation_name`,'.
		'  `work_name`,'.
		'  `artist_name`,'.
		'  SUM(`ci_qty`) AS total'.
		' FROM `Variation`'.
		'  INNER JOIN `CartItem` ON `variation_id`=`ci_variation`'.
		'  INNER JOIN `Work` ON `work_id`=`variation_work`'.
		'  INNER JOIN `Artist` ON `artist_id`=`work_artist`'.
		' GROUP BY `variation_name`,`work_name`,`artist_name`',[]);

	$cartTable = '<tr><th>User</th><th>Open Cart</th></tr>';
	foreach ($carts as $c) {
		$cartTable .= '<tr><td>'.$c['user_display'].
			'</td><td>$'.$c['total'].
			'</td><tr>';
	}
	$variationTable = '<tr><th>Work</th><th>Variation</th><th>Sold</th></tr>';
	$lastArtist = '';
	foreach ($variations as $v) { 
		if ($lastArtist != $v['artist_name']) {
			$variationTable .= '<tr><th colspan="3">'.$v['artist_name'].'</th></tr>';
			$lastArtist=$v['artist_name'];
		}
		$variationTable .= '<tr><td>'.$v['work_name'].
			'</td><td>'.$v['variation_name'].
			'</td><td>'.$v['total'].
			'</td></tr>';
	}
	echo <<<__HTML__
<h3>Open Carts</h3>
<table>{$cartTable}<table>
<hr>
<h3>Sales by Work</h3>
<table>{$variationTable}</table>
__HTML__;
}

$msg = '';
if (isset($_POST['user']) && isset($_POST['pass'])) {
	/* A login attempt */
	$dbPass = getData('SELECT * FROM `User` WHERE `user_name`=?',[$_POST['user']]);

	if (password_verify($_POST['pass'],$dbPass['user_hash'])) {
		/* Success */
		unset($dbPass['user_hash']);
		$_SESSION['user'] = $dbPass;
	} else {
		$_GET['pg'] = 'login';
		$msg = 'Username/Password combination was not correct';
	}
}

if (!isset($_GET['pg'])) { $_GET['pg'] = ''; }
switch ($_GET['pg']) {
case 'login':
	printHeader('Login');
	printLogin($msg??'');
	break;
case 'logout':
	unset($_SESSION['user']);
	header('Location: ?pg=home');
	die();
	break;
case 'css':
	header("Content-type: text/css");
	printCSS();
	die();
	break;
case 'admin':
	if (!isset($_SESSION['user'])) {
		header("Location: ?pg=home");
		die();
	}
	if ($_SESSION['user']['user_type'] != 'admin') {
		 header("Location: ?pg=home");
		 die();
	}
	printHeader('Admin Console');
	printCarts();
	break;
case 'cart':
	/* Display the cart */
	if (!isset($_SESSION['user'])) {
		header("Location: ?pg=login");
		die();
	}
	printHeader('Shopping Cart');
	$openCart = getData('SELECT `cart_id` FROM `Cart` WHERE `cart_user`=? AND `cart_status`=\'open\'',
		[$_SESSION['user']['user_id']??-1]);
	$cart = new Cart($openCart['cart_id']??-1);
	echo $cart->toHTML();
	break;
case 'work':
	/* Display the information about an artwork */
	printHeader('Art Shop');
	$work = new Work(($_GET['work'])??-1);
	echo $work->toHTML();
	break;
case 'search':
	$filter = $_GET['search'];
	printHeader('Search Results');
case 'home':
default:
	if (!isset($filter)) {
		printHeader('');
	}

	/* Display the list of artworks */
	
	echo Work::allToHTML($filter??null);
	break;
}
printFooter();

?>

dbObjects.php

<?php

function getData($query,$args=[],$default=[]) {
	$DB = 'csci2006_artShop';
	$USER = 'csci2006_art';
	$PASS = 'hz0fs9vV4vkKL4An';

	try {
		$pdo = new PDO("mysql:host=localhost;dbname={$DB}",$USER,$PASS);
		$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
	} catch (PDOException $e) {
		echo '<h2>Database Error</h2>';
		die();
	}

	$rs = $pdo->prepare($query,[PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
	$rs->setFetchMode(PDO::FETCH_NAMED);
	$rs->execute($args);

	$data = [];
	while ($row = $rs->fetch()) {
		$data[] = $row;
	}

	unset($DB);
	unset($USER);
	unset($PASS);
	unset($rs);
	unset($pdo);

	if (count($data) == 0) {
		return $default;
	}
	if (count($data) == 1 && isset($data[0])) {
		return $data[0];
	}
	return $data;
}

class Artist {
	private $id = null;
	private $info = [];

	public function __construct($id) {
		$this->id = intval($id);
	}

	private function loadData() {
		if (count($this->info) > 0) {
			/* data is already loaded */
			return;
		}

		/* Load the data from the database */
		$this->info = getData(
			'SELECT * FROM Artist WHERE artist_id=?',
			[$this->id],
			[
				'artist_id'=>-1,
				'artist_name'=>'',
				'artist_birth'=>-1,
				'artist_death'=>-1,
			],
		);
	}

	public function getName() {
		$this->loadData();
		return $this->info['artist_name'];
	}
}

class Work {
	private $id = null;
	private $info = [];
	private $desc = [];
	private $variations = [];

	public function __construct($id) {
		$this->id = intval($id);
	}

	private function loadData() {
		if (count($this->info) > 0) {
			/* data is already loaded */
			return;
		}

		/* Load the data from the database */
		$this->info = getData(
			'SELECT * FROM Work WHERE work_id=?',
			[$this->id],
			[
				'work_id'=>-1,
				'work_artist'=>null,
				'work_name'=>'',
				'work_created'=>-1,
			]
		);
		
		/* Load the description text as well */
		$this->desc = getData(
			'SELECT * FROM Description WHERE desc_work=? ORDER BY desc_ord ASC',
			[$this->id],
			[]
		);
	}

	private function getVariations() {
		if (count($this->variations) > 0) {
			/* data is already loaded */
			return;
		}

		/* Load the data from the database */
		$this->variations = getData(
			'SELECT * FROM Variation WHERE variation_work=?',
			[$this->id],
			[]
		);
	}

	public function toHTML() {
		$this->loadData();
		$artist = new Artist($this->info['work_artist']);

		$name = $artist->getName();
		$html = <<<__HTML__
			<div class="work">
			 <img src="/resources/csci2006/csci2006_work_{$this->id}.png">
			 <div class="text">
			  <span class="name">{$this->info['work_name']}</span>
			  <span class="artist">{$name}</span>
			  <div class="desc">
__HTML__;
		foreach ($this->desc as $d) {
			$html .= '<p>'.$d['desc_text'].'</p>';
		}
		$html .= <<<__HTML__
			  </div>
			 </div>
			</div>
__HTML__;

		return $html;
	}

	public static function allToHTML($filter) {
		$works = getData(
			'SELECT * FROM Work'.
			' INNER JOIN Artist on artist_id=work_artist'.
			((strlen($filter)>0)?' WHERE work_name LIKE %?%':''),
			[$filter],
			[]
		);
		
		$html = '<div class="grid">';
		
		foreach ($works as $work) {
			$html .= <<<__HTML__
				<div class="card"><a href="?pg=work&work={$work['work_id']}">
				 <img src="/resources/csci2006/csci2006_work_{$work['work_id']}.png">
				 <span class="work">{$work['work_name']}</span>
				 <span class="artist">{$work['artist_name']}</span>
				</a></div>
__HTML__;
		}

		$html .= '</div>';
		return $html;
	}
}

class Cart {
	private $id = null;
	private $info = [];
	private $items = [];

	public function __construct($id) {
		$this->id = intval($id);
	}

	private function loadData() {
		if (count($this->info) > 0) {
			/* data is already loaded */
			return;
		}

		/* Load the data from the database */
		$this->info = getData(
			'SELECT * FROM Cart WHERE cart_id=?',
			[$this->id],
			[
				'cart_id'=>-1,
				'cart_user'=>null,
				'cart_status'=>'open',
				'cart_created'=>'CURRENT_DATE',
			]
		);

		/* Load the cart items as well */
		$this->items = getData(
			'SELECT * FROM CartItem'.
			' INNER JOIN Variation ON variation_id=ci_variation'.
			' INNER JOIN Work ON work_id=variation_work'.
			' WHERE ci_cart=?',
			[$this->id],
			[]
		);

	}

	public function toHTML() {
		$this->loadData();

		$html = <<<__HTML__
			<div class="title">
			 <h1>Shopping Cart</h1>
			</div>
			<table class="table-fill">
			 <thead>
			  <tr>
			   <th colspan="2">Product</th>
			   <th>#</th>
			   <th>Price</th>
			   <th>Amount</th>
			  </tr>
			 </thead>
			 <tbody>
__HTML__;

		$subTotal = 0;
		foreach ($this->items as $item) {
			$unit=number_format($item['variation_price'],2);
			$subTotal += $item['variation_price']*$item['ci_qty'];
			$total=number_format($item['variation_price']*$item['ci_qty'],2);

			$html .= <<<__HTML__
				<tr>
				 <td><img src="/resources/csci2006/csci2006_work_{$item['work_id']}.png"></td>
				 <td>{$item['work_name']} - {$item['variation_name']}</td>
				 <td>{$item['ci_qty']}</td>
				 <td class="right">\${$unit}</td>
				 <td class="right">\${$total}</td>
				</tr>
__HTML__;
		}

		$grand = $subTotal;
		$st = number_format($subTotal,2);

		$grand += $subTotal*0.1;
		$tax = number_format($subTotal*0.1,2);
		
		$grand += ($subTotal>500)?0:40;
		$ship = number_format(($subTotal>500)?0:40,2);

		$total = number_format($grand,2);

		$html .= <<<__HTML__
			<tr class="totals">
			 <td colspan="4">Subtotal</td>
			 <td>\${$st}</td>
			</tr>
			<tr class="totals">
			 <td colspan="4">Tax</td>
			 <td>\${$tax}</td>
			</tr>
			<tr class="totals">
			 <td colspan="4">Shipping</td>
			 <td>\${$ship}</td>
			</tr>
			<tr class="totals">
			 <td colspan="4">Grand Total</td>
			 <td>\${$total}</td>
			</tr>
			</tbody></table>
__HTML__;
	
		return $html;	
	}
}

?>
Go To Live Site