User account management, roles, permissions, authentication PHP and MySQL - Part 4


This is part 4 of a series on how to create a user accounts management system in PHP. You can find the other parts here: part1, part2, part 3.

So far we have covered user registration and login for the public area. An admin user can log in even as it is now but we haven't yet worked at creating admin user accounts. Also, if you just enter http://localhost/user-accounts/admin/dashboard.php on the browser, you can access the admin section without being an admin user. But we will fix all of that soon.

In this part, we will be creating, updating admin user accounts. We will also verify that the old password matches before updating a user account.

Inside admin/users folder, create these 3 files:

  1. userForm.php: Contains the form for creating and editing user accounts.
  2. userList.php: Lists all the administrative users on the system.
  3. userLogic.php: In MVC (Model-View-Controller)  parlance, we can refer to this as a user controller. It contains the logic such as receiving user info from the form, saving it in the database, retrieving it again, manipulating it, and so on.

Let's start with userForm.php. Open it up and paste this code into it.

userForm.php:

<?php include('../../config.php'); ?>
<?php include(INCLUDE_PATH . '/logic/common_functions.php') ?>
<?php include(ROOT_PATH . '/admin/users/userLogic.php'); ?>
<?php $roles = getAllRoles(); ?>

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>UserAccounts - Create Admin user Account</title>
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
    <!-- Custome styles -->
    <link rel="stylesheet" href="../../assets/css/style.css">
  </head>
  <body>
    <?php include(INCLUDE_PATH . "/layouts/admin_navbar.php") ?>
    <div class="container" style="margin-bottom: 150px;">
      <div class="row">
        <div class="col-md-4 col-md-offset-4">
          <a href="userList.php" class="btn btn-primary" style="margin-bottom: 5px;">
            <span class="glyphicon glyphicon-chevron-left"></span>
            Users
          </a>
          <br>

          <form class="form" action="userForm.php" method="post" enctype="multipart/form-data">
            <?php if ($isEditing === true ): ?>
              <h2 class="text-center">Update Admin user</h2>
            <?php else: ?>
              <h2 class="text-center">Create Admin user</h2>
            <?php endif; ?>
            <hr>
            <!-- if editting user, we need that user's id -->
            <?php if ($isEditing === true): ?>
              <input type="hidden" name="user_id" value="<?php echo $user_id ?>">
            <?php endif; ?>
            <div class="form-group <?php echo isset($errors['username']) ? 'has-error' : '' ?>">
              <label class="control-label">Username</label>
              <input type="text" name="username" value="<?php echo $username; ?>" class="form-control">
              <?php if (isset($errors['username'])): ?>
                <span class="help-block"><?php echo $errors['username'] ?></span>
              <?php endif; ?>
            </div>
            <div class="form-group <?php echo isset($errors['email']) ? 'has-error' : '' ?>">
              <label class="control-label">Email Address</label>
              <input type="email" name="email" value="<?php echo $email; ?>" class="form-control">
              <?php if (isset($errors['email'])): ?>
                <span class="help-block"><?php echo $errors['email'] ?></span>
              <?php endif; ?>
            </div>
            <?php if ($isEditing === true ): ?>
              <div class="form-group <?php echo isset($errors['passwordOld']) ? 'has-error' : '' ?>">
                <label class="control-label">Old Password</label>
                <input type="password" name="passwordOld" class="form-control">
                <?php if (isset($errors['passwordOld'])): ?>
                  <span class="help-block"><?php echo $errors['passwordOld'] ?></span>
                <?php endif; ?>
              </div>
            <?php endif; ?>
            <div class="form-group <?php echo isset($errors['password']) ? 'has-error' : '' ?>">
              <label class="control-label">Your Password</label>
              <input type="password" name="password" class="form-control">
              <?php if (isset($errors['password'])): ?>
                <span class="help-block"><?php echo $errors['password'] ?></span>
              <?php endif; ?>
            </div>
            <div class="form-group <?php echo isset($errors['role_id']) ? 'has-error' : '' ?>">
              <label class="control-label">User Role</label>
              <select class="form-control" name="role_id">
                <option value="" ></option>
                <?php foreach ($roles as $role): ?>
                  <?php if ($role['id'] === $role_id): ?>
                    <option value="<?php echo $role['id'] ?>" selected><?php echo $role['name'] ?></option>
                  <?php else: ?>
                    <option value="<?php echo $role['id'] ?>"><?php echo $role['name'] ?></option>
                  <?php endif; ?>
                <?php endforeach; ?>
              </select>
              <?php if (isset($errors['role_id'])): ?>
                <span class="help-block"><?php echo $errors['role_id'] ?></span>
              <?php endif; ?>
            </div>
            <div class="form-group" style="text-align: center;">
              <?php if (!empty($profile_picture)): ?>
                <img src="<?php echo BASE_URL . '/assets/images/' . $profile_picture; ?>" id="profile_img" style="height: 100px; border-radius: 50%" alt="">
              <?php else: ?>
                <img src="http://via.placeholder.com/150x150" id="profile_img" style="height: 100px; border-radius: 50%" alt="">
              <?php endif; ?>
              <input type="file" name="profile_picture" id="profile_input" value="" style="display: none;">
            </div>
            <div class="form-group">
              <?php if ($isEditing === true): ?>
                <button type="submit" name="update_user" class="btn btn-success btn-block btn-lg">Update user</button>
              <?php else: ?>
                <button type="submit" name="save_user" class="btn btn-success btn-block btn-lg">Save user</button>
              <?php endif; ?>
            </div>
          </form>
        </div>
      </div>
    </div>
  <?php include(INCLUDE_PATH . "/layouts/footer.php") ?>
