<?php

class User {
   private $id = 0;
   private $username = "";
   private $password = "";
   private $firstname = "";
   private $lastname = "";
   // createtime will be stored in the class using the native SQL datetime format
   private $createtime = "";
   // lastlogin will be stored in the class using the native SQL datetime format
   private $lastlogin = "";
   // lastbadlogin will be stored in the class using the native SQL datetime format
   private $lastbadlogin = "";
   private $badlogincount = 0;
   // lastupdate will be stored in the class using the native SQL datetime format
   private $lastupdate = "";

   const COOKIENAME = SESSNAME . "_rememberme";
   const ROLE_ADMIN = "admin";
   const ROLE_USER = "user";
   const ROLE_GUEST = "guest";
   const ROLE_VALIDROLES = array(User::ROLE_GUEST, User::ROLE_ADMIN, User::ROLE_USER);
   const GUEST         = 1000601;
   const USER          = 1000602;
   const ADMIN         = 1000603;
   const LOGININVALID  = 1000604;
   const LOGINLOCKED   = 1000605;

   public function getID() {
      return intval($this->id);
   }

   public function getUsername($flag = 0) {
      switch ($flag) {
         case HTMLSAFE:
            return htmlspecialchars($this->lastname);
            break;
         case HTMLFORMSAFE:
            return htmlspecialchars($this->lastname, ENT_QUOTES);
            break;
         case CSVSAFE:
            return str_replace('"', '""', $this->lastname);
            break;
         default:
            return $this->username;
            break;
      }
   }

   public function getFirstName($flag = 0) {
      switch ($flag) {
         case HTMLSAFE:
            return htmlspecialchars($this->firstname);
            break;
         case HTMLFORMSAFE:
            return htmlspecialchars($this->firstname, ENT_QUOTES);
            break;
         case CSVSAFE:
            return str_replace('"', '""', $this->firstname);
            break;
         default:
            return $this->firstname;
            break;
      }
   }

   public function getLastName($flag = 0) {
      switch ($flag) {
         case HTMLSAFE:
            return htmlspecialchars($this->lastname);
            break;
         case HTMLFORMSAFE:
            return htmlspecialchars($this->lastname, ENT_QUOTES);
            break;
         case CSVSAFE:
            return str_replace('"', '""', $this->lastname);
            break;
         default:
            return $this->lastname;
            break;
      }
   }

   public function getFullName($flag = 0) {
      $fullname = $this->firstname . " " . $this->lastname;
      switch ($flag) {
         case HTMLSAFE:
            return htmlspecialchars($fullname);
            break;
         case HTMLFORMSAFE:
            return htmlspecialchars($fullname, ENT_QUOTES);
            break;
         case CSVSAFE:
            return str_replace('"', '""', $fullname);
            break;
         default:
            return $fullname;
            break;
      }
   }

   public function getCreateTime($flag = 0) {
      switch ($flag) {
         case TIMESTAMP:
            return strtotime($this->createtime);
            break;
         case PRETTY:
            return date("F j Y H:i:s", strtotime($this->createtime));
            break;
         default:
            return $this->createtime;
            break;
      }
   }

   public function getLastLogin($flag = 0) {
      switch ($flag) {
         case TIMESTAMP:
            return strtotime($this->lastlogin);
            break;
         case PRETTY:
            return (($this->lastlogin == "0000-00-00 00:00:00") || ($this->lastlogin == "")) ? "Never" : date("F j Y H:i:s", strtotime($this->lastlogin));
            break;
         default:
            return $this->lastlogin;
            break;
      }
   }

   public function getLastBadLogin($flag = 0) {
      switch ($flag) {
         case TIMESTAMP:
            return strtotime($this->lastbadlogin);
            break;
         case PRETTY:
            return (($this->lastbadlogin == "0000-00-00 00:00:00") || ($this->lastbadlogin == "")) ? "Never" : date("F j Y H:i:s", strtotime($this->lastbadlogin));
            break;
         default:
            return $this->lastbadlogin;
            break;
      }
   }

   public function getBadLoginCount() {
      return intval($this->badlogincount);
   }

   public function getLastUpdate($flag = 0) {
      switch ($flag) {
         case TIMESTAMP:
            return strtotime($this->lastupdate);
            break;
         case PRETTY:
            return date("F j Y H:i:s", strtotime($this->lastupdate));
            break;
         default:
            return $this->lastupdate;
            break;
      }
   }

   public function setID($id = null) {
      if (is_null($id)) return false;
      $id = abs(intval($id));
      if ($id == 0) return false;
      $this->id = $id;
      return true;
   }

   public function setUsername($username = null) {
      if (is_null($username) || ($username == "")) return false;
      settype($username, "string");
      $this->username = $username;
      return true;
   }

   public function setPassword($password = null) {
      if (is_null($password)) return false;
      $this->password = password_hash($password, PASSWORD_DEFAULT);
      return true;
   }

   public function setPasswordHash($hash = null) {
      if (is_null($hash)) return false;
      $this->password = $hash;
      return true;
   }

