2
\$\begingroup\$

I have been working on my script which allows people to sign-up and login to my website. I was wondering if someone could review the code. It works but I can't help but feel it could be optimized.

forgot_password.php

<?php
// core configuration
require_once "config/core.php";

// include login checker
require_once "login_checker.php";

// include classes
require_once "config/database.php";
require_once 'objects/user.php';
require_once "libs/php/utils.php";

// get database connection
$database = new Database();
$db = $database->getConnection();

// initialize objects
$user = new User($db);
$utils = new Utils();

// header HTML
require_once "layout_head.php";

// if the login form was submitted
if ($_POST) {

    echo "<div class='col-sm-12'>";

    // check if username and password are in the database
    $user->email = $_POST['email'];

    if ($user->emailExists()) {

        // update access code for user
        $access_code = $utils->getToken();

        $user->access_code = $access_code;
        if ($user->updateAccessCode()) {

            // send reset link
            $body="Hi there.<br /><br />";
            $body.="Please click the following link to reset your password: {$home_url}reset_password/?access_code={$access_code}";
            $subject="Reset Password";
            $send_to_email=$_POST['email'];

            if ($utils->sendEmailViaPhpMail($send_to_email, $subject, $body)) {
                echo "<div class='alert alert-info'>
                            Password reset link was sent to your email.
                            Click that link to reset your password.
                        </div>";
            } else {
                // message if unable to send email for password reset link
                echo "<div class='alert alert-danger'>ERROR: Unable to send reset link.</div>";
            }
        } else {
            // message if unable to update access code
            echo "<div class='alert alert-danger'>ERROR: Unable to update access code.</div>";
        }

    } else {
        // message if email does not exist
        echo "<div class='alert alert-danger'>Your email cannot be found.</div>";
    }

    echo "</div>";
}
?>

        <div class='col-md-4'></div>
        <div class='col-md-4'>

            <div class='account-wall'>
                <div id='my-tab-content' class='tab-content'>
                    <div class='tab-pane active' id='login'>
                        <img class='profile-img' src='images/login-icon.png'>
                        <form class='form-signin' action='<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>' method='post'>
                            <input type='email' name='email' class='form-control' placeholder='Your email' required autofocus>
                            <input type='submit' class='btn btn-lg btn-primary btn-block' value='Send Reset Link' style='margin-top:1em;' />
                        </form>
                    </div>
                </div>
            </div>

        </div>
        <div class='col-md-4'></div>

<?php
// footer HTML and JavaScript codes
require_once "layout_foot.php";
?>

index.php

<?php
// core configuration
require_once "config/core.php";

// include login checker
$require_login = true;
require_once "login_checker.php";

// header HTML
require_once 'layout_head.php';
?>
        <div class='col-md-12'>
            <p>Hi, <?php echo $_SESSION['firstname']; ?></p>
        </div>
<?php
// footer HTML and JavaScript codes
require 'layout_foot.php';
?>

login.php

<?php
// core configuration
require_once "config/core.php";

// include login checker
$require_login = false;
require_once "login_checker.php";

// default to false
$access_denied = false;

// if the login form was submitted
if ($_POST) {
    // include classes
    include_once "config/database.php";
    include_once "objects/user.php";

    // get database connection
    $database = new Database();
    $db = $database->getConnection();

    // initialize objects
    $user = new User($db);

    // check if email and password are in the database
    $user->email = $_POST['email'];

    // check if email exists, also get user details using this emailExists() method
    $email_exists = $user->emailExists();

    // validate login
    if ($email_exists && password_verify($_POST['password'], $user->password) && $user->status==1) {

        // if it is, set the session value to true
        $_SESSION['logged_in'] = true;
        $_SESSION['user_id'] = $user->id;
        $_SESSION['access_level'] = $user->access_level;
        $_SESSION['firstname'] = htmlspecialchars($user->firstname, ENT_QUOTES, 'UTF-8');
        $_SESSION['lastname'] = $user->lastname;

        // if the access level detected is 'Administrator', redirect the user to the administrator section
        if ($user->access_level=='Administrator') {
            header("Location: {$home_url}admin/index.php");
        } else {
            // else, redirect only to 'Customer' section
            header("Location: {$home_url}index.php");
        }
    } else {
        // if username does not exist or password is wrong
        $access_denied = true;
    }
}