<script type="text/javascript" src="../../assets/js/display_profile_image.js"></script>

If we open this page up in our browser at http://localhost/user-accounts/admin/users/userForm.php, we see an error message that says we are calling an undefined method getAllRoles(). We need this method because, in order to create an admin user, we need to select a role from the list of all the roles in the database to assign to this user. So we will get all roles from the database and populate them on an option-select field in the form.

We will create this method inside the userLogic.php file. Like so:

userLogic.php:

<?php
  // variable declaration. These variables will be used in the user form
  $user_id = 0;
  $role_id = NULL;
  $username = "";
  $email = "";
  $password = "";
  $passwordConf = "";
  $profile_picture = "";
  $isEditing = false;
  $users = array();
  $errors = array();

  function getAllRoles(){
    global $conn;
    $sql = "SELECT id, name FROM roles";
    $stmt = $conn->prepare($sql);
    $stmt->execute();
    $result = $stmt->get_result();
    $roles = $result->fetch_all(MYSQLI_ASSOC);
    return $roles;
  }

Refresh on your browser now you will see that the error is gone and our form now stands clean at the center of the page. Nice!

If you click on the role dropdown on the form, you will notice there are no roles yet. This is because we had created the roles table in the database but we had not added roles to it. Using PHPMyAdmin or any MySQL client you have, add the following three roles in the roles table in our database: Admin, Editor, and Author.

Or you can simply run this SQL insert command to insert all three roles at once:

INSERT INTO `roles`(`id`, `name`, `description`) 
VALUES  (1, 'Admin', 'Has authority of users and roles and permissions.' ), 
		(2, 'Author', 'Has full authority of own posts'), 
		(3, 'Editor', 'Has full authority over all posts')

If you reload the page, these roles will now become available in your role select field.

At this point, we cannot yet create a user. But the form is all set. All that remains is the code which receives the values submitted by the form. We will place this code in the userLogic.php file. Open it once again and let's add the remaining code required for creating, updating, editing and deleting the user.

userLogic.php:

// ... variables declaration is up here ...
// ACTION: update user
if (isset($_POST['update_user'])) { // if user clicked update_user button ...
    $user_id = $_POST['user_id'];
    updateUser($user_id);
}
// ACTION: Save User
if (isset($_POST['save_user'])) {  // if user clicked save_user button ...
    saveUser();
}
// ACTION: fetch user for editting
if (isset($_GET["edit_user"])) {
  $user_id = $_GET["edit_user"];
  editUser($user_id);
}
// ACTION: Delete user
if (isset($_GET['delete_user'])) {
  $user_id = $_GET['delete_user'];
  deleteUser($user_id);
}

function updateUser($user_id) {
  global $conn, $errors, $username, $role_id, $email, $isEditing;
  $errors = validateUser($_POST, ['update_user', 'update_profile']);

  // receive all input values from the form
  $username = $_POST['username'];
  $email = $_POST['email'];
  $password = password_hash($_POST['password'], PASSWORD_DEFAULT); //encrypt the password before saving in the database
  $profile_picture = uploadProfilePicture();
  if (count($errors) === 0) {
    if (isset($_POST['role_id'])) {
      $role_id = $_POST['role_id'];
    }
    $sql = "UPDATE users SET username=?, role_id=?, email=?, password=?, profile_picture=? WHERE id=?";
    $result = modifyRecord($sql, 'sisssi', [$username, $role_id, $email, $password, $profile_picture, $user_id]);

    if ($result) {
      $_SESSION['success_msg'] = "User account successfully updated";
      header("location: " . BASE_URL . "admin/users/userList.php");
      exit(0);
    }
  } else {
    // continue editting if there were errors
    $isEditing = true;
  }
}
// Save user to database
function saveUser(){
  global $conn, $errors, $username, $role_id, $email, $isEditing;
  $errors = validateUser($_POST, ['save_user']);
  // receive all input values from the form
  $username = $_POST['username'];
  $email = $_POST['email'];
  $password = password_hash($_POST['password'], PASSWORD_DEFAULT); //encrypt the password before saving in the database
  $profile_picture = uploadProfilePicture(); // upload profile picture and return the picture name
  if (count($errors) === 0) {
    if (isset($_POST['role_id'])) {
      $role_id = $_POST['role_id'];
    }
    $sql = "INSERT INTO users SET username=?, role_id=?, email=?, password=?, profile_picture=?";
    $result = modifyRecord($sql, 'sisss', [$username, $role_id, $email, $password, $profile_picture]);

    if($result){
      $_SESSION['success_msg'] = "User account created successfully";
      header("location: " . BASE_URL . "admin/users/userList.php");
      exit(0);
    } else {
      $_SESSION['error_msg'] = "Something went wrong. Could not save user in Database";
    }
  }
}
function getAdminUsers(){
  global $conn;
  // for every user, select a user role name from roles table, and then id, role_id and username from user table
  // where the role_id on user table matches the id on roles table
  $sql = "SELECT r.name as role, u.id, u.role_id, u.username
          FROM users u
          LEFT JOIN roles r ON u.role_id=r.id
          WHERE role_id IS NOT NULL AND u.id != ?";

  $users = getMultipleRecords($sql, 'i', [$_SESSION['user']['id']]);
  return $users;
}

function editUser($user_id){
  global $conn, $user_id, $role_id, $username, $email, $isEditing, $profile_picture;

  $sql = "SELECT * FROM users WHERE id=?";
  $user = getSingleRecord($sql, 'i', [$user_id]);

  $user_id = $user['id'];
  $role_id = $user['role_id'];
  $username = $user['username'];
  $profile_picture = $user['profile_picture'];
  $email = $user['email'];
  $isEditing = true;
}
function deleteUser($user_id) {
  global $conn;
  $sql = "DELETE FROM users WHERE id=?";
  $result = modifyRecord($sql, 'i', [$user_id]);

  if ($result) {
    $_SESSION['success_msg'] = "User trashed!!";
    header("location: " . BASE_URL . "admin/users/userList.php");
    exit(0);
  }
}