   public function setFirstName($firstname = null) {
      if (is_null($firstname) || ($firstname == "")) return false;
      settype($firstname, "string");
      $this->firstname = $firstname;
      return true;
   }

   public function setLastName($lastname = null) {
      if (is_null($lastname) || ($lastname == "")) return false;
      settype($lastname, "string");
      $this->lastname = $lastname;
      return true;
   }

   public function setBadLoginCount($count = null) {
      if (is_null($count)) return false;
      $this->badlogincount = intval($count);
   }

   public function saveLastLogin() {
      global $globaldbh;
      $query = "UPDATE " . AppDB::TABLE_USERS . " SET lastlogin=:lastlogin WHERE id=:id";
      $fields = array();
      $fields[':id'] = $this->getID();
      $fields[':lastlogin'] = (new DateTime("now", new DateTimeZone("UTC")))->format('Y-m-d H:i:s');
      $sth = $globaldbh->prepare($query);
      $sth->execute($fields);
   }

   public static function getUserByUsername($username = null) {
      global $globaldbh;
      if (is_null($username)) return false;
      $query = "SELECT id FROM " . AppDB::TABLE_USERS . " WHERE username=:username";
      $fields = array();
      $fields[':username'] = $username;
      $sth = $globaldbh->prepare($query);
      $sth->execute($fields);
      if ($row = $sth->fetch()) {
         return new User($row['id']);
      } else {
         return false;
      }
   }

   public function setCookie() {
      global $globaldbh;
      $query = "DELETE FROM " . AppDB::TABLE_COOKIES . " WHERE user_id=:user_id AND ipaddress=:ipaddress";
      $fields = array();
      $fields[':user_id'] = $this->getID();
      $fields[':ipaddress'] = $_SERVER['REMOTE_ADDR'];
      $sth = $globaldbh->prepare($query);
      $sth->execute($fields);
      $hash = uniqid("", true) . uniqid("", true);
      $query = "INSERT INTO " . AppDB::TABLE_COOKIES . " ";
      if (DBTYPE == 'mysql') {
         $query .= "VALUES(:hash, :user_id, :ipaddress, UTC_TIMESTAMP() + INTERVAL 30 DAY)";
      } elseif (DBTYPE == 'sqlite') {
         $query .= "VALUES(:hash, :user_id, :ipaddress, DATETIME('NOW','+30 DAY'))";
      }
      $fields[':hash'] = $hash;
      $sth = $globaldbh->prepare($query);
      $sth->execute($fields);
      setcookie(User::COOKIENAME, $hash, array('expires' => time() + (60 * 60 * 24 * 30), 'path' => "/", 'domain' => $_SERVER['SERVER_NAME'], 'samesite' => 'Lax'));
   }

   public function saveLastUpdate() {
      global $globaldbh;
      $query = "UPDATE " . AppDB::TABLE_USERS . " ";
      if (DBTYPE == 'mysql') {
         $query .= "SET lastupdate=UTC_TIMESTAMP() WHERE id=:id";
      } elseif (DBTYPE == 'sqlite') {
         $query .= "SET lastupdate=DATETIME('NOW') WHERE id=:id";
      }
      $fields = array(':id' => $this->getID());
      $sth = $globaldbh->prepare($query);
      $sth->execute($fields);
   }

   public function incrementBadLogins() {
      global $globaldbh;

      $this->badlogincount++;
      $query = "UPDATE " . AppDB::TABLE_USERS . " ";
      if (DBTYPE == 'mysql') {
         $query .= "SET badlogincount=:badlogincount, lastbadlogin=UTC_TIMESTAMP() WHERE id=:id";
      } elseif (DBTYPE == 'sqlite') {
         $query .= "SET badlogincount=:badlogincount, lastbadlogin=DATETIME('NOW') WHERE id=:id";
      }
      $fields = array();
      $fields[':id'] = $this->getID();
      $fields[':badlogincount'] = $this->getBadLoginCount();
      $sth = $globaldbh->prepare($query);
      $sth->execute($fields);
   }