// header HTML
require_once "layout_head.php";
?>
        <div class='col-sm-6 col-md-4 col-md-offset-4'>
<?php $action = isset($_GET['action']) ? $_GET['action'] : ""; ?>

<?php if ($action =='not_yet_logged_in') : ?>
        <div class='alert alert-danger margin-top-40' role='alert'>
            Please login.
        </div>

<?php elseif ($action =='account_verified') : ?>
        <div class='alert alert-info'>
            Your account has been verified, you can now login.
        </div>

<?php elseif ($action =='password_reset') : ?>
        <div class='alert alert-info'>
            Password was reset. Please login.
        </div>

<?php elseif ($access_denied) : ?>
        <div class='alert alert-danger margin-top-40' role='alert'>
            Access Denied.<br /><br />
            Your username or password maybe incorrect
        </div>

<?php endif; ?>
            <div class='account-wall'>
                <div id='my-tab-content' class='tab-content'>
                    <div class='tab-pane active' id='login'>
                        <img class='profile-img' src='images/login-icon.png'>
                        <form class='form-signin' action='<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>' method='post'>
                            <input type='text' name='email' class='form-control' placeholder='Email' required autofocus />
                            <input type='password' name='password' class='form-control' placeholder='Password' required />
                            <input type='submit' class='btn btn-lg btn-primary btn-block' value='Log In' />
                            <div class='margin-1em-zero text-align-center'>
                                <a href='<?php echo $home_url; ?>forgot_password.php'>Forgot password?</a>
                            </div>
                        </form>
                    </div>
                </div>
            </div>

        </div>
<?php
// footer HTML and JavaScript codes
require_once "layout_foot.php";
?>

login_checker.php

<?php
// login checker for the 'Customer' access level

// if the access level detected was not 'Administrator', redirect the user to the login page
if (isset($_SESSION['access_level']) && $_SESSION['access_level']=="Administrator") {
    header("Location: {$home_url}admin/index.php");
}

// if $require_login was set and value is 'true'
else if (isset($require_login) && $require_login==true) {
    // if user not yet logged in, redirect to login page
    if (!isset($_SESSION['access_level'])) {
        header("Location: {$home_url}login.php");
    }
}

else if (isset($_SESSION['access_level']) && $_SESSION['access_level']=="Customer") {
    header("Location: {$home_url}index.php");
}


else {
    // no problem, stay on current page
}
?>

logout.php

<?php
// core configuration
require_once "config/core.php";

// destroy session, it will remove ALL session settings
session_destroy();

//redirect to login page
header("Location: {$home_url}login.php");
?>

register.php

<?php
// core configuration
require_once "config/core.php";

// include login checker
require_once "login_checker.php";

// include classes
require_once 'config/database.php';
require_once 'objects/user.php';
require_once "libs/php/utils.php";

// header HTML
require_once "layout_head.php";
?>
        <div class='col-md-12'>
