sql >> Database teknologi >  >> RDS >> Mysql

Brugerkontostyring, roller, tilladelser, godkendelse PHP og MySQL - Del 2

Dette er anden del af en serie om brugerkonti management system, godkendelse, roller, tilladelser. Du kan finde den første del her.

Databasekonfiguration

Opret en MySQL-database kaldet brugerkonti. Opret derefter en fil i rodmappen på dit projekt (brugerkonti-mappen), og kald den config.php. Denne fil vil blive brugt til at konfigurere databasevariabler og derefter forbinde vores applikation til den MySQL-database, vi lige har oprettet.

config.php:

<?php
	session_start(); // start session
	// connect to database
	$conn = new mysqli("localhost", "root", "", "user-accounts");
	// Check connection
	if ($conn->connect_error) {
	    die("Connection failed: " . $conn->connect_error);
	}
  // define global constants
	define ('ROOT_PATH', realpath(dirname(__FILE__))); // path to the root folder
	define ('INCLUDE_PATH', realpath(dirname(__FILE__) . '/includes' )); // Path to includes folder
	define('BASE_URL', 'http://localhost/user-accounts/'); // the home url of the website
?>

Vi har også startet sessionen, fordi vi skal bruge den senere til at gemme loggede brugeroplysninger såsom brugernavn. I slutningen af ​​filen definerer vi konstanter, som vil hjælpe os med bedre at håndtere filinkludering.

Vores applikation er nu forbundet til MySQL-databasen. Lad os oprette en formular, der giver en bruger mulighed for at indtaste deres detaljer og registrere deres konto. Opret en signup.php-fil i projektets rodmapp:

signup.php:

<?php include('config.php'); ?>
<?php include(INCLUDE_PATH . '/logic/userSignup.php'); ?>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>UserAccounts - Sign up</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="assets/css/style.css">
</head>
<body>
  <?php include(INCLUDE_PATH . "/layouts/navbar.php") ?>

  <div class="container">
    <div class="row">
      <div class="col-md-4 col-md-offset-4">
        <form class="form" action="signup.php" method="post" enctype="multipart/form-data">
          <h2 class="text-center">Sign up</h2>
          <hr>
          <div class="form-group">
            <label class="control-label">Username</label>
            <input type="text" name="username" class="form-control">
          </div>
          <div class="form-group">
            <label class="control-label">Email Address</label>
            <input type="email" name="email" class="form-control">
          </div>
          <div class="form-group">
            <label class="control-label">Password</label>
            <input type="password" name="password" class="form-control">
          </div>
          <div class="form-group">
            <label class="control-label">Password confirmation</label>
            <input type="password" name="passwordConf" class="form-control">
          </div>
          <div class="form-group" style="text-align: center;">
            <img src="http://via.placeholder.com/150x150" id="profile_img" style="height: 100px; border-radius: 50%" alt="">
            <!-- hidden file input to trigger with JQuery  -->
            <input type="file" name="profile_picture" id="profile_input" value="" style="display: none;">
          </div>
          <div class="form-group">
            <button type="submit" name="signup_btn" class="btn btn-success btn-block">Sign up</button>
          </div>
          <p>Aready have an account? <a href="login.php">Sign in</a></p>
        </form>
      </div>
    </div>
  </div>
<?php include(INCLUDE_PATH . "/layouts/footer.php") ?>
<script type="text/javascript" src="assets/js/display_profile_image.js"></script>

På den allerførste linje i denne fil inkluderer vi config.php filen, vi oprettede tidligere, fordi vi bliver nødt til at bruge INCLUDE_PATH-konstanten, som config.php giver i vores signup.php-fil. Ved at bruge denne INCLUDE_PATH-konstant inkluderer vi også navbar.php, footer.php og userSignup.php, som indeholder logikken til at registrere en bruger i en database. Vi vil oprette disse filer meget snart.

Nær slutningen af ​​filen er der et rundt felt, hvor brugeren kan klikke for at uploade et profilbillede. Når brugeren klikker på dette område og vælger et profilbillede fra deres computer, vises der først en forhåndsvisning af dette billede.

Dette billede forhåndsvisning opnås med jquery. Når brugeren klikker på knappen upload billede, vil vi programmæssigt udløse filindtastningsfeltet ved hjælp af JQuery, og dette bringer brugerens computerfiler frem, så de kan gennemse deres computer og vælge deres profilbillede. Når de vælger billedet, bruger vi Jquery still til at vise billedet midlertidigt. Koden, der gør dette, findes i vores display_profile_image.php-fil, som vi snart vil oprette.