Without filling the form, click on the 'Save user' button and you will see that the validation messages appear on the form. We are using the same validateUser() function we defined a while back. So you see how refactoring our code into such methods saves us from repeating code. Even image upload is handled by the uploadProfileImage() had defined in one of the previous tutorials.

Let's create our first admin user. Fill out the form and click the 'Save user' button. This saves our Admin user in the database and redirects to the userList.php page which is empty for now.

The userList.php file is supposed to list the admin users available in the database. So let's write the code for that.

userList.php:

<?php include('../../config.php') ?>
<?php include(ROOT_PATH . '/admin/users/userLogic.php') ?>
<?php
  $adminUsers = getAdminUsers();
?>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Admin Area - Users </title>
  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
  <!-- Custome styles -->
  <link rel="stylesheet" href="../../static/css/style.css">
</head>
<body>
  <?php include(INCLUDE_PATH . "/layouts/admin_navbar.php") ?>
  <div class="col-md-8 col-md-offset-2">
    <a href="userForm.php" class="btn btn-success">
      <span class="glyphicon glyphicon-plus"></span>
      Create new user
    </a>
    <hr>
    <h1 class="text-center">Admin Users</h1>
    <br />
    <?php if (isset($users)): ?>
      <table class="table table-bordered">
        <thead>
          <tr>
            <th>N</th>
            <th>Username</th>
            <th>Role</th>
            <th colspan="2" class="text-center">Action</th>
          </tr>
        </thead>
        <tbody>
          <?php foreach ($adminUsers as $key => $value): ?>
            <tr>
              <td><?php echo $key + 1; ?></td>
              <td><?php echo $value['username'] ?></td>
              <td><?php echo $value['role']; ?></td>
              <td class="text-center">
                <a href="<?php echo BASE_URL ?>admin/users/userForm.php?edit_user=<?php echo $value['id'] ?>" class="btn btn-sm btn-success">
                  <span class="glyphicon glyphicon-pencil"></span>
                </a>
              </td>
              <td class="text-center">
                <a href="<?php echo BASE_URL ?>admin/users/userForm.php?delete_user=<?php echo $value['id'] ?>" class="btn btn-sm btn-danger">
                  <span class="glyphicon glyphicon-trash"></span>
                </a>
              </td>
            </tr>
          <?php endforeach; ?>
        </tbody>
      </table>
    <?php else: ?>
      <h2 class="text-center">No users in database</h2>
    <?php endif; ?>
  </div>
  <?php include(INCLUDE_PATH . "/layouts/footer.php") ?>
</body>
</html>

In our userLogic.php file from a while ago, we included a method called getAdminUsers(). This method selects all admin users from the database to be displayed.

Just refresh the userList.php page on the browser and voila! We have our first Admin user listed on a table. Click on the green button with the pencil icon to edit the user. You can also click on the red button with the trash icon to delete the user.

User Roles

Now we can create our user and assign roles to them but what if we want to add another role to the system? We can't rely on running a command directly on our database everytime we want to create a role, right? Let's Just wrap up this section with Creating, updating and deleting roles.

Navigate to the admin/roles folder and create three files: roleForm.php, roleList.php, and roleLogic.php. (Similar to users folder, no?)

roleForm.php:

<?php include('../../config.php') ?>
<?php include(ROOT_PATH . '/includes/logic/common_functions.php') ?>
<?php include(ROOT_PATH . '/admin/roles/roleLogic.php') ?>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Admin - Create new role </title>
  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
  <!-- Custom styles -->
  <link rel="stylesheet" href="../../static/css/style.css">
