CSCI 2006 - Spring 2024 - Server-Side ProgrammingLab #4 - PHP Arrays & Form DataSolution

Solution

index.php

<?php

function printForm() {
	echo <<<__HTML__
<form method="POST" enctype="multipart/form-data" action="?">
  <fieldset>
    <input type="hidden" name="form" value="reprint">
    <legend>Create a New Reprint</legend>
    <table>
      <tr>
        <td colspan="2"><p>
          <label for="title">Title</label><br>
          <input type="text" id="title" class="required hilightable" name="info[title]">
        </p></td>
      </tr>
      <tr>
        <td><p>
          <label for="artist">Artist</label><br>
          <input type="text" id="artist" class="required hilightable" name="info[artist]">
        </p></td>
        <td><p>
          <label for="year">Year</label><br>
          <input type="year" id="year" class="required hilightable" name="info[year]">
        </p></td>
      </tr>
      <tr>
        <td colspan="2"><p>
          <label for="desc">Description</label><br>
          <textarea id="desc" class="required hilightable" name="info[desc]"></textarea>
        </p></td>
      </tr>
      <tr class="variation">
        <td><p>
          <label for="var-1">Variation Name</label><br>
          <input type="text" id="var-1" class="required hilightable" name="info[variations][]">
        </p></td>
        <td>
          <p>
            <label for="price-1">Price</label><br>
            <input type="number" id="price-1" step="1" min="1" class="required hilightable" name="info[prices][]">
          </p>
        </td>
      </tr>
      <tr id="addVariation">
        <td colspan="2"><p>
          <input type="button" class="btn" value="Add Variation"> 
        </p></td>
      </tr>
      <tr>
        <td><p>
          <label for="file">Reprint Image</label><br>
          <input type="file" id="file" accept="image/*" name="reprint">
	</p></td>
        <td><img id="file_preview" style="display:none;"></td>
      </tr>
      <tr>
        <td colspan="2">
          <div class="rectangle centered">
            <input type="submit" class="btn"> <input type="reset" value="Clear Form" class="btn">
          </div>
        </td>
      </tr>
    </table>
    <template id="variation">
      <tr class="variation">
        <td class="var"><p>
          <label>Variation Name</label><br>
          <input type="text" class="required hilightable" name="info[variations][]">
        </p></td>
        <td class="price">
          <p>
            <label>Price</label><br>
            <input type="number" step="1" min="1" class="required hilightable" name="info[prices][]">
          </p>
          <div class="remove">X</div>
        </td>
      </tr>
    </template>    
  </fieldset>
</form>
__HTML__;
}

function printAnswers() {
	$reprints = scandir('./data/');
	foreach ($reprints as $rp) {
		if ($rp == '.' || $rp == '..') { continue; }
		$DATA=[];
		include('./data/'.$rp);

		$variations = '';
		foreach ($DATA['variations'] as $info) {
			$variations .= '<li>'.$info['name'].' - $'.$info['price'].'</li>';
		}

		echo <<<__HTML__
<div class="entry">
  <img src="{$DATA['file']}">
  <div class="info">
    <h2>{$DATA['name']}</h2>
    <h3>by {$DATA['artist']} (c.{$DATA['date']})</h3>
    <p>{$DATA['desc']}</p>
    <h4>Variations</h4>
    {$variations}
  </div>
</div>
__HTML__;
	}
}

echo <<<__HTML__
<!DOCTYPE html>
<html>
<head>
  <title>Lab #4 - Forms</title>
  <style>