Vis ikke i browseren endnu. Lad os først give denne fil, hvad vi skylder den. Indtil videre, inde i assets/css-mappen, lad os oprette style.css-filen, som vi linkede i head-sektionen.

style.css:

@import url('https://fonts.googleapis.com/css?family=Lora');
* { font-family: 'Lora', serif; font-size: 1.04em; }
span.help-block { font-size: .7em; }
form label { font-weight: normal; }
.success_msg { color: '#218823'; }
.form { border-radius: 5px; border: 1px solid #d1d1d1; padding: 0px 10px 0px 10px; margin-bottom: 50px; }
#image_display { height: 90px; width: 80px; float: right; margin-right: 10px; }

På den første linje i denne fil importerer vi en Google-skrifttype ved navn 'Lora' for at få vores app til at få en smukkere skrifttype.

Den næste fil, vi skal bruge i denne signup.php, er filerne navbar.php og footer.php. Opret disse to filer i mappen Includes/layouts:

navbar.php:

<div class="container"> <!-- The closing container div is found in the footer -->
  <nav class="navbar navbar-default">
    <div class="container-fluid">
      <div class="navbar-header">
        <a class="navbar-brand" href="#">UserAccounts</a>
      </div>
      <ul class="nav navbar-nav navbar-right">
          <li><a href="<?php echo BASE_URL . 'signup.php' ?>"><span class="glyphicon glyphicon-user"></span> Sign Up</a></li>
          <li><a href="<?php echo BASE_URL . 'login.php' ?>"><span class="glyphicon glyphicon-log-in"></span> Login</a></li>
      </ul>
    </div>
  </nav>

footer.php:

    <!-- JQuery -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <!-- Bootstrap JS -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
  </div> <!-- closing container div -->
</body>
</html>

Den allersidste linje i signup.php-filen linker til et JQuery-script ved navn display_profile_image.js og det gør lige hvad navnet siger. Opret denne fil i assets/js mappen, og indsæt denne kode i den:

display_profile_image.js:

$(document).ready(function(){
  // when user clicks on the upload profile image button ...
  $(document).on('click', '#profile_img', function(){
    // ...use Jquery to click on the hidden file input field
    $('#profile_input').click();
    // a 'change' event occurs when user selects image from the system.
    // when that happens, grab the image and display it
    $(document).on('change', '#profile_input', function(){
      // grab the file
      var file = $('#profile_input')[0].files[0];
      if (file) {
          var reader = new FileReader();
          reader.onload = function (e) {
              // set the value of the input for profile picture
              $('#profile_input').attr('value', file.name);
              // display the image
              $('#profile_img').attr('src', e.target.result);
          };
          reader.readAsDataURL(file);
      }
    });
  });
});

Og til sidst, userSignup.php-filen. Denne fil er det sted, hvor tilmeldingsformularens data sendes til behandling og lagring i databasen. Opret userSignup.php inde i includes/logic-mappen og indsæt denne kode i den:

userSignup.php:

<?php include(INCLUDE_PATH . "/logic/common_functions.php"); ?>
<?php
// variable declaration
$username = "";
$email  = "";
$errors  = [];
// SIGN UP USER
if (isset($_POST['signup_btn'])) {
	// validate form values
	$errors = validateUser($_POST, ['signup_btn']);

	// receive all input values from the form. No need to escape... bind_param takes care of escaping
	$username = $_POST['username'];
	$email = $_POST['email'];
	$password = password_hash($_POST['password'], PASSWORD_DEFAULT); //encrypt the password before saving in the database
	$profile_picture = uploadProfilePicture();
	$created_at = date('Y-m-d H:i:s');

	// if no errors, proceed with signup
	if (count($errors) === 0) {
		// insert user into database
		$query = "INSERT INTO users SET username=?, email=?, password=?, profile_picture=?, created_at=?";
		$stmt = $conn->prepare($query);
		$stmt->bind_param('sssss', $username, $email, $password, $profile_picture, $created_at);
		$result = $stmt->execute();
		if ($result) {
		  $user_id = $stmt->insert_id;
			$stmt->close();
			loginById($user_id); // log user in
		 } else {
			 $_SESSION['error_msg'] = "Database error: Could not register user";
		}
	 }
}

Jeg gemte denne fil til sidst, fordi den havde mere arbejde i sig. Den første ting er, at vi inkluderer endnu en fil ved navn common_functions.php øverst i denne fil. Vi inkluderer denne fil, fordi vi bruger to metoder, der kommer fra den, nemlig:validateUser() og loginById(), som vi vil oprette snart.

Opret denne common_functions.php-fil i din include/logic-mappe:

common_functions.php:

<?php
  // Accept a user ID and returns true if user is admin and false if otherwise
  function isAdmin($user_id) {
    global $conn;
    $sql = "SELECT * FROM users WHERE id=? AND role_id IS NOT NULL LIMIT 1";
    $user = getSingleRecord($sql, 'i', [$user_id]); // get single user from database
    if (!empty($user)) {
      return true;
    } else {
      return false;
    }
  }
  function loginById($user_id) {
    global $conn;
    $sql = "SELECT u.id, u.role_id, u.username, r.name as role FROM users u LEFT JOIN roles r ON u.role_id=r.id WHERE u.id=? LIMIT 1";
    $user = getSingleRecord($sql, 'i', [$user_id]);

    if (!empty($user)) {
      // put logged in user into session array
      $_SESSION['user'] = $user;
      $_SESSION['success_msg'] = "You are now logged in";
      // if user is admin, redirect to dashboard, otherwise to homepage
      if (isAdmin($user_id)) {
        $permissionsSql = "SELECT p.name as permission_name FROM permissions as p
                            JOIN permission_role as pr ON p.id=pr.permission_id
                            WHERE pr.role_id=?";
        $userPermissions = getMultipleRecords($permissionsSql, "i", [$user['role_id']]);
        $_SESSION['userPermissions'] = $userPermissions;
        header('location: ' . BASE_URL . 'admin/dashboard.php');
      } else {
        header('location: ' . BASE_URL . 'index.php');
      }
      exit(0);
    }
  }

// Accept a user object, validates user and return an array with the error messages
  function validateUser($user, $ignoreFields) {
  		global $conn;
      $errors = [];
      // password confirmation
      if (isset($user['passwordConf']) && ($user['password'] !== $user['passwordConf'])) {
        $errors['passwordConf'] = "The two passwords do not match";
      }
      // if passwordOld was sent, then verify old password
      if (isset($user['passwordOld']) && isset($user['user_id'])) {
        $sql = "SELECT * FROM users WHERE id=? LIMIT 1";
        $oldUser = getSingleRecord($sql, 'i', [$user['user_id']]);
        $prevPasswordHash = $oldUser['password'];
        if (!password_verify($user['passwordOld'], $prevPasswordHash)) {
          $errors['passwordOld'] = "The old password does not match";
        }
      }
      // the email should be unique for each user for cases where we are saving admin user or signing up new user
      if (in_array('save_user', $ignoreFields) || in_array('signup_btn', $ignoreFields)) {
        $sql = "SELECT * FROM users WHERE email=? OR username=? LIMIT 1";
        $oldUser = getSingleRecord($sql, 'ss', [$user['email'], $user['username']]);
        if (!empty($oldUser['email']) && $oldUser['email'] === $user['email']) { // if user exists
          $errors['email'] = "Email already exists";
        }
        if (!empty($oldUser['username']) && $oldUser['username'] === $user['username']) { // if user exists
          $errors['username'] = "Username already exists";
        }
      }

      // required validation
  	  foreach ($user as $key => $value) {
        if (in_array($key, $ignoreFields)) {
          continue;
        }
  			if (empty($user[$key])) {
  				$errors[$key] = "This field is required";
  			}
  	  }
  		return $errors;
  }
  // upload's user profile profile picture and returns the name of the file
  function uploadProfilePicture()
  {
    // if file was sent from signup form ...
    if (!empty($_FILES) && !empty($_FILES['profile_picture']['name'])) {
        // Get image name
        $profile_picture = date("Y.m.d") . $_FILES['profile_picture']['name'];
        // define Where image will be stored
        $target = ROOT_PATH . "/assets/images/" . $profile_picture;
        // upload image to folder
        if (move_uploaded_file($_FILES['profile_picture']['tmp_name'], $target)) {
          return $profile_picture;
          exit();
        }else{
          echo "Failed to upload image";
        }
    }
  }

Lad mig henlede din opmærksomhed på 2 vigtige funktioner i denne fil. De er: getSingleRecord() og getMultipleRecords(). Disse funktioner er meget vigtige, fordi hvor som helst i hele vores applikation, når vi vil vælge en post fra databasen, vil vi bare kalde funktionen getSingleRecord() og sende SQL-forespørgslen til den. Hvis vi vil vælge flere poster, har du gættet det, vil vi simpelthen også kalde funktionen getMultipleRecords() og sende den relevante SQL-forespørgsel.

Disse to funktioner tager 3 parametre, nemlig SQL-forespørgslen, variabeltyperne (f.eks. betyder 's' streng, 'si' betyder streng og heltal og så videre) og til sidst en tredje parameter, som er en matrix af alle de værdier, der forespørgslen skal udføres.

For eksempel, hvis jeg vil vælge fra brugertabellen, hvor brugernavnet er 'John' og alder 24, vil jeg bare skrive min forespørgsel sådan:

$sql = SELECT * FROM users WHERE username=John AND age=20; // this is the query

$user = getSingleRecord($sql, 'si', ['John', 20]); // perform database query

I funktionskaldet repræsenterer 's' strengtype (da brugernavnet 'John' er en streng), og 'i' betyder heltal (alder 20 er et heltal). Denne funktion gør vores arbejde enormt nemt, for hvis vi ønsker at udføre en databaseforespørgsel hundrede forskellige steder i vores applikation, skal vi ikke kun bruge disse to linjer. Selve funktionerne har hver omkring 8 - 10 linjer kode, så vi er skånet for at gentage kode. Lad os implementere disse metoder på én gang.

config.php-filen vil blive inkluderet i hver fil, hvor databaseforespørgsler udføres, da den har databasekonfiguration. Så det er det perfekte sted at definere disse metoder. Åbn config.php igen og tilføj blot disse metoder til slutningen af ​​filen:

config.php:

// ...More code here ...

function getMultipleRecords($sql, $types = null, $params = []) {
  global $conn;
  $stmt = $conn->prepare($sql);
  if (!empty($params) && !empty($params)) { // parameters must exist before you call bind_param() method
    $stmt->bind_param($types, ...$params);
  }
  $stmt->execute();
  $result = $stmt->get_result();
  $user = $result->fetch_all(MYSQLI_ASSOC);
  $stmt->close();
  return $user;
}
function getSingleRecord($sql, $types, $params) {
  global $conn;
  $stmt = $conn->prepare($sql);
  $stmt->bind_param($types, ...$params);
  $stmt->execute();
  $result = $stmt->get_result();
  $user = $result->fetch_assoc();
  $stmt->close();
  return $user;
}
function modifyRecord($sql, $types, $params) {
  global $conn;
  $stmt = $conn->prepare($sql);
  $stmt->bind_param($types, ...$params);
  $result = $stmt->execute();
  $stmt->close();
  return $result;
}

Vi bruger forberedte erklæringer, og det er vigtigt af sikkerhedsmæssige årsager.

Nu tilbage til vores common_functions.php fil igen. Denne fil har 4 vigtige funktioner, som senere vil blive brugt af mange andre filer.

Når brugeren registrerer sig, vil vi gerne sikre os, at de har leveret de rigtige data, så vi kalder validateUser() funktionen, som denne fil leverer. Hvis et profilbillede blev valgt, uploader vi det ved at kalde uploadProfilePicture() funktionen, som denne fil giver.

Hvis det lykkedes at gemme brugeren i databasen, vil vi logge dem på med det samme, så vi kalder loginById() funktionen, som denne fil giver. Når en bruger logger ind, vil vi gerne vide, om de er admin eller normale, så vi kalder isAdmin() funktionen, som denne fil leverer. Hvis vi finder ud af, at de er admin (hvis isAdmin() returnerer sand), omdirigerer vi dem til dashboardet. Hvis normale brugere, omdirigerer vi til hjemmesiden.

Så du kan se vores common_functions.php-fil er meget vigtig. Vi vil bruge alle disse funktioner, når vi skal arbejde på vores admin-sektion, som i høj grad reducerer vores arbejde og undgår gentagelse af kode.

Lad os oprette brugertabellen for at gøre det muligt for brugeren at tilmelde sig. Men da brugertabellen er relateret til rolletabellen, opretter vi først rolletabellen.

rolletabel:

CREATE TABLE `roles` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(255) NOT NULL,
 `description` text NOT NULL,
  PRIMARY KEY (`id`)
)

