Trivia Game 4: A New Hope

This version of the trivia game uses a front controller design pattern, separating the Controller from the View, and directing (almost) all requests in the /inclass/trivia4/ directory to the index.php file below. That script instantiates the TriviaController class, which contains the program logic. We also wrote a Database class to simplify our interactions with the MySQLi connector.

The directory layout is as follows:

Below is the source code for each file.

.htaccess

# Enable mod_rewrite engine
RewriteEngine on
RewriteBase /

# Store the current location in an environment variable CWD
RewriteCond $0#%{REQUEST_URI} ([^#]*)#(.*)\1$
RewriteRule ^.*$ - [E=CWD:%2]

# Rewrites to redirect everything to index.php if the file doesn't exist
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . %{ENV:CWD}index.php [L]

index.php

<?php

spl_autoload_register(function($classname) {
    include "classes/$classname.php";
});


// Join session or start one
session_start();

// Parse the URL
$path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
$path = str_replace("/inclass/trivia4/", "", $path);
$parts = explode("/", $path);


// If the user's email is not set in the session, then it's not
// a valid session (they didn't get here from the login page),
// so we should send them over to log in first before doing
// anything else!
if (!isset($_SESSION["email"])) {
    // they need to see the login
    $parts = ["login"];
}

// Instantiate the controller and run
$trivia = new TriviaController();
$trivia->run($parts[0]);

Database.php

<?php

class Database {

    private $mysqli;
    
    public function __construct() {
        include('../database_connection.php'); // where the script starts
        mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // Extra Error Printing
        $this->mysqli = new mysqli($dbserver, $dbuser, $dbpass, $dbdatabase);
        //$this->mysqli = new mysqli("localhost", "root", "", "example"); // XAMPP
    }
    
    public function query($query, $bparam = null, ...$params) {
        $stmt = $this->mysqli->prepare($query);
        
        if ($bparam != null)
            $stmt->bind_param($bparam, ...$params);
            
        if (!$stmt->execute()) {
            // execute failed
            return false;
        }
        
        // execute succeeded
        if (($res = $stmt->get_result()) !== false) 
            return $res->fetch_all(MYSQLI_ASSOC);
            
        return true;
    }
}

TriviaController.php

<?php

class TriviaController {

    private $db;
    
    private $url = "/inclass/trivia4";
    
    public function __construct() {
        $this->db = new Database();
    }
    
    public function run($command) {
        
        switch($command) {
            case "question":
                $this->question();
                break;
            case "logout":
                $this->destroySession();
            case "login":
            default:
                $this->login();
                break;
        }
            
    }
    
    private function destroySession() {          
        session_destroy();

        session_start();
    }
    
    
    public function login() {
        // our login code from index.php last time!
        $error_msg = "";
        if (isset($_POST["email"])) { /// validate the email coming in
            $data = $this->db->query("select * from user where email = ?;", "s", $_POST["email"]);
            if ($data === false) {
                $error_msg = "Error checking for user";
            } else if (!empty($data)) { 
                // user was found!
                // validate the user's password
                if (password_verify($_POST["password"], $data[0]["password"])) {
                    $_SESSION["name"] = $data[0]["name"];
                    $_SESSION["email"] = $data[0]["email"];
                    $_SESSION["score"] = $data[0]["score"];
                    header("Location: {$this->url}/question/");
                    return;
                } else {
                    $error_msg = "Invalid Password";
                }
            } else {
                $hash = password_hash($_POST["password"], PASSWORD_DEFAULT);
                $insert = $this->db->query("insert into user (name, email, password) values (?, ?, ?);", "sss", $_POST["name"], $_POST["email"], $hash);
                if ($insert === false) {
                    $error_msg = "Error creating new user";
                } 
                
                $_SESSION["name"] = $_POST["name"];
                $_SESSION["email"] = $_POST["email"];
                $_SESSION["score"] = 0;
                header("Location: {$this->url}/question/");
                return;
            }

        }

        include "templates/login.php";
    }
    
    
    public function question() {
        // Our php code from question.php last time!

        $data = $this->db->query("select id, question from question order by rand() limit 1;");
        if (!isset($data[0])) {
            die("No questions in the database");
        }
        $question = $data[0];

        $message = "";

        if (isset($_POST["questionid"])) {
            $qid = $_POST["questionid"];
            $answer = $_POST["answer"];
            
            $data = $this->db->query("select * from question where id = ?;", "i", $qid);
            if ($data === false) {
                // did not work
                $message = "<div class='alert alert-info'>Error: could not find previous question</div>";
            } else if (!isset($data[0])) {
                // worked
                $message = "<div class='alert alert-info'>Error: could not find previous question</div>";
            } else {
                // found question
                if ($data[0]["answer"] == $answer) {
                    // user answered correctly -- perhaps we should also be better about how we
                    // verify their answers, perhaps use strtolower() to compare lower case only.
                    $message = "<div class='alert alert-success'><b>$answer</b> was correct!</div>";
                    
                    // Update the score in the session object
                    $_SESSION["score"] += $data[0]["points"];
                    // Update the score in the database using the SQL UPDATE query
                    $this->db->query("update user set score  = ? where email = ?;", "is", $_SESSION["score"], $_SESSION["email"]);
                } else { 
                    $message = "<div class='alert alert-danger'><b>$answer</b> was incorrect! The answer was: {$data[0]['answer']}</div>";
                }
            }
        }
        
        // set user information for the page
        $user = [
            "name" => $_SESSION["name"],
            "email" => $_SESSION["email"],
            "score" => $_SESSION["score"]
        ];
        
        include("templates/question.php");
    }
}