   public static function getUserFromLogin($username = null, $password = null) {
      global $globaldbh;

      $user = User::getUserByUsername($username);
      if ($user === false) {
         return User::LOGININVALID;
      }
      if (($user->getBadLoginCount() >= MAXBADLOGINS) && ((strtotime($user->getLastBadLogin()) + (BADLOGINEXPIRATION * 60)) > time())) {
         return User::LOGINLOCKED;
      }

      $query = "SELECT id, password FROM " . AppDB::TABLE_USERS . " WHERE username=:username";
      $fields = array(':username' => $username);
      $sth = $globaldbh->prepare($query);
      $sth->execute($fields);
      if ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
         if (password_verify($password, $row['password'])) {
            $user = new User($row['id']);
            $user->setBadLoginCount(0);
            $saved = $user->save();
            return $user;
         }
      }
      $user->incrementBadLogins();
      return User::LOGININVALID;
   }

   public static function validateUserCookie($hash = null) {
      global $globaldbh;
      $query = "SELECT user_id FROM " . AppDB::TABLE_COOKIES . " WHERE hash=:hash AND ipaddress=:ipaddress ";
      if (DBTYPE == 'mysql') {
         $query .= "AND expiration >= UTC_TIMESTAMP()";
      } elseif (DBTYPE == 'sqlite') {
         $query .= "AND DATETIME(expiration) >= DATETIME('NOW')";
      }
      $fields = array();
      $fields[':hash'] = $hash;
      $fields[':ipaddress'] = $_SERVER['REMOTE_ADDR'];
      $sth = $globaldbh->prepare($query);
      $sth->execute($fields);
      if ($row = $sth->fetch()) {
         $user = new User($row['user_id']);
         return $user->getID();
      } else {
         return 0;
      }
   }

   public function removeCookie() {
      global $globaldbh;
      if (!isset($_COOKIE[User::COOKIENAME])) return;
      setcookie(User::COOKIENAME, "", time() - 3600, "/", $_SERVER['SERVER_NAME']);
      $query = "DELETE FROM " . AppDB::TABLE_COOKIES . " WHERE user_id=:user_id AND ipaddress=:ipaddress";
      $fields = array();
      $fields[':user_id'] = $this->getID();
      $fields[':ipaddress'] = $_SERVER['REMOTE_ADDR'];
      $sth = $globaldbh->prepare($query);
      $sth->execute($fields);
   }

   public static function getList($search = null) {
      global $globaldbh;
      $fields = array();
      if (is_null($search)) {
         $query = "SELECT id FROM " . AppDB::TABLE_USERS . " ORDER BY firstname, lastname";
      } else {
         $query = "SELECT id FROM " . AppDB::TABLE_USERS . " WHERE (firstname LIKE :search) OR (lastname LIKE :search) OR (username LIKE :search) ORDER BY firstname, lastname";
         $fields[':search'] = "%" . $search . "%";
      }
      $sth = $globaldbh->prepare($query);
      $thelist = array();
      if ($sth->execute($fields)) {
         while ($row = $sth->fetch()) {
            $thelist[] = new User($row['id']);
         }
      }
      return $thelist;
   }

   public function isLoggedIn() {
      if ( $this->getID() != 0 ) { return true; } else { return false; }
   }

   public function save() {
      global $globaldbh;

      if ($this->getFirstName() == "") return false;
      if ($this->getLastName() == "") return false;

      $fields = array();
      if ($this->getID() == 0) {
         $query = "INSERT INTO " . AppDB::TABLE_USERS . " ";
         $query .= "(username, password, firstname, lastname, createtime, lastupdate) ";
         $query .= "VALUES(:username, :password, :firstname, :lastname, :createtime, :lastupdate)";
         $fields[':password'] = $this->password; // There is no "getter" for password since it should never read outside the class
         $fields[':createtime'] = (new DateTime("now", new DateTimeZone("UTC")))->format('Y-m-d H:i:s');
      } else {
         $query = "UPDATE " . AppDB::TABLE_USERS . " SET username=:username, ";
         if ($this->password != "") {
            $query .= "password=:password, ";
            $fields[':password'] = $this->password; // There is no "getter" for password since it should never read outside the class
         }
         $query .= "firstname=:firstname, lastname=:lastname, ";
         $query .= "lastupdate=:lastupdate, badlogincount=:badlogincount WHERE id=:id";
         $fields[':id'] = $this->getID();
         $fields[':badlogincount'] = $this->getBadLoginCount();
      }
      $fields[':username'] = $this->getUsername();
      $fields[':firstname'] = $this->getFirstName();
      $fields[':lastname'] = $this->getLastName();
      $fields[':lastupdate'] = (new DateTime("now", new DateTimeZone("UTC")))->format('Y-m-d H:i:s');
      $sth = $globaldbh->prepare($query);
      $saved = $sth->execute($fields);
      return $saved;
   }

   function __construct($reqid = 0) {
      global $globaldbh;
      $reqid = intval($reqid);
      $query = "SELECT id, username, firstname, lastname, createtime, lastlogin, " .
         "lastbadlogin, badlogincount, lastupdate FROM " . AppDB::TABLE_USERS . " WHERE id=:id";
      $fields = array();
      $fields[':id'] = $reqid;
      $sth = $globaldbh->prepare($query);
      if ($sth->execute($fields)) {
         if ($row = $sth->fetch()) {
            $this->setID($row['id']);
            $this->setUsername($row['username']);
            $this->setFirstName($row['firstname']);
            $this->setLastName($row['lastname']);
            $this->createtime = $row['createtime'];
            $this->lastlogin = $row['lastlogin'];
            $this->lastbadlogin = $row['lastbadlogin'];
            $this->setBadLoginCount($row['badlogincount']);
            $this->lastupdate = $row['lastupdate'];
         }
      }
   }
}

// vim: set ts=3:sw=3