brugertabel:

CREATE TABLE `users`(
    `id` INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
    `role_id` INT(11) DEFAULT NULL,
    `username` VARCHAR(255) UNIQUE NOT NULL,
    `email` VARCHAR(255) UNIQUE NOT NULL,
    `password` VARCHAR(255) NOT NULL,
    `profile_picture` VARCHAR(255) DEFAULT NULL,
    `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    `updated_at` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
    CONSTRAINT `users_ibfk_1` FOREIGN KEY(`role_id`) REFERENCES `roles`(`id`) ON DELETE SET NULL ON UPDATE NO ACTION
)

 Brugertabellen er relateret til rolletabellen i et Mange-til-en-forhold. Når en rolle slettes fra rolletabellen, ønsker vi, at alle brugere, der tidligere har haft dette rolle_id som deres attribut, skal have værdien sat til NULL. Det betyder, at brugeren ikke længere vil være admin.

Hvis du opretter tabellen manuelt, gør det klogt i at tilføje denne begrænsning. Hvis du bruger PHPMyAdmin, kan du gøre det ved at klikke på strukturfanen på brugertabellen, derefter relationsvisningstabellen og til sidst udfylde denne formular sådan her:

På dette tidspunkt tillader vores system en bruger at registrere sig, og efter registrering bliver de automatisk logget ind. Men efter at have logget ind, som vist i loginById() funktionen, bliver de omdirigeret til startsiden (index.php). Lad os oprette den side. I roden af ​​programmet skal du oprette en fil med navnet index.php.

index.php:

<?php include("config.php") ?>
<?php include(INCLUDE_PATH . "/logic/common_functions.php"); ?>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>UserAccounts - Home</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/navbar.php") ?>
    <?php include(INCLUDE_PATH . "/layouts/messages.php") ?>
    <h1>Home page</h1>
    <?php include(INCLUDE_PATH . "/layouts/footer.php") ?>

Åbn nu din browser, gå til http://localhost/user-accounts/signup.php, udfyld formularen med nogle testoplysninger (og husk dem, da vi senere bruger brugeren til at logge ind), og klik derefter på tilmeldingsknappen. Hvis alt gik vel, vil brugeren blive gemt i databasen, og vores applikation vil omdirigere til hjemmesiden.

På hjemmesiden vil du se en fejl, som opstår, fordi vi inkluderer messages.php fil, som vi endnu ikke har oprettet. Lad os skabe det med det samme.

I mappen include/layouts skal du oprette en fil med navnet messages.php:

messages.php: 

<?php if (isset($_SESSION['success_msg'])): ?>
  <div class="alert <?php echo 'alert-success'; ?> alert-dismissible" role="alert">
    <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
    <?php
      echo $_SESSION['success_msg'];
      unset($_SESSION['success_msg']);
    ?>
  </div>
<?php endif; ?>

<?php if (isset($_SESSION['error_msg'])): ?>
  <div class="alert alert-danger alert-dismissible" role="alert">
    <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
    <?php
      echo $_SESSION['error_msg'];
      unset($_SESSION['error_msg']);
    ?>
  </div>
<?php endif; ?>

Opdater nu hjemmesiden, og fejlen er væk.

Og det er det for denne del. I den næste del vil vi fortsætte med at validere tilmeldingsformularen, bruger login/log ud og begynde arbejdet med admin sektionen. Dette lyder som for meget arbejde, men tro mig, det er ligetil, især da vi allerede har skrevet noget kode, der letter vores arbejde med Admin-sektionen.

Tak for at følge. Håber du kommer med. Hvis du har nogle tanker, så smid dem i kommentarerne nedenfor. Hvis du stødte på fejl eller ikke forstod noget, så lad mig det vide i kommentarfeltet, så jeg kan prøve at hjælpe dig.

Vi ses i næste del.


  1. PostgreSQL multi INSERT...RETURNERER med flere kolonner

  2. Tilføjelse af fremmednøgle til en skinnemodel

  3. Sådan får du en liste over aktiverede/deaktiverede tjekbegrænsninger i SQL Server-databasen - SQL Server / TSQL-vejledning, del 86

  4. Få rekordtællinger for alle tabeller i MySQL-databasen