login.php (template)

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">  

        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="author" content="your name">
        <meta name="description" content="include some description about your page">  

        <title>Trivia Game Login</title>

        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous"> 
    </head>

    <body>


        <div class="container" style="margin-top: 15px;">
            <div class="row col-xs-8">
                <h1>CS4640 Television Trivia Game - Get Started</h1>
                <p> Welcome to our trivia game!  To get started, login below or enter a new username and password to create an account</p>
            </div>
            <div class="row justify-content-center">
                <div class="col-4">
                <?php
                    if (!empty($error_msg)) {
                        echo "<div class='alert alert-danger'>$error_msg</div>";
                    }
                ?>
                <form action="<?=$this->url?>/login/" method="post">
                    <div class="mb-3">
                        <label for="email" class="form-label">Email</label>
                        <input type="email" class="form-control" id="email" name="email"/>
                    </div>
                    <div class="mb-3">
                        <label for="name" class="form-label">Name</label>
                        <input type="text" class="form-control" id="name" name="name"/>
                    </div>
                    
                    <div class="mb-3">
                        <label for="password" class="form-label">Password</label>
                        <input type="password" class="form-control" id="password" name="password"/>
                    </div>
                   
                    <div class="text-center">                
                    <button type="submit" class="btn btn-primary">Log in / Create Account</button>
                    </div>
                </form>
                </div>
            </div>
        </div>

        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script>
    </body>
</html>

question.php (template)

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">  

        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="author" content="your name">
        <meta name="description" content="include some description about your page">  

        <title>Trivia Game</title>

        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous"> 
    </head>

    <body>


        <div class="container" style="margin-top: 15px;">
            <div class="row col-xs-8">
                <h1>CS4640 Television Trivia Game</h1>
                <h3>Hello <?=$user["name"]?>! Score: <?=$user["score"]?></h3>
            </div>
            <div class="row">
                <div class="col-xs-8 mx-auto">
                <form action="<?=$this->url?>/question/" method="post">
                    <div class="h-100 p-5 bg-light border rounded-3">
                    <h2>Question</h2>
                    <p><?=$question["question"]?></p>
                    <input type="hidden" name="questionid" value="<?=$question["id"]?>"/>
                    </div>
                    <?=$message?>
                    <div class="h-10 p-5 mb-3">
                        <input type="text" class="form-control" id="answer" name="answer" placeholder="Type your answer here">
                    </div>
                    <div class="text-center">                
                    <button type="submit" class="btn btn-primary">Submit</button>
                    <a href="<?=$this->url?>/logout/" class="btn btn-danger">Log out</a>
                    </div>
                </form>
                </div>
            </div>
        </div>


        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script>
    </body>
</html>