<?php
// if form was posted
if ($_POST) {

    // get database connection
    $database = new Database();
    $db = $database->getConnection();

    // initialize objects
    $user = new User($db);
    $utils = new Utils();

    // set user email to detect if it already exists
    $user->email = $_POST['email'];

    // check if email already exists
    if ($user->emailExists()) {
        echo "<div class='alert alert-danger'>";
            echo "The email you specified is already registered. Please try again or <a href='{$home_url}login'>login.</a>";
        echo "</div>";
    } else {
        // set values to object properties
        $user->firstname = $_POST['firstname'];
        $user->lastname = $_POST['lastname'];
        $user->contact_number = $_POST['contact_number'];
        $user->address = $_POST['address'];
        $user->password = $_POST['password'];
        $user->access_level = 'Customer';
        $user->status = 0;

        // access code for email verification
        $access_code = $utils->getToken();
        $user->access_code = $access_code;

        // create the user
        if ($user->create()) {

            // send confimation email
            $send_to_email=$_POST['email'];
            $body="Hi {$send_to_email}.<br /><br />";
            $body.="Please click the following link to verify your email and login: {$home_url}verify/?access_code={$access_code}";
            $subject="Verification Email";

            if ($utils->sendEmailViaPhpMail($send_to_email, $subject, $body)) {
                echo "<div class='alert alert-success'>
            Verification link was sent to your email. Click that link to login.
        </div>";
            } else {
                echo "<div class='alert alert-danger'>
            User was created but unable to send verification email. Please contact admin.
        </div>";
            }

            // empty posted values
            $_POST = array();

        } else {
            echo "<div class='alert alert-danger' role='alert'>Unable to register. Please try again.</div>";
        }
    }
}
?>
            <form action='register.php' method='post' id='register'>
                <table class='table table-responsive'>

                    <tr>
                        <td class='width-30-percent'>Firstname</td>
                        <td><input type='text' name='firstname' class='form-control' required value="<?php echo isset($_POST['firstname']) ? htmlspecialchars($_POST['firstname'], ENT_QUOTES) : "";  ?>" /></td>
                    </tr>

                    <tr>
                        <td>Lastname</td>
                        <td><input type='text' name='lastname' class='form-control' required value="<?php echo isset($_POST['lastname']) ? htmlspecialchars($_POST['lastname'], ENT_QUOTES) : "";  ?>" /></td>
                    </tr>

                    <tr>
                        <td>Contact Number</td>
                        <td><input type='text' name='contact_number' class='form-control' required value="<?php echo isset($_POST['contact_number']) ? htmlspecialchars($_POST['contact_number'], ENT_QUOTES) : "";  ?>" /></td>
                    </tr>

                    <tr>
                        <td>Address</td>
                        <td><textarea name='address' class='form-control' required><?php echo isset($_POST['address']) ? htmlspecialchars($_POST['address'], ENT_QUOTES) : "";  ?></textarea></td>
                    </tr>

                    <tr>
                        <td>Email</td>
                        <td><input type='email' name='email' class='form-control' required value="<?php echo isset($_POST['email']) ? htmlspecialchars($_POST['email'], ENT_QUOTES) : "";  ?>" /></td>
                    </tr>

                    <tr>
                        <td>Password</td>
                        <td><input type='password' name='password' class='form-control' required /></td>
                    </tr>

                    <tr>
                        <td>Confirm Password</td>
                        <td>
                            <input type="password" name="confirm_password" class="form-control" required />
                            <p></p>
                            <div class="alert alert-danger">Passwords do not match!</div>
                            <p></p>
                        </td>
                    </tr>

                    <tr>
                        <td></td>
                        <td>
                            <button type="submit" class="btn btn-primary">
                                <span class="glyphicon glyphicon-plus"></span>
                                Register
                            </button>
                        </td>
                    </tr>

                </table>
            </form>
        </div>
<?php
// include page footer HTML
require_once "layout_foot.php";
?>

reset_password.php

<?php
// core configuration
require_once "config/core.php";

// include login checker
require_once "login_checker.php";

// include classes
require_once "config/database.php";
require_once "objects/user.php";

// get database connection
$database = new Database();
$db = $database->getConnection();

// initialize objects
$user = new User($db);

// header HTML
require_once "layout_head.php";
?>
        <div class='col-sm-12'>
<?php
// get given access code
$access_code = isset($_GET['access_code']) ? $_GET['access_code'] : die("Access code not found.");

// check if access code exists
$user->access_code = $access_code;

if (!$user->accessCodeExists()) {
    die('Access code not found.');
} else {
    // if form was posted
    if ($_POST) {

        // set values to object properties
        $user->password = $_POST['password'];

        // reset password
        if ($user->updatePassword()) {
            header("Location: {$home_url}login.php?action=password_reset");
        } else {
            echo "<div class='alert alert-danger'>Unable to reset password.</div>";
        }
    }
?>
            <form action='<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>?access_code=<?php echo $access_code; ?>' method='post'>
                <table class='table table-hover table-responsive table-bordered'>
                    <tr>
                        <td>Password</td>
                        <td><input type='password' name='password' class='form-control' required></td>
                    </tr>
                    <tr>
                        <td></td>
                        <td><button type='submit' class='btn btn-primary'>Reset Password</button></td>
                    </tr>
                </table>
            </form>
        </div>
<?php
}
// include page footer HTML
require_once "layout_foot.php";
?>

