← CAPE IT library  ·  Web page construction lesson  ·  Sweet Escape project

STUDENT
CAPE IT · UNIT 2 · END OF MODULE 2 PRACTICAL · OBJ. 2, 3, 5, 10 & 11

Sweet Escape Goes Live

Build a small database-driven website with WAMP: a live menu read from MySQL, an online order form that validates input and saves real orders, and a staff page that reports on them.

In the Sweet Escape project you designed the bakery’s database and built a static HTML site. Its order form could only pretend to work. Now you’ll connect the two, so the website reads from and writes to the database.

WAMP on your computer (http://localhost) BrowserChrome / Edge Apacheweb server: finds the file PHPruns menu.php, builds HTML MySQLsweet_escape database 1 request 2 SQL 3 rows 4 HTML page W = Windows · A = Apache · M = MySQL (or MariaDB) · P = PHP
TimeWorkYou will need
3 sessions of about 80 minutes, plus write-upIndividual or pairs (each person submits their own write-up)A Windows PC with WampServer, a code editor (Notepad++ or VS Code) and the starter kit
⬇ Download the starter kit (.zip)

What you will hand in

  1. Your finished sweet-escape folder (zipped), with all pages working.
  2. Your answers to the database warm-up (Part B).
  3. A completed test plan with screenshots as evidence (Part G).
  4. A 400–500 word write-up (Part H).

Part A · Start WAMP

  1. Start WampServer from the Start menu. Wait until the tray icon turns green. Orange means a service didn’t start: ask your teacher.
  2. Open http://localhost in your browser. You should see the WampServer home page.
  3. Unzip the starter kit into C:\wamp64\www\, so that you have C:\wamp64\www\sweet-escape\index.php.
  4. Visit http://localhost/sweet-escape/. You will see a connection error. That’s expected, because the database doesn’t exist yet.

Why not just double-click the .php file? PHP runs on the server. If you open the file directly, the browser gets raw PHP code that nothing has run. Always go through http://localhost/… so that Apache hands the page to PHP first.

Part B · Build and explore the database

  1. Open http://localhost/phpmyadmin. Log in as root with no password, and choose the MySQL server.
  2. Click Import, choose sweet_escape.sql from the kit and click Import (or Go). A new database, sweet_escape, appears on the left.
  3. Refresh http://localhost/sweet-escape/. The home page should now load and say how many items are on the menu.
  4. Open sweet_escape and then Designer (under More). Compare the diagram with your ERD from Module 1. Find the composite key in order_details.

Warm-up: write SQL in phpMyAdmin’s SQL tab and record each query and its result

  1. List the name and price of every drink, cheapest first.
  2. How many products are there in each category?
  3. Which customer placed order 3? (Use a JOIN.)
  4. Try INSERT INTO product (name, category, price) VALUES ('Doubles', 'Street Food', 6);. What happens, and which constraint stopped it?
  5. Try DELETE FROM customer WHERE customer_id = 1;. What happens, and why is that a good thing?

After experimenting, you can re-import sweet_escape.sql at any time to reset the data.

Part C · How the pages fit together

FileWhat it does
includes/db.phpConnects to MySQL with PDO. It also defines e(), which makes text safe to print in HTML. If you use MariaDB, change the port to 3307.
includes/header.php, footer.phpThe shared top and bottom of every page, with the navigation bar.
index.phpHome page (finished, so read it as an example).
menu.phpShows the products from the database. TODOs 1–2
order.php + js/validate.jsThe order form: its HTML, browser checks, server checks and saving. TODOs 3–8
staff-orders.phpOrders report and best sellers. TODOs 9–10, plus extension 11
css/style.cssThe styles. Change the colours to make the site your own.

Two patterns you will use in every TODO

1. Reading rows. query() runs fixed SQL, and fetchAll() returns an array of rows that you loop through.

$rows = $pdo->query('SELECT name, price FROM product')->fetchAll();
foreach ($rows as $r) {
    echo '<p>' . e($r['name']) . ' costs $' . number_format($r['price'], 2) . '</p>';
}

2. Using anything the user typed. Never glue user input into SQL. Use a prepared statement: put ? placeholders in the SQL and pass the values separately.

$stmt = $pdo->prepare('SELECT * FROM customer WHERE phone = ?');
$stmt->execute([$phone]);            // $phone is sent separately, never run as SQL
$customer = $stmt->fetch();          // one row, or false if nothing matched

SQL injection: if you build SQL like "... WHERE phone = '$phone'", a visitor could type ' OR '1'='1 and change what your query does. Prepared statements stop this. Cross-site scripting: if you print a customer’s name without e(), someone could “name” themselves <script>…</script> and run code in the staff’s browser.

STUDENT
SESSION 1–2

Part D · A live menu

TODO 1 · menu.php
Write a query that reads the products from the database (filtered by $category if one was chosen), sorted by category then name. Store the rows in $products.

Hint: without a category, use $pdo->query(...). With a category, use a prepared statement with WHERE category = ?. Either way, finish with $products = $stmt->fetchAll();.

TODO 2 · menu.php
Loop through $products and print one card per product: name, category tag, description and price (2 decimal places). Give unavailable items the class "sold-out" and the text "Sold out today". Use e() on every value you print.

Check: menu.php shows 10 cards, and Mauby appears faded and marked “Sold out today”. menu.php?category=Drinks shows only 2 cards. Now try menu.php?category=Soup. Why does it show everything instead of an error? (Read the code above your TODO.)

Part E · The order form (Objective 10)

The form is where Module 2’s “form-enabled web pages” come together: HTML form elements, CSS, client-side scripting for validation, and a server that stores the result.

TODO 3 · order.php
Read the AVAILABLE products (product_id, name, price) sorted by name into $menu.
TODO 7 · order.php
Print one <option> per product in $menu. The value is the product_id and the text shows the name and price. Mark it selected if it matches $v['product'][$i].
TODO 6 · order.php
Add labelled inputs for phone (type tel, required), email (type email, optional) and pickup date (type date, required), each followed by a <span class="error" id="err_..."> that shows $errors for that field. Refill each with its value from $v.

Hint: copy the pattern of the name field that is already in the form. Choose the input type carefully (tel, email, date), because on phones they bring up the right keyboard.

TODO 8 · js/validate.js
Check the other fields the same way, calling showError() for each: phone (pattern /^[0-9 +()-]{7,20}$/), email (optional, but must look like an email), pickup date (tomorrow to 30 days, not Sunday/Monday), and items (at least one row, quantities 1-50, no item chosen twice). If anything fails, set ok = false.

Hint: the name check just above the TODO is a working example to copy. Use showError('phone', message), which returns true when the message is empty. Test it by submitting with fields left blank. The form must not send until it is correct.

Why validate twice? JavaScript runs in the visitor’s browser, and they can switch it off or edit it. It gives quick feedback, but it can’t be trusted. The PHP checks in TODO 4 run on the server, where the visitor can’t change them.

TODO 4 · order.php
Validate phone (7-20 digits/spaces/+-()), email (optional, but must be valid if given), and pickup date (tomorrow to 30 days ahead, not on Sunday or Monday). Add a message to $errors for each problem.

Hint: preg_match() for the phone number, filter_var($email, FILTER_VALIDATE_EMAIL) for the email, and DateTime objects for the dates. $date->format('N') gives 1 for Monday up to 7 for Sunday.

TODO 5 · order.php
Save the order. Find the customer by phone (or INSERT a new one), INSERT the order, then INSERT one order_details row per item. Use prepared statements and a transaction. Put the new order_id in $done.

Hint: the order must be saved in this sequence: customer, then order, then order details. The details need the new order_id, which $pdo->lastInsertId() gives you. Wrap the three steps in beginTransaction() … commit(), and call rollBack() in the catch. Then a failure halfway through leaves no half-saved order.

Check: place an order, then look in phpMyAdmin. There should be one new row in orders and one row per item in order_details. Place another order with the same phone number: no new customer row should appear.

Part F · The staff report

TODO 9 · staff-orders.php
Write the query that lists every order with: order_id, customer name and phone, pickup_date, status, the items as one piece of text (e.g. "4 x Currants Roll, 2 x Sorrel Drink") and the order total. JOIN orders, customer, order_details and product; GROUP BY the order; sort by pickup date.
TODO 10 · staff-orders.php
Write a query that lists each product that has been ordered, with the total quantity sold and the total money taken, best seller first. Store it in $best.

Check: with the sample data, order #1 shows “4 x Currants Roll, 2 x Sorrel Drink” and a total of $52.00. Currants Roll is the best seller, with 7 sold.

Extension: When the "Mark as ..." form is posted, UPDATE the order's status (only to 'Ready' or 'Collected') using a prepared statement, then reload the page. (staff-orders.php, TODO 11).

STUDENT
SESSION 3

Part G · Test plan

Test every rule with normal, boundary and erroneous data. Do each test twice: once with JavaScript on, and once with it switched off. (In Chrome or Edge, open DevTools with F12, press Ctrl+Shift+P and type “Disable JavaScript”.) Take a screenshot of each result.

#TestData typeInputExpected resultActual result✓/✗
1Valid orderNormal2 × Pine Tart, pickup next WednesdaySaved, order number shown
2QuantityBoundaryQuantity 50, then 5150 accepted, 51 rejected
3Pickup dayErroneousA Sunday“We are closed on Sundays and Mondays.”
4
5
6
7
8
9
10
11
12

Part H · Write-up (400–500 words)

  1. Justify the tools (Obj. 5). Why is a database-driven site better than the static HTML site for Sweet Escape? Use the selection criteria: nature of the solution, type of data, type of storage, access method and type of reports.
  2. Frameworks (Obj. 11). This site uses plain PHP. Describe how a web framework such as Flask could build the same site, and give one advantage and one disadvantage.
  3. Validation and security. Explain why the form is checked in the browser and on the server. Describe one attack that prepared statements or e() prevent.
  4. Going live. The site runs only on localhost. What would the bakery need to put it on the internet, and what new risks would that bring (Module 3)?

Marking guide (40 marks)

AreaMarksFor full marks
Database warm-up (Part B)5Five correct queries with results. The constraint errors are explained.
Live menu (TODOs 1–2)5Products come from the database. The category filter works. Sold-out items are shown. Output is escaped.
Form elements and CSS (TODOs 3, 6, 7)5Correct input types, labels, error spans and a drop-down built from the database. The values refill after an error.
Client-side validation (TODO 8)6Every rule is checked in JavaScript with clear messages. The form won’t send until it is valid.
Server-side validation and saving (TODOs 4–5)8Every rule is re-checked in PHP. Prepared statements and a transaction are used. Returning customers are reused.
Staff report (TODOs 9–10)4Correct JOINs, totals and best-seller ranking.
Test plan (Part G)3At least 12 tests, covering normal, boundary and erroneous data, with screenshots.
Write-up (Part H)4All four points answered with reasons, not just statements.

Extensions (TODO 11, a staff login with password_hash(), searching orders by phone) can earn credit that makes up for lost marks elsewhere, up to 40.