logo sykohpath.com

				where code goes to die

Web Development From Scratch, Day 8

After "Create", there is "Read".  Technically we covered part of this yesterday with "get_all_users" in our model, but we need to actually use it - mainly for a login form.  It's quite simple, really.  Use SQL statements to Read from the database.

First, we'll have to actually create the login form.  Nothing too fancy, just a simple "Username" and "Password" setup.

Normally, in an OOP environment, we'd use the same controller for this, but again, since that's a bit later, we're going to create a new controller, called "login.php".  In order to keep things from getting messy, we need to rename our "users.php" to "register.php".  Why didn't we do this earler? oops.  Shoulda planned for that, right?

1) rename controller/users.php to controller/register.php

We need to slightly modify our index.php as well.  Since we access our site by typing in /index.php, and we have that require our controller, how do we make it access our other controller?  /index.php/register would be a rather nice way to do it, right?

There...sort of is...a way to do it without using an actual framework.  We're going to have "ugly" URL's as a result, but there's ways around that, although this is actually beneficial from an SEO standpoint!

1) grab the full URL that was sent: http://<yoursite.com>/index.php/register
2) Parse that: [host]<yoursite.com>, [path]/index.php/register
3) break that up: [0]=> [1]=>index.php [2]=>register
4) If [2] is set, call that "controller", else default to the "index" controller.

Code Sample:
  1. <?php
  2. require "../../includes/db_connect.php"; //modify to your needs
  3. $url = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
  4. $urlParse = parse_url($url);
  5. $urlParts = explode('/', $urlParse['path']);
  6. if(isset($urlParts[3])){
  7.   //check if file exists
  8.   $controller = "controller/" . $urlParts[3] . ".php";
  9.   if(!file_exists($controller)){
  10.    die("Controller not found: " . $controller);
  11.   }
  12. } else {
  13.   $controller = "controller/index.php";
  14. }
  15. require $controller; //pass to controller
  16. ?>

* Your initial database require should not change, if it's working from last time, don't change it to match what I have!
* We use $_SERVER superglobals to craft the full URL.  Assume we're hitting it from http (we should test for https, honestly)
* Parse_url gives us an array of the url...
* ...and we explode that array into the parts.
* I use the third position, since I'm hitting this from http://sykohpath.com/websitetest.com.git/index.php/register ([1]=>websitetest.com.git [2]=>index.php [3]=>register).  If you're at http://<yoursite.com>/index.php, you'll be using position 2 to grab the controller name ([1]=>index.php, [2]=>/<controller>)
* Need to test if that file even exists! If not, error out.

Since we set a default controller to "index.php", we want this page to be the first page that pulls up, and not directly to our register page.  So, let's whip up the following pages:

Code Sample:
  1. <?php
  2. if($urlParts[2] == "" || $urlParts[3] == ""){
  3.   //index.php was not supplied, so modify links to reflect that
  4.   $prefix = "index.php/";
  5. }
  6. require "view/index.php";
  7. ?>

Code Sample:
  1. <?php
  2. if(!isset($prefix)){ $prefix = ""; } //init
  3. ?><html>
  4. <head>
  5. <title>User Management System</title>
  6. </head>
  7. <body>
  8. <?php if($_SESSION["logged_in"]=="YES"): ?>
  9.   Welcome Back, <?php echo $_SESSION['name']; ?>!<br>
  10.   <a href="<?php echo $prefix; ?>logout">Logout</a><br>
  11. <?php else: ?>
  12.   <a href="<?php echo $prefix; ?>login">Login</a><br>
  13.   <a href="<?php echo $prefix; ?>register">Register</a><br>
  14. <?php endif; ?>
  15. <?php print_r($_SESSION); ?>
  16. </body>
  17. </html>

* Since this is a landing page which can be accessed through index.php via http://<yoursite.com>/ - we need to make sure the links are flexible to handle  http://<yoursite.com>/ as well as http://<yoursite.com>/index.php - we do this by checking our url parts that were set in our index.php for the controller checker.
* We have a check to see if the user is logged in.  If they are, show their name, and a link to logout.  If they aren't, show a link to login.
* For testing/learning purposes, a print_r($_SESSION) displays all that was set once the user is logged in. It's an unneccessary line.

After making those changes, try them out.  Go to index.php/test, as well as index.php/register - test should error, while register should pull up the registration form we made yesterday.  Try index.php/ and the new index page we made should pull up.