</head>
<body>
  <?php include(INCLUDE_PATH . "/layouts/admin_navbar.php") ?>
  <div class="col-md-8 col-md-offset-2">
      <a href="roleList.php" class="btn btn-primary">
        <span class="glyphicon glyphicon-chevron-left"></span>
        Roles
      </a>
      <hr>
      <form class="form" action="roleForm.php" method="post">
        <?php if ($isEditting === true): ?>
          <h1 class="text-center">Update Role</h1>
        <?php else: ?>
          <h1 class="text-center">Create Role</h1>
        <?php endif; ?>
        <br />

        <?php if ($isEditting === true): ?>
          <input type="hidden" name="role_id" value="<?php echo $role_id ?>">
        <?php endif; ?>
        <div class="form-group <?php echo isset($errors['name']) ? 'has-error': '' ?>">
          <label class="control-label">Role name</label>
          <input type="text" name="name" value="<?php echo $name; ?>" class="form-control">
          <?php if (isset($errors['name'])): ?>
            <span class="help-block"><?php echo $errors['name'] ?></span>
          <?php endif; ?>
        </div>
        <div class="form-group <?php echo isset($errors['description']) ? 'has-error': '' ?>">
          <label class="control-label">Description</label>
          <textarea name="description" value="<?php echo $description; ?>"  rows="3" cols="10" class="form-control"><?php echo $description; ?></textarea>
          <?php if (isset($errors['description'])): ?>
            <span class="help-block"><?php echo $errors['description'] ?></span>
          <?php endif; ?>
        </div>
        <div class="form-group">
          <?php if ($isEditting === true): ?>
            <button type="submit" name="update_role" class="btn btn-primary">Update Role</button>
          <?php else: ?>
            <button type="submit" name="save_role" class="btn btn-success">Save Role</button>
          <?php endif; ?>
        </div>
      </form>
  </div>
  <?php include(INCLUDE_PATH . "/layouts/footer.php") ?>
</body>
</html>

This is very similar to what we did in the users case so I won't do much explaining here. We proceed now to the roleLogic.php where we write the code required for creating, updating and deleting roles.

roleLogic.php:

<?php
  $role_id = 0;
  $name = "";
  $description = "";
  $isEditting = false;
  $roles = array();
  $errors = array();

  // ACTION: update role
  if (isset($_POST['update_role'])) {
      $role_id = $_POST['role_id'];
      updateRole($role_id);
  }
  // ACTION: Save Role
  if (isset($_POST['save_role'])) {
      saveRole();
  }
  // ACTION: fetch role for editting
  if (isset($_GET["edit_role"])) {
    $role_id = $_GET['edit_role'];
    editRole($role_id);
  }
  // ACTION: Delete role
  if (isset($_GET['delete_role'])) {
    $role_id = $_GET['delete_role'];
    deleteRole($role_id);
  }
  // Save role to database
  function saveRole(){
    global $conn, $errors, $name, $description;
    $errors = validateRole($_POST, ['save_role']);
    if (count($errors) === 0) {
       // receive form values
       $name = $_POST['name'];
       $description = $_POST['description'];
       $sql = "INSERT INTO roles SET name=?, description=?";
       $result = modifyRecord($sql, 'ss', [$name, $description]);

       if ($result) {
         $_SESSION['success_msg'] = "Role created successfully";
         header("location: " . BASE_URL . "admin/roles/roleList.php");
         exit(0);
       } else {
         $_SESSION['error_msg'] = "Something went wrong. Could not save role in Database";
       }
    }
  }
  function updateRole($role_id){
    global $conn, $errors, $name, $isEditting; // pull in global form variables into function
    $errors = validateRole($_POST, ['update_role']); // validate form
    if (count($errors) === 0) {
      // receive form values
      $name = $_POST['name'];
      $description = $_POST['description'];
      $sql = "UPDATE roles SET name=?, description=? WHERE id=?";
      $result = modifyRecord($sql, 'ssi', [$name, $description, $role_id]);

      if ($result) {
        $_SESSION['success_msg'] = "Role successfully updated";
        $isEditting = false;
        header("location: " . BASE_URL . "admin/roles/roleList.php");
        exit(0);
      } else {
        $_SESSION['error_msg'] = "Something went wrong. Could not save role in Database";
      }
    }
  }
  function editRole($role_id){
    global $conn, $name, $description, $isEditting;
    $sql = "SELECT * FROM roles WHERE id=? LIMIT 1";
    $role = getSingleRecord($sql, 'i', [$role_id]);

    $role_id = $role['id'];
    $name = $role['name'];
    $description = $role['description'];
    $isEditting = true;
  }
  function deleteRole($role_id) {
    global $conn;
    $sql = "DELETE FROM roles WHERE id=?";
    $result = modifyRecord($sql, 'i', [$role_id]);
    if ($result) {
      $_SESSION['success_msg'] = "Role trashed!!";
      header("location: " . BASE_URL . "admin/roles/roleList.php");
      exit(0);
    }
  }
  function getAllRoles(){
    global $conn;
    $sql = "SELECT id, name FROM roles";
    $roles = getMultipleRecords($sql);
    return $roles;
  }

