Guest User

user.class.php

a guest
Mar 21st, 2016
120
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 18.17 KB | None | 0 0
  1. <?php
  2.  
  3. /**
  4.  * User class
  5.  */
  6.  
  7. class User
  8. {
  9.  
  10.   public $errors;
  11.  
  12.  
  13.   /**
  14.    * Magic getter - read data from a property that isn't set yet
  15.    *
  16.    * @param string $name  Property name
  17.    * @return mixed
  18.    */
  19.    public function __get($name)
  20.    {
  21.    }
  22.  
  23.  
  24.   /**
  25.    * Get a page of user records and the previous and next page (if there are any)
  26.    *
  27.    * @param string $page  Page number
  28.    * @return array        Previous page, next page and user data. Page elements are null if there isn't a previous or next page.
  29.    */
  30.   public static function paginate($page)
  31.   {
  32.     $data = [];
  33.     $users_per_page = 5;
  34.  
  35.     // Calculate the total number of pages
  36.     $total_users = static::_getTotalUsers();
  37.     $total_pages = (int) ceil($total_users / $users_per_page);
  38.      
  39.  
  40.     // Make sure the current page is valid
  41.     $page = (int) $page;
  42.  
  43.     if ($page < 1) {
  44.       $page = 1;
  45.     } elseif ($page > $total_pages) {
  46.       $page = $total_pages;
  47.     }
  48.  
  49.  
  50.     // Calculate the next and previous pages
  51.     $data['previous'] = $page == 1 ? null : $page - 1;
  52.     $data['next'] = $page == $total_pages ? null : $page + 1;
  53.  
  54.  
  55.     // Get the page of users
  56.     try {
  57.  
  58.       $db = Database::getInstance();
  59.  
  60.       $offset = ($page - 1) * $users_per_page;
  61.  
  62.       $data['users'] = $db->query("SELECT * FROM users ORDER BY email LIMIT $users_per_page OFFSET $offset")->fetchAll();
  63.  
  64.     } catch(PDOException $exception) {
  65.  
  66.       error_log($exception->getMessage());
  67.  
  68.       $data['users'] = [];
  69.     }
  70.  
  71.     return $data;
  72.   }
  73.  
  74.  
  75.   /**
  76.    * Authenticate a user by email and password
  77.    *
  78.    * @param string $email     Email address
  79.    * @param string $password  Password
  80.    * @return mixed            User object if authenticated correctly, null otherwise
  81.    */
  82.   public static function authenticate($email, $password)
  83.   {
  84.     $user = static::findByEmail($email);
  85.  
  86.     if ($user !== null) {
  87.  
  88.       // Check the user has been activated
  89.       if ($user->is_active) {
  90.  
  91.         // Check the hashed password stored in the user record matches the supplied password
  92.         if (Hash::check($password, $user->password)) {
  93.           return $user;
  94.         }
  95.       }
  96.     }
  97.   }
  98.  
  99.  
  100.   /**
  101.    * Find the user with the specified ID
  102.    *
  103.    * @param string $id  ID
  104.    * @return mixed      User object if found, null otherwise
  105.    */
  106.   public static function findByID($id)
  107.   {
  108.     try {
  109.  
  110.       $db = Database::getInstance();
  111.  
  112.       $stmt = $db->prepare('SELECT * FROM users WHERE id = :id LIMIT 1');
  113.       $stmt->execute([':id' => $id]);
  114.       $user = $stmt->fetchObject('User');
  115.  
  116.       if ($user !== false) {
  117.         return $user;
  118.       }
  119.  
  120.     } catch(PDOException $exception) {
  121.  
  122.       error_log($exception->getMessage());
  123.     }
  124.   }
  125.  
  126.  
  127.   /**
  128.    * Get the user by ID or display a 404 Not Found page if not found.
  129.    *
  130.    * @param array $data  $_GET data
  131.    * @return mixed       User object if found, null otherwise
  132.    */
  133.   public static function getByIDor404($data)
  134.   {
  135.     if (isset($data['id'])) {
  136.       $user = static::findByID($data['id']);
  137.  
  138.       if ($user !== null) {
  139.         return $user;
  140.       }
  141.     }
  142.  
  143.     Util::showNotFound();
  144.   }
  145.  
  146.  
  147.   /**
  148.    * Find the user with the specified email address
  149.    *
  150.    * @param string $email  email address
  151.    * @return mixed         User object if found, null otherwise
  152.    */
  153.   public static function findByEmail($email)
  154.   {
  155.     try {
  156.  
  157.       $db = Database::getInstance();
  158.  
  159.       $stmt = $db->prepare('SELECT * FROM users WHERE email = :email LIMIT 1');
  160.       $stmt->execute([':email' => $email]);
  161.       $user = $stmt->fetchObject('User');
  162.  
  163.       if ($user !== false) {
  164.         return $user;
  165.       }
  166.  
  167.     } catch(PDOException $exception) {
  168.  
  169.       error_log($exception->getMessage());
  170.     }
  171.   }
  172.  
  173.  
  174.   /**
  175.    * Signup a new user
  176.    *
  177.    * @param array $data  POST data
  178.    * @return User
  179.    */
  180.   public static function signup($data)
  181.   {
  182.     // Create a new user model and set the attributes
  183.     $user = new static();
  184.  
  185.     $user->name = $data['name'];
  186.     $user->surname = $data['surname'];
  187.     $user->email = $data['email'];
  188.     $user->password = $data['password'];
  189.  
  190.     if ($user->isValid()) {
  191.  
  192.       // Generate a random token for activation and base64 encode it so it's URL safe
  193.       $token = base64_encode(uniqid(rand(), true));
  194.       $hashed_token = sha1($token);
  195.  
  196.       try {
  197.  
  198.         $db = Database::getInstance();
  199.  
  200.         $stmt = $db->prepare('INSERT INTO users (name, surname, email, password, activation_token) VALUES (:name, :surname, :email, :password, :token)');
  201.         $stmt->bindParam(':name', $user->name);
  202.         $stmt->bindParam(':surname', $user->surname);
  203.         $stmt->bindParam(':email', $user->email);
  204.         $stmt->bindParam(':password', Hash::make($user->password));
  205.         $stmt->bindParam(':token', $hashed_token);
  206.          
  207.  
  208.         $stmt->execute();
  209.  
  210.         // Send activation email
  211.         $user->_sendActivationEmail($token);
  212.  
  213.       } catch(PDOException $exception) {
  214.  
  215.         // Log the exception message
  216.         error_log($exception->getMessage());
  217.       }
  218.     }
  219.  
  220.     return $user;
  221.   }
  222.  
  223.  
  224.   /**
  225.    * Find the user by remember token
  226.    *
  227.    * @param string $token  token
  228.    * @return mixed         User object if found, null otherwise
  229.    */
  230.   public static function findByRememberToken($token)
  231.   {
  232.     try {
  233.  
  234.       $db = Database::getInstance();
  235.  
  236.       $stmt = $db->prepare('SELECT u.* FROM users u JOIN remembered_logins r ON u.id = r.user_id WHERE token = :token');
  237.       $stmt->execute([':token' => $token]);
  238.       $user = $stmt->fetchObject('User');
  239.  
  240.       if ($user !== false) {
  241.         return $user;
  242.       }
  243.  
  244.     } catch(PDOException $exception) {
  245.  
  246.       error_log($exception->getMessage());
  247.     }
  248.   }
  249.  
  250.  
  251.   /**
  252.    * Deleted expired remember me tokens
  253.    *
  254.    * @return integer  Number of tokens deleted
  255.    */
  256.   public static function deleteExpiredTokens()
  257.   {
  258.     try {
  259.  
  260.       $db = Database::getInstance();
  261.  
  262.       $stmt = $db->prepare("DELETE FROM remembered_logins WHERE expires_at < '" . date('Y-m-d H:i:s') . "'");
  263.       $stmt->execute();
  264.  
  265.       return $stmt->rowCount();
  266.  
  267.     } catch(PDOException $exception) {
  268.  
  269.       // Log the detailed exception
  270.       error_log($exception->getMessage());
  271.     }
  272.  
  273.     return 0;
  274.   }
  275.  
  276.  
  277.   /**
  278.    * Find the user for password reset, by the specified token and check the token hasn't expired
  279.    *
  280.    * @param string $token  Reset token
  281.    * @return mixed         User object if found and the token hasn't expired, null otherwise
  282.    */
  283.   public static function findForPasswordReset($token)
  284.   {
  285.     $hashed_token = sha1($token);
  286.  
  287.     try {
  288.  
  289.       $db = Database::getInstance();
  290.  
  291.       $stmt = $db->prepare('SELECT * FROM users WHERE password_reset_token = :token LIMIT 1');
  292.       $stmt->execute([':token' => $hashed_token]);
  293.       $user = $stmt->fetchObject('User');
  294.  
  295.       if ($user !== false) {
  296.  
  297.         // Check the token hasn't expired
  298.         $expiry = DateTime::createFromFormat('Y-m-d H:i:s', $user->password_reset_expires_at);
  299.  
  300.         if ($expiry !== false) {
  301.           if ($expiry->getTimestamp() > time()) {
  302.             return $user;
  303.           }
  304.         }
  305.       }
  306.  
  307.     } catch(PDOException $exception) {
  308.  
  309.       error_log($exception->getMessage());
  310.     }
  311.   }
  312.  
  313.  
  314.   /**
  315.    * Activate the user account, nullifying the activation token and setting the is_active flag
  316.    *
  317.    * @param string $token  Activation token
  318.    * @return void
  319.    */
  320.   public static function activateAccount($token)
  321.   {
  322.     $hashed_token = sha1($token);
  323.  
  324.     try {
  325.  
  326.       $db = Database::getInstance();
  327.  
  328.       $stmt = $db->prepare('UPDATE users SET activation_token = NULL, is_active = TRUE WHERE activation_token = :token');
  329.       $stmt->execute([':token' => $hashed_token]);
  330.  
  331.     } catch(PDOException $exception) {
  332.  
  333.       // Log the detailed exception
  334.       error_log($exception->getMessage());
  335.     }
  336.   }
  337.  
  338.  
  339.   /**
  340.    * Delete the user.
  341.    *
  342.    * @return void
  343.    */
  344.   public function delete()
  345.   {
  346.     try {
  347.  
  348.       $db = Database::getInstance();
  349.  
  350.       $stmt = $db->prepare('DELETE FROM users WHERE id = :id');
  351.       $stmt->bindParam(':id', $this->id, PDO::PARAM_INT);
  352.       $stmt->execute();
  353.  
  354.     } catch(PDOException $exception) {
  355.  
  356.       // Log the detailed exception
  357.       error_log($exception->getMessage());
  358.     }
  359.   }
  360.    
  361.     /**
  362.    * Delete the user.
  363.    *
  364.    * @return void
  365.    */
  366.   public function selectschools()
  367.   {
  368.     try {
  369.  
  370.       $db = Database::getInstance();
  371.  
  372.       $sql= "SELECT id, name FROM schools";
  373.  
  374.         $stmt = $pdo->query($sql);
  375.  
  376.         $schools = $stmt->fetchAll(PDO::FETCH_ASSOC);
  377.  
  378.     } catch(PDOException $exception) {
  379.  
  380.       // Log the detailed exception
  381.       error_log($exception->getMessage());
  382.     }
  383.   }
  384.  
  385.  
  386.   /**
  387.    * Update or insert the user's details based on the data. Data is validated and $this->errors is set if
  388.    * if any values are invalid.
  389.    *
  390.    * @param array $data  Data ($_POST array)
  391.    * @return boolean     True if the values were updated / inserted successfully, false otherwise.
  392.    */
  393.   public function save($data)
  394.   {
  395.     $this->name = $data['name'];
  396.       $this->surname = $data['surname'];
  397.     $this->email = $data['email'];
  398.  
  399.     // If editing a user, only validate and update the password if a value provided
  400.     if (isset($this->id) && empty($data['password'])) {
  401.       unset($this->password);
  402.     } else {
  403.       $this->password = $data['password'];
  404.     }
  405.  
  406.     // Convert values of the checkboxes to boolean
  407.     $this->is_active = isset($data['is_active']) && ($data['is_active'] == '1');
  408.     $this->is_admin = isset($data['is_admin']) && ($data['is_admin'] == '1');
  409.  
  410.     if ($this->isValid()) {
  411.  
  412.       try {
  413.  
  414.         $db = Database::getInstance();
  415.  
  416.         // Prepare the SQL: Update the existing record if editing, or insert new if adding
  417.         if (isset($this->id)) {
  418.  
  419.           $sql = 'UPDATE users SET name = :name, surname = :surname,  email = :email, is_active = :is_active, is_admin = :is_admin';
  420.  
  421.           if (isset($this->password)) {  // only update password if set
  422.             $sql .= ', password = :password';
  423.           }
  424.  
  425.           $sql .= ' WHERE id = :id';
  426.  
  427.         } else {
  428.  
  429.           $sql = 'INSERT INTO users (name, surname, email, password, is_active, is_admin) VALUES (:name, :surname, :email, :password, :is_active, :is_admin)';
  430.         }
  431.  
  432.         // Bind the parameters
  433.         $stmt = $db->prepare($sql);
  434.         $stmt->bindParam(':name', $this->name);
  435.         $stmt->bindParam(':surname', $this->surname);
  436.         $stmt->bindParam(':email', $this->email);
  437.         $stmt->bindParam(':is_active', $this->is_active);
  438.         $stmt->bindParam(':is_admin', $this->is_admin);
  439.  
  440.         if (isset($this->id)) {
  441.           if (isset($this->password)) {  // only update password if set
  442.             $stmt->bindParam(':password', Hash::make($this->password));
  443.           }
  444.  
  445.           $stmt->bindParam(':id', $this->id, PDO::PARAM_INT);
  446.  
  447.         } else {
  448.           $stmt->bindParam(':password', Hash::make($this->password));
  449.         }
  450.  
  451.         $stmt->execute();
  452.  
  453.         // Set the ID if a new record
  454.         if ( ! isset($this->id)) {
  455.           $this->id = $db->lastInsertId();
  456.         }
  457.  
  458.         return true;
  459.  
  460.       } catch(PDOException $exception) {
  461.  
  462.         // Set generic error message and log the detailed exception
  463.         $this->errors = ['error' => 'A database error occurred.'];
  464.         error_log($exception->getMessage());
  465.       }
  466.     }
  467.  
  468.     return false;
  469.   }
  470.  
  471.  
  472.   /**
  473.    * Remember the login by storing a unique token associated with the user ID
  474.    *
  475.    * @param integer $expiry  Expiry timestamp
  476.    * @return mixed           The token if remembered successfully, false otherwise
  477.    */
  478.   public function rememberLogin($expiry)
  479.   {
  480.    
  481.     // Generate a unique token
  482.     $token = uniqid($this->email, true);
  483.  
  484.     try {
  485.  
  486.       $db = Database::getInstance();
  487.  
  488.       $stmt = $db->prepare('INSERT INTO remembered_logins (token, user_id, expires_at) VALUES (:token, :user_id, :expires_at)');
  489.       $stmt->bindParam(':token', sha1($token));  // store a hash of the token
  490.       $stmt->bindParam(':user_id', $this->id, PDO::PARAM_INT);
  491.       $stmt->bindParam(':expires_at', date('Y-m-d H:i:s', $expiry));
  492.       $stmt->execute();
  493.  
  494.       if ($stmt->rowCount() == 1) {
  495.         return $token;
  496.       }
  497.  
  498.     } catch(PDOException $exception) {
  499.  
  500.       // Log the detailed exception
  501.       error_log($exception->getMessage());
  502.     }
  503.  
  504.     return false;
  505.   }
  506.  
  507.  
  508.   /**
  509.    * Forget the login based on the token value
  510.    *
  511.    * @param string $token  Remember token
  512.    * @return void
  513.    */
  514.   public function forgetLogin($token)
  515.   {
  516.     if ($token !== null) {
  517.  
  518.       try {
  519.  
  520.         $db = Database::getInstance();
  521.  
  522.         $stmt = $db->prepare('DELETE FROM remembered_logins WHERE token = :token');
  523.         $stmt->bindParam(':token', $token);
  524.         $stmt->execute();
  525.  
  526.       } catch(PDOException $exception) {
  527.  
  528.         // Log the detailed exception
  529.         error_log($exception->getMessage());
  530.       }
  531.     }
  532.   }
  533.  
  534.  
  535.   /**
  536.    * Start the password reset process by generating a unique token and expiry and saving them in the user model
  537.    *
  538.    * @return boolean  True if the user model was updated successfully, false otherwise
  539.    */
  540.   public function startPasswordReset()
  541.   {
  542.     // Generate a random token and base64 encode it so it's URL safe
  543.     $token = base64_encode(uniqid(rand(), true));
  544.     $hashed_token = sha1($token);
  545.  
  546.     // Set the token to expire in one hour
  547.     $expires_at = date('Y-m-d H:i:s', time() + 60 * 60);
  548.    
  549.     try {
  550.  
  551.       $db = Database::getInstance();
  552.  
  553.       $stmt = $db->prepare('UPDATE users SET password_reset_token = :token, password_reset_expires_at = :expires_at WHERE id = :id');
  554.       $stmt->bindParam(':token', $hashed_token);
  555.       $stmt->bindParam(':expires_at', $expires_at);
  556.       $stmt->bindParam(':id', $this->id, PDO::PARAM_INT);
  557.       $stmt->execute();
  558.  
  559.       if ($stmt->rowCount() == 1) {
  560.         $this->password_reset_token = $token;
  561.         $this->password_reset_expires_at = $expires_at;
  562.  
  563.         return true;
  564.       }
  565.  
  566.     } catch(PDOException $exception) {
  567.  
  568.       // Log the detailed exception
  569.       error_log($exception->getMessage());
  570.     }
  571.  
  572.     return false;
  573.   }
  574.  
  575.  
  576.   /**
  577.    * Reset the password
  578.    *
  579.    * @return boolean  true if the password was changed successfully, false otherwise
  580.    */
  581.   public function resetPassword()
  582.   {
  583.     $password_error = $this->_validatePassword();
  584.  
  585.     if ($password_error === null) {
  586.  
  587.       try {
  588.  
  589.         $db = Database::getInstance();
  590.  
  591.         $stmt = $db->prepare('UPDATE users SET password = :password, password_reset_token = NULL, password_reset_expires_at = NULL WHERE id = :id');
  592.         $stmt->bindParam(':password', Hash::make($this->password));
  593.         $stmt->bindParam(':id', $this->id, PDO::PARAM_INT);
  594.         $stmt->execute();
  595.  
  596.         if ($stmt->rowCount() == 1) {
  597.           return true;
  598.         }
  599.  
  600.       } catch(PDOException $exception) {
  601.  
  602.         // Set generic error message and log the detailed exception
  603.         $this->errors = ['error' => 'A database error occurred.'];
  604.         error_log($exception->getMessage());
  605.       }
  606.      
  607.     } else {
  608.       $this->errors['password'] = $password_error;
  609.     }
  610.  
  611.     return false;
  612.   }
  613.  
  614.  
  615.   /**
  616.    * Validate the properties and set $this->errors if any are invalid
  617.    *
  618.    * @return boolean  true if valid, false otherwise
  619.    */
  620.   public function isValid()
  621.   {
  622.     $this->errors = [];
  623.  
  624.     //
  625.     // name
  626.     //
  627.     if ($this->name == '') {
  628.       $this->errors['name'] = 'Please enter a valid name';
  629.     }
  630.  
  631.     //
  632.     // email address
  633.     //
  634.     if (filter_var($this->email, FILTER_VALIDATE_EMAIL) === false) {
  635.       $this->errors['email'] = 'Please enter a valid email address';
  636.     }
  637.  
  638.     if ($this->_emailTaken($this->email)) {
  639.       $this->errors['email'] = 'That email address is already taken';
  640.     }
  641.  
  642.     //
  643.     // password
  644.     //
  645.     $password_error = $this->_validatePassword();
  646.     if ($password_error !== null) {
  647.       $this->errors['password'] = $password_error;
  648.     }
  649.  
  650.     return empty($this->errors);
  651.   }
  652.  
  653.  
  654.   /**
  655.    * Get the total number of users
  656.    *
  657.    * @return integer
  658.    */
  659.   private static function _getTotalUsers()
  660.   {
  661.     try {
  662.  
  663.       $db = Database::getInstance();
  664.       $count = (int) $db->query('SELECT COUNT(*) FROM users')->fetchColumn();
  665.  
  666.     } catch(PDOException $exception) {
  667.  
  668.       error_log($exception->getMessage());
  669.       $count = 0;
  670.     }
  671.  
  672.     return $count;
  673.   }
  674.  
  675.  
  676.   /**
  677.    * See if the email address is taken (already exists), ignoring the current user if already saved.
  678.    *
  679.    * @param string $email  Email address
  680.    * @return boolean       True if the email is taken, false otherwise
  681.    */
  682.   private function _emailTaken($email)
  683.   {
  684.     $isTaken = false;
  685.     $user = $this->findByEmail($email);
  686.  
  687.     if ($user !== null) {
  688.  
  689.       if (isset($this->id)) {  // existing user
  690.  
  691.         if ($this->id != $user->id) {  // different user
  692.           $isTaken = true;
  693.         }
  694.  
  695.       } else {  // new user
  696.         $isTaken = true;
  697.       }
  698.     }
  699.  
  700.     return $isTaken;
  701.   }
  702.  
  703.  
  704.   /**
  705.    * Validate the password
  706.    *
  707.    * @return mixed  The first error message if invalid, null otherwise
  708.    */
  709.   private function _validatePassword()
  710.   {
  711.     if (isset($this->password) && (strlen($this->password) < 5)) {
  712.       return 'Please enter a longer password';
  713.     }
  714.  
  715.     if (isset($this->password_confirmation) && ($this->password != $this->password_confirmation)) {
  716.       return 'Please enter the same password';
  717.     }
  718.   }
  719.  
  720.  
  721.   /**
  722.    * Send activation email to the user based on the token
  723.    *
  724.    * @param string $token  Activation token
  725.    * @return mixed         User object if authenticated correctly, null otherwise
  726.    */
  727.   private function _sendActivationEmail($token)
  728.   {
  729.     // Note hardcoded protocol
  730.     $url = 'http://'.$_SERVER['HTTP_HOST'].'/activate_account.php?token=' . $token;
  731.  
  732.     $body = <<<EOT
  733.  
  734. <p>Please click on the following link to activate your account.</p>
  735.  
  736. <p><a href="$url">$url</a></p>
  737.  
  738. EOT;
  739.  
  740.     Mail::send($this->name, $this->email, 'Activate account', $body);
  741.   }
  742.  
  743. }
Add Comment
Please, Sign In to add comment