One last thing, change the form location in view/user_form.php to point to the new location:
<form name="user_registration" action="register" method="post">

Now to move on to the login form!  Not much to the view:

Code Sample:
  1. <html>
  2. <head>
  3. <title>User Login Form</title>
  4. </head>
  5. <body>
  6. <form name="user_login" action="login" method="post">
  7.   <table>
  8.    <tr>
  9.     <td>
  10.      Username
  11.     </td>
  12.     <td>
  13.      <input type="text" name="username">
  14.     </td>
  15.    </tr>
  16.    <tr>
  17.     <td>
  18.      Password
  19.     </td>
  20.     <td>
  21.      <input type="password" name="password">
  22.     </td>
  23.    </tr>
  24.    <tr>
  25.     <td>
  26.      Errors:
  27.     </td>
  28.     <td>
  29.      <b  echo $errors; ?></b>
  30.     </td>
  31.    </tr>
  32.    <tr>
  33.     <td>
  34.     </td>
  35.     <td>
  36.      <input type="submit" name="submit" value="Submit Form">
  37.     </td>
  38.    </tr>
  39.   </table>
  40. </form>
  41. </body>
  42. </html>

Next our controller, a little bit more complicated:

Code Sample:
  1. <?php
  2. require "model/user_model.php";
  3. //check if logged in!
  4. if($_SESSION['logged_in'] == "YES"){
  5.   require "view/index.php";
  6.   exit;
  7. }
  8. //login user
  9. if(isset($_POST['submit'])){
  10.   //setup array to match table fields=>form values
  11.   $form = array(
  12.    "username" => filter_var($_POST['username'], FILTER_SANITIZE_STRING),
  13.    "password" => filter_var($_POST['password'], FILTER_SANITIZE_STRING)
  14.    );
  15.   //secure/encrypt the password
  16.   $salt = "pepper";
  17.   $form['password'] = md5($salt + md5($form['password']));
  18.   //check if user and password matches
  19.   $errors = validate_user($websitetest_db, $form);
  20.   if(is_array($errors)){
  21.    //if so, cram that user into session
  22.    $_SESSION['logged_in'] = "YES";
  23.    $_SESSION['username'] = $errors['username'];
  24.    $_SESSION['name'] = $errors['name'];
  25.    require "view/index.php";
  26.    exit;
  27.   }
  28. }
  29. require "view/user_login.php";
  30. ?>

* This is VERY similar to the register.php controller.  We still sanitize our fields, and then encrypt the password.
* We pass our associative array (containing the username and password) to the validate_user function, which resides in the model.
* We are passed back an array if the user is validated, and we set the fields according to what was passed back - in this case, only set the flag that the user is logged_in, as well as the username and their name.
* On completion, call the index view, which contains a detection for if the user is logged in.
* If we are not passed back an array, it's an error, so simply call the login view again, which displays the error.

We add the following function to the model:

Code Sample:
  1. ...
  2. function validate_user($db_conn, $dataArray){
  3.   $sql = "SELECT * FROM users WHERE username='" . $dataArray['username'] . "'";
  4.   $result = mysqli_query($db_conn, $sql);
  5.   if(!$result){
  6.    die('SQL Error: ' . mysqli_error($db_conn));
  7.   }
  8.   if(mysqli_num_rows($result) > 0){
  9.    $row = mysqli_fetch_assoc($result);
  10.    if($row['password']==$dataArray['password']){
  11.     return $row; //pass user account back
  12.    } else {
  13.     return "Incorrect Password";
  14.    }
  15.   } else {
  16.    return "Username not found";
  17.   }
  18. }
  19. ...

