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

Lab #4 - PHP Arrays & Form Data

Purpose: Handle form data, include file uploads

Instructions

  1. Create a PHP file, called "index.php", that displays the following HTML form:
    <!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%; }
    
    .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;
    }
      </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>
    <form method="POST" enctype="multipart/form-data">
      <fieldset>
        <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">
            </p></td>
          </tr>
          <tr>
            <td><p>
              <label for="artist">Artist</label><br>
              <input type="text" id="artist" class="required hilightable">
            </p></td>
            <td><p>
              <label for="year">Year</label><br>
              <input type="year" id="year" class="required hilightable">
            </p></td>
          </tr>
          <tr>
            <td colspan="2"><p>
              <label for="desc">Description</label><br>
              <textarea id="desc" class="required hilightable"></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">
            </p></td>
            <td>
              <p>
                <label for="price-1">Price</label><br>
                <input type="number" id="price-1" step="1" min="1" class="required hilightable">
              </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/*">
    	</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">
            </p></td>
            <td class="price">
              <p>
                <label>Price</label><br>
                <input type="number" step="1" min="1" class="required hilightable">
              </p>
              <div class="remove">X</div>
            </td>
          </tr>
        </template>    
      </fieldset>
    </form>
    </html>
  2. Create a "route" in your PHP script for the processing of the returned form data, I recommend using either POST variables to signal a form submission, or ?act=form in the URL
  3. Modify the HTML to specify the names of the returned values, and the return URL to reference the route you created
  4. Collect the text and numeric data that the user submitted into an array with a structure that clearly identifies all of the components. Keep in mind that the user can submit multiple variation names and corresponding prices, so you will need able to nest that information using the tricks you have learned about with arrays
  5. Our uploaded file will be referenced in $_FILES (if you would like to see the structure of the information gathered, do a var_dump($_FILES). We will need to validate a variety of conditions about the uploaded file before we save it, perform the following validation steps:
  6. Check that the array is structured as we expect -- since we are only expecting a single file uploaded, we can do this by checking if the "error" key without of uploaded file information exists and is a "scalar" value (i.e. not an array)
  7. Next, check what the value of the error value is, there are several common status that we can build useful messages to our user about:
    • UPLOAD_ERR_OK - this is what we want, everything mechanically worked
    • UPLOAD_ERR_NO_FILE - the file the user choose did not exist when the form was submitted
    • UPLOAD_ERR_INI_SIZE - the file was bigger than the server's PHP configuration allows for
    • UPLOAD_ERR_FORM_SIZE - the file was bigger than a different configuration allows for
    • [anything else] - is an error that we do not have a custom message for
  8. Next, check to see that the filesize is less than 1,000,000 bytes
  9. Next, check the file type. To do this, use code similar to the following:
    /* this is our main tool for getting an accurate file type */
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    /* this is a clever way to get two pieces of information: 
     *   1. the extension of the file that we will use when we rename it, 
     *   2. and whether it is an approved type 
     */
    $ext = array_search($finfo->file($_FILES[...]['tmp_name']), [
    	/* We will only be accepting jpeg and png files for this lab */
    	'jpg'=>'image/jpeg',
    	'png'=>'image/png',
    ],true);
    /* If the $ext variable is not set, then the file type was not approved */
    if ($ext === false) { /* file is not an approved type */ }
  10. We need to give our webserver to work with the filesystem where the uploaded files will end up being placed. To do that, execute
    mkdir -p ~/public_html/csci2006/lab04/data
    mkdir -p ~/public_html/csci2006/lab04/images
    chmod o+w ~/public_html/csci2006/lab04/data ~/public_html/csci2006/lab04/images
    on the server (log in via SSH). NOTE this is not an ideal way to allow this permission, as it allows any user on the system to edit the contents of those directories, but since we are sharing this server between many students, we can not use a more appropriate approach (of creating a folder for uploads and giving the user "www-data" sole access to that folder
  11. Now that the file has been validated, and we have our directories on the system, we are ready to determine the new name of the file. This is a generated name, so I recommend something like: reprint_%d where %d is the current time (using time()) -- you should verify that the filename is not already in use, using file_exists.
  12. Next, move the uploaded file using the function "move_uploaded_file" to the images directory with the new file name, and the discovered $ext
  13. Next, save a copy of the other information the user submitted in a PHP file in the data directory. I recommmend using file_put_contents and var_export to help you do that a bit more efficiently.
  14. Edit your code so that previously submitted data is presented underneath the form on the page - format this in a way that makes sense for the users of your site
  15. Upload your PHP files (if you have not been doing that to test your code as you go), to the remote server. Please the file in "public_html/csci2006/lab04/index.php"
  16. In a web-browser, go to the URL below
  17. Ensure that your code is not producing any errors or warnings, by using the log-access tool. If you have trouble understanding the log messages, please email me for assistance. Note the log does not reset, so you need to look at when any errors/warnings occurred and which HTTP request they were for to better understand whether you have already fixed that issue

Submitting Instructions