But when you click on the 'Save Role' button, It gives a warning about an undefined validateRole() method. Just like the validateUser() for users, we will add this validateRole() method in our common_functions.php file. So open the file and add this function to the bottom of it.

common_functions.php:

// ... other functions up here ...

// Accept a post object, validates post and return an array with the error messages
function validateRole($role, $ignoreFields) {
    global $conn;
    $errors = [];
    foreach ($role as $key => $value) {
      if (in_array($key, $ignoreFields)) {
          continue;
      }
      if (empty($role[$key])) {
        $errors[$key] = "This field is required";
      }
    }
    return $errors;
}

Click Save Role button again and you see the error messages displaying.

Next up is the roleList.php file. 

roleList.php:

<?php include('../../config.php') ?>
<?php include(ROOT_PATH . '/admin/roles/roleLogic.php') ?>
<?php
  $roles = getAllRoles();
?>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Admin Area - User Roles </title>
  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
  <!-- Custome styles -->
  <link rel="stylesheet" href="../../static/css/style.css">
</head>
<body>
  <?php include(INCLUDE_PATH . "/layouts/admin_navbar.php") ?>
  <div class="col-md-8 col-md-offset-2">
    <a href="roleForm.php" class="btn btn-success">
      <span class="glyphicon glyphicon-plus"></span>
      Create new role
    </a>
    <hr>
    <h1 class="text-center">User Roles</h1>
    <br />
    <?php if (isset($roles)): ?>
      <table class="table table-bordered">
        <thead>
          <tr>
            <th>N</th>
            <th>Role name</th>
            <th colspan="3" class="text-center">Action</th>
          </tr>
        </thead>
        <tbody>
          <?php foreach ($roles as $key => $value): ?>
            <tr>
              <td><?php echo $key + 1; ?></td>
              <td><?php echo $value['name'] ?></td>
              <td class="text-center">
                <a href="<?php echo BASE_URL ?>admin/roles/assignPermissions.php?assign_permissions=<?php echo $value['id'] ?>" class="btn btn-sm btn-info">
                  permissions
                </a>
              </td>
              <td class="text-center">
                <a href="<?php echo BASE_URL ?>admin/roles/roleForm.php?edit_role=<?php echo $value['id'] ?>" class="btn btn-sm btn-success">
                  <span class="glyphicon glyphicon-pencil"></span>
                </a>
              </td>
              <td class="text-center">
                <a href="<?php echo BASE_URL ?>admin/roles/roleForm.php?delete_role=<?php echo $value['id'] ?>" class="btn btn-sm btn-danger">
                  <span class="glyphicon glyphicon-trash"></span>
                </a>
              </td>
            </tr>
          <?php endforeach; ?>
        </tbody>
      </table>
    <?php else: ?>
      <h2 class="text-center">No roles in database</h2>
    <?php endif; ?>
  </div>
  <?php include(INCLUDE_PATH . "/layouts/footer.php") ?>
</body>
</html>

On your browser, go to http://localhost/user-accounts/admin/roles/roleList.php. And now we can create, edit, update and delete roles too.

Once again thanks for following. Hopefully, I'll see you in the next part where we work on permissions and user profile editting.

Awa Melvine

Share Google Plus Linkedin


Comments