* Ok, "finally" we deal with the "Read" part of CRUD.
* Using SQL, just pull all records that match the passed username. (Note that we haven't put in a duplicate username filter in our registration yet!)
* If there's more than 0 rows returned, it means that a matching record was found.  Otherwise, 0 rows returned means there were no records that match the passed username.
* Compare the pulled password with the passed password - either it matches, or it doesn't.

After all this, we'll have the following in our $_SESSION:
Array ( [logged_in] => YES [username] => testAccount [name] => Test Account )

Now, if we try to login as "test" (Bob Bobkins!) we'll see that it doesn't work...because the password it's checking against is unencrypted.  We'll deal with that one record in a later section (Delete!).

Our login system is almost complete.  We just need to "logout".  Painfully simple!

Code Sample:
  1. <?php
  2. //check if logged in!
  3. if($_SESSION['logged_in'] == "YES"){
  4.   unset($_SESSION);
  5. }
  6. require "view/index.php";
  7. ?>

GOOD NESS.  That was quite a bit for "Read", wasn't it?  Well, a login system is the...I don't want to say "backbone"...it's more like the "kidneys" of a user management system.

Next will be the user "Profile" once they are logged in.  This will cover "Update" part of CRUD.

php, tutorial, crud, update,



Auto-install Important Updates

I'm one of those people that like to have everything updated every day.  So, every day when I log-in, I run Update Manager.  Well, that's freaking annoying, why can't it run automatically?  CRON JOB TIME.

First, get this through Software Manager, or whatever you use.  The package is called:

Next, open terminal...well, open this biglong filename:
Code Sample:
  1. sudo gedit /etc/apt/apt.conf.d/02periodic

And cram all this stuff inside:
Code Sample:
  1. # Enable the update script (0 = disable)
  2. APT::Periodic::Enable "1";
  3. # Get package lists every X days - apt-get update
  4. APT::Periodic::Update-Package-Lists "1";
  5. # Get upgrades every X days - apt-get upgrade --download-only
  6. APT::Periodic::Download-Upgradeable-Packages "0";
  7. # Clean every X days - apt-get autoclean
  8. APT::Periodic::AutocleanInterval "0";
  9. # Allow unattended script to run
  10. # Requires the package “unattended-upgrades” and will write
  11. # a log in /var/log/unattended-upgrades
  12. APT::Periodic::Unattended-Upgrade "1";

Change how often you want to update...heck, just read the comments, it's pretty self-explanatory.

Next, confirm the settings for what you actually want to download.  
Code Sample:
  1. sudo gedit /etc/apt/apt.conf.d/50unattended-upgrades

My default didn't grab "stable", so I had to add it at the top:
Code Sample:
  1. // Automatically upgrade packages from these (origin, archive) pairs
  2. Unattended-Upgrade::Allowed-Origins {
  3. "${distro_id} stable";
  4. "${distro_id} ${distro_codename}-security";
  5. // "${distro_id} ${distro_codename}-updates";
  6. // "${distro_id} ${distro_codename}-proposed";
  7. // "${distro_id} ${distro_codename}-backports";
  8. };
  9. // List of packages to not update
  10. Unattended-Upgrade::Package-Blacklist {
  11. // "vim";
  12. // "libc6";
  13. // "libc6-dev";
  14. // "libc6-i686";
  15. };
  16. // This option allows you to control if on a unclean dpkg exit
  17. // unattended-upgrades will automatically run
  18. //   dpkg --force-confold --configure -a
  19. // The default is true, to ensure updates keep getting installed
  20. //Unattended-Upgrade::AutoFixInterruptedDpkg "false";
  21. // Split the upgrade into the smallest possible chunks so that
  22. // they can be interrupted with SIGUSR1. This makes the upgrade
  23. // a bit slower but it has the benefit that shutdown while a upgrade
  24. // is running is possible (with a small delay)
  25. //Unattended-Upgrades::MinimalSteps "true";
  26. // Send email to this address for problems or packages upgrades
  27. // If empty or unset then no email is sent, make sure that you
  28. // have a working mail setup on your system. The package 'mailx'
  29. // must be installed or anything that provides /usr/bin/mail.
  30. //Unattended-Upgrade::Mail "root@localhost";
  31. // Set this value to "true" to get emails only on errors. Default
  32. // is to always send a mail if Unattended-Upgrade::Mail is set
  33. //Unattended-Upgrade::MailOnlyOnError "true";
  34. // Do automatic removal of new unused dependencies after the upgrade
  35. // (equivalent to apt-get autoremove)
  36. //Unattended-Upgrade::Remove-Unused-Dependencies "false";
  37. // Automatically reboot *WITHOUT CONFIRMATION* if a
  38. // the file /var/run/reboot-required is found after the upgrade
  39. //Unattended-Upgrade::Automatic-Reboot "false";
  40. // Use apt bandwidth limit feature, this example limits the download
  41. // speed to 70kb/sec
  42. //Acquire::http::Dl-Limit "70";

Aaaaand you're all set.  So now I can simply log in and magically...not manually update every dang thing.

ubuntu, mint 12, linux, auto install, updates,