@import url(https://fonts.googleapis.com/css?family=Open+Sans);
@import url(https://fonts.googleapis.com/css?family=Merriweather);


/* general text formatting */

h1, h2, h3, legend {
 font-family: 'Merriweather', serif;    }
body {
   font-family: 'Open Sans', Arial, sans-serif;
   font-size: 16px;
}

table {  
   width: 90%;
   margin: 0 auto;
}
table tbody td{
   vertical-align: top;
}

legend {
   background-color: #616161 ;
    color: white;
   margin: 0 auto;
   width: 90%;
   padding: 0.25em;
   text-align: center;
   font-weight: bold;
   font-size: 24px;
}
fieldset {
   margin: 1em auto;
   background-color: #F5F5F5;
   width: 70%;
}
form p {
   margin-top: 0.5em;
}
form input, form select {
    font-size: 16px;
    height: 24px;
    padding: 3px;
}
form select {
    height: 30px;
}
form textarea {
    height: 5em;
}
#title, #desc { width: 100% }
#artist { width: 90%; }
#year { width: 40%; }
.variation input[type=text] { width: 90%; }
.variation input[type=number] { width: 40%; }
td img { max-width: 90%; }


.variation td { position: relative; }
.remove {
    position: absolute;
    top: 18px;
    font-size: 2em;
    left: 40%;
    margin-left: 20px;
    background-color: #ff9100a6;
    border-radius: 5px;
    border: solid 1px black;
    padding: 5px;
}

.box {
   border: 1pt solid #9E9E9E;
   padding: 0.5em;
   margin-bottom: 0.4em;
}

.rectangle {
   background-color: #BDBDBD;
   padding: 0.5em;
    margin-bottom: 5px;
}
.centered {
   text-align: center;
}
.highlight {
    background-color: #FFE0B2;
}    
.error {
    background: #FFCDD2 url(http://74.208.52.43/resources/csci2005/lab10/error.png) no-repeat 98% center;
    box-shadow: 0 0 5px #FF5252;
    border-color: #FF1744;    
}

.btn {
  -webkit-border-radius: 3;
  -moz-border-radius: 3;
  border-radius: 3px;
  height: 32px;
  color: black;
  font-size: 14px;
  background: #FF9100;
  padding: 5px 20px 5px 20px;
  text-decoration: none;
}

.btn:hover {
  background: #FFAB40;
  text-decoration: none;
}

.info {
  max-width: 50%;
  display: inline-block;
  vertical-align: top;
}
.entry {
  border: 1px solid black;
  border-radius: 5px;
  padding: 10px;
}
.entry img {
  max-width:45%;
  display: inline;
}
  </style>
  <script type="text/javascript">
document.addEventListener("DOMContentLoaded", function () {
	var varCount = 1;
	document.querySelector("form").addEventListener("focusin",(e) => {
		if (e.target.classList.contains("hilightable")) {
			e.target.classList.add("highlight");
			e.target.classList.remove("error");
		}
	});
	document.querySelector("form").addEventListener("focusout",(e) => {
		e.target.classList.remove("highlight");
		if (e.target.classList.contains("required") && e.target.value == "") {
			e.target.classList.add("error");
		}
	});
	document.querySelector("form").addEventListener("submit",(e) => {
		document.querySelectorAll(".required").forEach((elem) => {
			if (elem.value == "") {
				e.preventDefault();
				elem.classList.add("error");
			}
		});
	});
	document.querySelector("#addVariation input").addEventListener("click",(e) => {
		var template = document.querySelector("#variation").content.cloneNode(true);
		varCount++;
		template.querySelector(".var label").setAttribute("for","var-"+varCount);
		template.querySelector(".var input").setAttribute("id", "var-"+varCount);
		template.querySelector(".price label").setAttribute("for","price-"+varCount);
		template.querySelector(".price input").setAttribute("id", "price-"+varCount);
		var ref = document.querySelector("#addVariation");
		ref.parentElement.insertBefore(template,ref);
	});
	document.querySelector("form").addEventListener("click",(e) => {
		if (e.target.classList.contains("remove")) {
			e.target.closest("tr").remove();
		}
	});
	document.querySelector("#file").addEventListener("change",(e) => {
		var prev = document.querySelector("#file_preview");
		prev.style.display="block";
		const [file] = e.target.files;
		prev.src = URL.createObjectURL(file);
	});
});
  </script>
</head>
<body>
__HTML__;

$msg = 'Uploads Disabled';
if (isset($_POST['form']) && $_POST['form'] == 'reprint') {
	$reprint = [
		'name'=>$_POST['info']['title'],
		'artist'=>$_POST['info']['artist'],
		'date'=>intval($_POST['info']['year']),
		'desc'=>$_POST['info']['desc'],
		'variations'=>[],
		'file'=>'',
	];
	foreach ($_POST['info']['variations'] as $k=>$v) {
		$reprint['variations'][] = [
			'name'=>$v,
			'price'=>intval($_POST['info']['prices'][$k]),
		];
	}

	/* is the files array formatted as expected */
	if (!isset($_FILES['reprint']['error']) || is_array($_FILES['reprint']['error'])) {
		$msg = 'Invalid Parameter';
	}

	/* What is the error status */
	if (strlen($msg) == 0) {
		switch ($_FILES['reprint']['error']) {
		case UPLOAD_ERR_OK: 
			break;
		case UPLOAD_ERR_NO_FILE: 
			$msg = 'No file sent'; 
			break;
		case UPLOAD_ERR_INI_SIZE:
		case UPLOAD_ERR_FORM_SIZE: 
			$msg = 'Exceeded filesize limit';
			break;
		default:
			$msg = 'Unknown error';
			break;
		}
	}

	/* Is the file the correct size */
	if (strlen($msg) == 0) {
		if ($_FILES['reprint']['size'] > 1000000) {
			$msg = 'Exceeded filesize limit';
		}
	}

	/* Check the file type */
	$ext = '';
	if (strlen($msg) == 0) {
		$finfo = new finfo(FILEINFO_MIME_TYPE);
		$ext = array_search($finfo->file($_FILES['reprint']['tmp_name']), [
			'jpg'=>'image/jpeg',
			'png'=>'image/png',
		],true);
		if ($ext === false) {
			$msg = 'Invalid file format';
		}
	}

	$name = '';
	do {
		$name = sprintf("reprint_%d",time());
	} while (file_exists('./data/'.$name.'.php'));


	if (strlen($msg) == 0) {
		/* Determine a name for the file */
		$reprint['file'] = sprintf('./images/%s.%s',$name,$ext);
		if (!move_uploaded_file($_FILES['reprint']['tmp_name'],$reprint['file'])) {
			$msg = 'Failed to move uploaded file';
		}
	}

	if (strlen($msg) == 0) {
		/* We have passed all of the checks */
		file_put_contents(
			sprintf('./data/%s.php',$name),
			'<?php $DATA='.var_export($reprint,true).'; ?>'
		);
	}
}

if ($msg != '') {
	echo '<h2>ERROR: '.$msg.'</h2>';
}
printForm();
printAnswers();

echo <<<__HTML__
</body>
</html>
__HTML__;

?>
Due to the nature of this exercise including file uploads, there is not a full demonstration of the target goal. The following script demo-page will do all of the same actions as your goal, but it will not display your images (it will display a placeholder instead)
Go To Live Site