verify.php

<?php
// core configuration
require_once "config/core.php";

// include classes
require_once 'config/database.php';
require_once 'objects/user.php';

// get database connection
$database = new Database();
$db = $database->getConnection();

// initialize objects
$user = new User($db);

// set access code
$user->access_code = isset($_GET['access_code']) ? $_GET['access_code'] : "";

// verify if access code exists
if (!$user->accessCodeExists()) {
    die("ERROR: Access code not found.");
} else {
    // update status
    $user->status = 1;
    $user->updateStatusByAccessCode();

    // and the redirect
    header("Location: {$home_url}login.php?action=account_verified");
}
?>

user.php

<?php
// 'user' object
class User
{

    // database connection and table name
    private $conn;
    private $table_name = "users";

    // object properties
    public $id;
    public $firstname;
    public $lastname;
    public $email;
    public $contact_number;
    public $address;
    public $password;
    public $access_level;
    public $access_code;
    public $status;
    public $created;
    public $modified;

    // constructor
    public function __construct($db)
    {
        $this->conn = $db;
    }

    // check if given email exist in the database
    function emailExists()
    {

        // query to check if email exists
        $query = "SELECT id, firstname, lastname, access_level, password, status
            FROM " . $this->table_name . "
            WHERE email = ?
            LIMIT 0,1";

        // prepare the query
        $stmt = $this->conn->prepare($query);

        // sanitize
        $this->email = htmlspecialchars(strip_tags($this->email));

        // bind given email value
        $stmt->bindParam(1, $this->email);

        // execute the query
        $stmt->execute();

        // get number of rows
        $num = $stmt->rowCount();

        // if email exists, assign values to object properties for easy access and use for php sessions
        if ($num>0) {

            // get record details / values
            $row = $stmt->fetch(PDO::FETCH_ASSOC);

            // assign values to object properties
            $this->id = $row['id'];
            $this->firstname = $row['firstname'];
            $this->lastname = $row['lastname'];
            $this->access_level = $row['access_level'];
            $this->password = $row['password'];
            $this->status = $row['status'];

            // return true because email exists in the database
            return true;
        }

        // return false if email does not exist in the database
        return false;
    }

    // create new user record
    function create()
    {

        // to get time stamp for 'created' field
        $this->created = date('Y-m-d H:i:s');

        // insert query
        $query = "INSERT INTO " . $this->table_name . "
            SET
        firstname = :firstname,
        lastname = :lastname,
        email = :email,
        contact_number = :contact_number,
        address = :address,
        password = :password,
        access_level = :access_level,
        access_code = :access_code,
        status = :status,
        created = :created";

        // prepare the query
        $stmt = $this->conn->prepare($query);

        // sanitize
        $this->firstname = htmlspecialchars(strip_tags($this->firstname));
        $this->lastname = htmlspecialchars(strip_tags($this->lastname));
        $this->email = htmlspecialchars(strip_tags($this->email));
        $this->contact_number = htmlspecialchars(strip_tags($this->contact_number));
        $this->address = htmlspecialchars(strip_tags($this->address));
        $this->password = htmlspecialchars(strip_tags($this->password));
        $this->access_level = htmlspecialchars(strip_tags($this->access_level));
        $this->access_code = htmlspecialchars(strip_tags($this->access_code));
        $this->status = htmlspecialchars(strip_tags($this->status));

        // bind the values
        $stmt->bindParam(':firstname', $this->firstname);
        $stmt->bindParam(':lastname', $this->lastname);
        $stmt->bindParam(':email', $this->email);
        $stmt->bindParam(':contact_number', $this->contact_number);
        $stmt->bindParam(':address', $this->address);

        // hash the password before saving to database
        $password_hash = password_hash($this->password, PASSWORD_BCRYPT);
        $stmt->bindParam(':password', $password_hash);

        $stmt->bindParam(':access_level', $this->access_level);
        $stmt->bindParam(':access_code', $this->access_code);
        $stmt->bindParam(':status', $this->status);
        $stmt->bindParam(':created', $this->created);

        // execute the query, also check if query was successful
        if ($stmt->execute()) {
            return true;
        } else {
            $this->showError($stmt);
            return false;
        }

    }

