CSCI 2006 - Spring 2024 - Server-Side ProgrammingAssignment #2 - Shopping Site and Cart ModificationSolution

Solution

index.php

<?php

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;
	}
__CSS__;
}


function printHeader($title) {
	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>
			<div><a href="?pg=cart">Cart</a></div>
		</nav>
	</header>
	<main>
__HTML__;
}


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


if (!isset($_GET['pg'])) { $_GET['pg'] = ''; }
switch ($_GET['pg']) {
case 'css':
	header("Content-type: text/css");
	printCSS();
	die();
	break;
case 'cart':
	/* Display the cart */
	printHeader('Shopping Cart');
	$cart = new Cart(($_GET['cart'])??-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