    public function showError($stmt)
    {
        echo "<pre>";
        print_r($stmt->errorInfo());
        echo "</pre>";
    }

    // read all user records
    function readAll($from_record_num, $records_per_page)
    {

        // query to read all user records, with limit clause for pagination
        $query = "SELECT
                id,
                firstname,
                lastname,
                email,
                contact_number,
                access_level,
                created
            FROM " . $this->table_name . "
            ORDER BY id DESC
            LIMIT ?, ?";

        // prepare query statement
        $stmt = $this->conn->prepare($query);

        // bind limit clause variables
        $stmt->bindParam(1, $from_record_num, PDO::PARAM_INT);
        $stmt->bindParam(2, $records_per_page, PDO::PARAM_INT);

        // execute query
        $stmt->execute();

        // return values
        return $stmt;
    }

    // used for paging users
    public function countAll()
    {

        // query to select all user records
        $query = "SELECT id FROM " . $this->table_name . "";

        // prepare query statement
        $stmt = $this->conn->prepare($query);

        // execute query
        $stmt->execute();

        // get number of rows
        $num = $stmt->rowCount();

        // return row count
        return $num;
    }

    // used in email verification feature
    function updateStatusByAccessCode()
    {

        // update query
        $query = "UPDATE " . $this->table_name . "
            SET status = :status
            WHERE access_code = :access_code";

        // prepare the query
        $stmt = $this->conn->prepare($query);

        // sanitize
        $this->status = htmlspecialchars(strip_tags($this->status));
        $this->access_code = htmlspecialchars(strip_tags($this->access_code));

        // bind the values from the form
        $stmt->bindParam(':status', $this->status);
        $stmt->bindParam(':access_code', $this->access_code);

        // execute the query
        if ($stmt->execute()) {
            return true;
        }

        return false;
    }

    // used in forgot password feature
    function updateAccessCode()
    {

        // update query
        $query = "UPDATE
                " . $this->table_name . "
            SET
                access_code = :access_code
            WHERE
                email = :email";

        // prepare the query
        $stmt = $this->conn->prepare($query);

        // sanitize
        $this->access_code = htmlspecialchars(strip_tags($this->access_code));
        $this->email = htmlspecialchars(strip_tags($this->email));

        // bind the values from the form
        $stmt->bindParam(':access_code', $this->access_code);
        $stmt->bindParam(':email', $this->email);

        // execute the query
        if ($stmt->execute()) {
            return true;
        }

        return false;
    }

    // check if given access_code exist in the database
    function accessCodeExists()
    {

        // query to check if access_code exists
        $query = "SELECT id
            FROM " . $this->table_name . "
            WHERE access_code = ?
            LIMIT 0,1";

        // prepare the query
        $stmt = $this->conn->prepare($query);

        // sanitize
        $this->access_code = htmlspecialchars(strip_tags($this->access_code));

        // bind given access_code value
        $stmt->bindParam(1, $this->access_code);

        // execute the query
        $stmt->execute();

        // get number of rows
        $num = $stmt->rowCount();

        // if access_code exists
        if ($num>0) {

            // return true because access_code exists in the database
            return true;
        }

        // return false if access_code does not exist in the database
        return false;

    }

    // used in forgot password feature
    function updatePassword()
    {

        // update query
        $query = "UPDATE " . $this->table_name . "
            SET password = :password
            WHERE access_code = :access_code";

        // prepare the query
        $stmt = $this->conn->prepare($query);

        // sanitize
        $this->password = htmlspecialchars(strip_tags($this->password));
        $this->access_code = htmlspecialchars(strip_tags($this->access_code));

        // bind the values from the form
        $password_hash = password_hash($this->password, PASSWORD_BCRYPT);
        $stmt->bindParam(':password', $password_hash);
        $stmt->bindParam(':access_code', $this->access_code);

        // execute the query
        if ($stmt->execute()) {
            return true;
        }

        return false;
    }

}

?>

utils.php

<?php

class Utils
{

    function getToken($length=32)
    {
        $token = "";
        $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        $codeAlphabet.= "abcdefghijklmnopqrstuvwxyz";
        $codeAlphabet.= "0123456789";
        for ($i = 0;$i<$length;$i++) {
            $token .= $codeAlphabet[$this->crypto_rand_secure(0, strlen($codeAlphabet))];
        }
        return $token;
    }

    function crypto_rand_secure($min, $max)
    {
        $range = $max - $min;
        if ($range < 0) {
            return $min; // not so random...
        }
        $log = log($range, 2);
        $bytes = (int) ($log / 8) + 1; // length in bytes
        $bits = (int) $log + 1; // length in bits
        $filter = (int) (1 << $bits) - 1; // set all lower bits to 1
        do {
            $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
            $rnd = $rnd & $filter; // discard irrelevant bits
        } while ($rnd >= $range);
        return $min + $rnd;
    }

    // send email using built in php mailer
    public function sendEmailViaPhpMail($send_to_email, $subject, $body)
    {

        $from_name = "";
        $from_email = "";

        $headers  = "MIME-Version: 1.0\r\n";
        $headers .= "Content-type: text/html; charset=iso-8859-1\r\n";
        $headers .= "From: {$from_name} <{$from_email}> \n";

        if (mail($send_to_email, $subject, $body, $headers)) {
            return true;
        } else {
            return false;
        }
    }

}
?>

core.php

<?php
// show error reporting
error_reporting(E_ALL);

// start php session
session_start();

// set your default time-zone
date_default_timezone_set('Europe/London');

// home page url
$home_url = "http://localhost/";
?>

database.php

<?php
// used to get mysql database connection
class Database
{

    // specify your own database credentials
    private $host = "";
    private $db_name = "";
    private $username = "";
    private $password = "";
    public $conn;

    // get the database connection
    public function getConnection()
    {

        $this->conn = null;

        try {
            $this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
        } catch (PDOException $exception) {
            echo "Connection error: " . $exception->getMessage();
        }

        return $this->conn;
    }
}
?>

admin/index.php

<?php
// core configuration
require_once "../config/core.php";

// check if logged in as administrator
require_once "login_checker.php";

// header HTML
require 'layout_head.php';
?>
        <div class='col-md-12'>
            <p>Hi, <?php echo $_SESSION['firstname']; ?></p>
        </div>
<?php
// include page footer HTML
require_once 'layout_foot.php';
?>

admin/login_checker.php

<?php
// login checker for the 'Administrator' access level

// if the session value is empty, the user has not yet logged in, redirect the user to the login page
if (empty($_SESSION['logged_in'])) {
    header("Location: {$home_url}login.php?action=not_yet_logged_in");
}

// if the access level detected was not 'Administrator', redirect the user to login page
else if ($_SESSION['access_level']!="Administrator") {
    header("Location: {$home_url}login.php?action=not_admin");
}

else {
    // no problem, stay on current page
}
?>
\$\endgroup\$
  • \$\begingroup\$ Where does $require_login variable come from? It doesn't seem to be defined anywhere \$\endgroup\$ – Your Common Sense Oct 16 '18 at 15:54
  • \$\begingroup\$ @YourCommonSense it's what's defined to check the user is logged in \$\endgroup\$ – Casper Oct 16 '18 at 16:00
  • \$\begingroup\$ Did you simplify your code? It looks simplified and like it's not doing much. Please take a look at the help center and include your actual code. Example code is hard to review and often leads to wrong answers. Nobody wants that. \$\endgroup\$ – Mast Oct 17 '18 at 5:45
  • 1
    \$\begingroup\$ @Mast your absolutely correct, sorry about that, I have added the complete code above, \$\endgroup\$ – Casper Oct 17 '18 at 8:20
  • 1
    \$\begingroup\$ @YourCommonSense sorry I previously didn't post the full code, I've learnt from that mistake and posted the full code above. \$\endgroup\$ – Casper Oct 17 '18 at 8:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.