937 lines
32 KiB
PHP
937 lines
32 KiB
PHP
<?php
|
|
// Import the PHPMailer class into the global namespace
|
|
use PHPMailer\PHPMailer\PHPMailer;
|
|
|
|
class User extends Model
|
|
{
|
|
private bool $_isChanged = false;
|
|
private bool $_autoSave = true;
|
|
private string $_lastError = '';
|
|
|
|
public function __construct($id)
|
|
{
|
|
parent::__construct($id);
|
|
|
|
// Critical properties
|
|
$this->userName = '';
|
|
$this->password = '';
|
|
$this->callsign = null;
|
|
$this->email = '';
|
|
$this->emailVerified = 0;
|
|
$this->callsignVerified = 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* User class destructor
|
|
** Saves changes to the user record if there are any (and autoSave is not disabled)
|
|
*/
|
|
public function __destruct()
|
|
{
|
|
if ($this->_autoSave && $this->_isChanged) $this->save();
|
|
}
|
|
|
|
|
|
/**
|
|
* Set AutoSave
|
|
** Allows (or prevents) changes to the user record from being saved to the database at
|
|
** completion of script execution. Enabled by default.
|
|
*
|
|
* @param string $autoSave (True if changes are to be saved automatically, otherwise false)
|
|
*/
|
|
public function setAutoSave($autoSave)
|
|
{
|
|
$this->_autoSave = $autoSave;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check the username for validity, then set
|
|
** Has minimum 4 characters in length.
|
|
** Acceptable characters A-Z, a-z, 0-9, underscore(_), hyphen(-)
|
|
*
|
|
* @param string $username
|
|
* @return boolean (True if username is acceptable and was set, otherwise false)
|
|
*/
|
|
public function setUsername($username)
|
|
{
|
|
// Remove whitespace
|
|
$username = trim($username);
|
|
|
|
// Test the username and set it if passes
|
|
if (strlen($username) <= 4 || preg_match('/[^a-z_\-0-9]/i', $username) == 0)
|
|
{
|
|
// Username is good, check if it exists
|
|
$user = UserRepository::getInstance()->getObjectByUsername($username);
|
|
if (!$user->isExistingObject())
|
|
{
|
|
$this->userName = $username;
|
|
$this->_isChanged = true;
|
|
return true;
|
|
}
|
|
|
|
$this->_lastError = 'Username is already in use.';
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'Username is too short or contains invalid characters. Acceptable characters A-Z, a-z, 0-9, underscore(_), hyphen(-).';
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check the password for validity, then set and hash
|
|
** Has minimum 8 characters in length. Adjust it by modifying {8,}
|
|
** At least one uppercase English letter. You can remove this condition by removing (?=.*?[A-Z])
|
|
** At least one lowercase English letter. You can remove this condition by removing (?=.*?[a-z])
|
|
** At least one digit. You can remove this condition by removing (?=.*?[0-9])
|
|
** //At least one special character, You can remove this condition by removing (?=.*?[#?!@$%^&*-])
|
|
**
|
|
** "/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/"
|
|
*
|
|
* @param string $password (Plain text password)
|
|
* @return boolean (True if password is acceptable and was set, otherwise false)
|
|
*/
|
|
public function setPassword($password)
|
|
{
|
|
// Remove whitespace
|
|
$password = trim($password);
|
|
|
|
// Test the password and set it if passes (no )
|
|
if (preg_match("/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$/", $password) == 1)
|
|
{
|
|
// Password is good, hash it
|
|
$this->password = password_hash($password, PASSWORD_ARGON2I);
|
|
$this->_isChanged = true;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'Password does not meet length or complexity requirements.';
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check the password against the encrypted password.
|
|
*
|
|
* @param string $password (Plain text password)
|
|
* @return boolean (True if password is correct, otherwise false)
|
|
*/
|
|
public function verifyPassword($password)
|
|
{
|
|
if ($this->password && !empty($this->password))
|
|
{
|
|
// Remove whitespace
|
|
$password = trim($password);
|
|
return password_verify($password, $this->password);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check the password against the encrypted password.
|
|
*
|
|
* @return array (Cookie parameters, otherwise false)
|
|
*/
|
|
public function generatePersistentAuthToken()
|
|
{
|
|
// Generate a selector (aka database key)
|
|
$selector = generateSecureToken(8); // Returns 16 bytes
|
|
$identifier = generateSecureToken(32); // Returns 64 bytes
|
|
$identifier_hashed = hash('sha256', $identifier);
|
|
|
|
$cookie_expiration_time = time() + 7776000; // 90 days
|
|
|
|
// Insert into the database
|
|
$pdo = PDOMysqlConnection::getInstance();
|
|
|
|
$sql = 'INSERT INTO auth_tokens (user_id, selector, validator, expiration)
|
|
VALUES (?, ?, ?, FROM_UNIXTIME(?))';
|
|
$stmt = $pdo->prepareAndExec($sql, [$this->_id, $selector, $identifier_hashed, $cookie_expiration_time]);
|
|
|
|
$id = $pdo->lastInsertId();
|
|
if ($id)
|
|
{
|
|
return
|
|
[
|
|
'id' => $id,
|
|
'selector' => $selector,
|
|
'identifier'=> $identifier,
|
|
'expiration'=> $cookie_expiration_time
|
|
];
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Validates the user token
|
|
*
|
|
* @param string $selector (Plain text 16 byte selector that identities the user)
|
|
* @param string $identifier (Plain text 32 byte unique identifier that is compared against stored hash)
|
|
* @return array (Cookie parameters, otherwise false)
|
|
*/
|
|
public function validatePersistentAuthToken($selector, $identifier)
|
|
{
|
|
$selector = trim($selector);
|
|
|
|
// Get the latest token from the database
|
|
$pdo = PDOMysqlConnection::getInstance();
|
|
|
|
$sql = 'SELECT validator, UNIX_TIMESTAMP(expiration) AS expires FROM auth_tokens WHERE user_id = ? AND selector = ? ORDER BY id DESC LIMIT 1';
|
|
$stmt = $pdo->prepareAndExec($sql, [$this->_id, $selector]);
|
|
$latestToken = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
// Make sure a token was found and not expired
|
|
if ($latestToken && $latestToken['expires'] > time())
|
|
{
|
|
// Validate the validator against the stored hash
|
|
if (hash_equals(hash('sha256', $identifier), $latestToken['validator']))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
$this->_lastError = 'Persistent token not found.';
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Helper function for changing password by first checking the old password
|
|
* against the encrypted password and setting the new password if the password
|
|
* validates successfully.
|
|
*
|
|
* @param string $oldPassword (Password that is being changed)
|
|
* @param string $newPassword (Password that is being set)
|
|
* @return boolean (True if password is correct, otherwise false)
|
|
*/
|
|
public function changePassword($oldPassword, $newPassword)
|
|
{
|
|
// Can't do anything if the password has not been set (hashed)
|
|
if ($this->password == null)
|
|
{
|
|
$this->_lastError = 'Cannot change password, no password is currently set.';
|
|
return false;
|
|
}
|
|
|
|
// Remove whitespace
|
|
$oldPassword = trim($oldPassword);
|
|
$newPassword = trim($newPassword);
|
|
|
|
// Sanity check
|
|
if (empty($oldPassword) || empty($newPassword))
|
|
{
|
|
$this->_lastError = 'Cannot change password, old password or new password are empty.';
|
|
return false;
|
|
}
|
|
|
|
// Verify the old password and set if correct
|
|
if ($this->verifyPassword($oldPassword))
|
|
{
|
|
// setPassword will populate _lastError if the password is invalid
|
|
return $this->setPassword($newPassword);
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'The old password is incorrect.';
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check the call for validity, then set
|
|
** Has minimum 4 characters in length.
|
|
** Acceptable characters A-Z, 0-9. Minimum length 3, Maximum length 10.
|
|
*
|
|
* @param string $callsign
|
|
* @return boolean (True if callsign is acceptable and was set, otherwise false)
|
|
*/
|
|
public function setCallsign($callsign)
|
|
{
|
|
if (is_null($callsign) || strlen($callsign) == 0)
|
|
{
|
|
$this->callsign = null;
|
|
return true;
|
|
}
|
|
|
|
// Remove whitespace & make all uppercase
|
|
$callsign = strtoupper(trim($callsign));
|
|
|
|
// Test the password and set it if passes (no )
|
|
if (strlen($callsign) >= 3 && strlen($callsign) <= 10 && preg_match('/[^a-z0-9]/i', $callsign) == 0)
|
|
{
|
|
// Callsign is good, make sure it is not in use by another user
|
|
$user = UserRepository::getInstance()->getObjectByCallsign($callsign);
|
|
if (!$user->isExistingObject())
|
|
{
|
|
if (!$this->isExistingObject() || ($this->isExistingObject() && is_null($this->callsign)))
|
|
$this->callsign = $callsign;
|
|
$this->callsignVerified = 0;
|
|
$this->_isChanged = true;
|
|
|
|
// Attempt callsign verification is the e-mail has already been verified
|
|
if ($this->emailVerified)
|
|
{
|
|
$this->completeCallsignVerification();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
$this->_lastError = 'Callsign is already in use with another user.';
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'Callsign must be 3-10 characters. Acceptable characters A-Z, 0-9.';
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check the email address for validity, then set
|
|
** Has minimum 4 characters in length.
|
|
** Acceptable characters A-Z, 0-9. Minimum length 3, Maximum length 10.
|
|
*
|
|
* @param string $callsign
|
|
* @return boolean (True if email address is acceptable and was set, otherwise false)
|
|
*/
|
|
public function setEmail($email)
|
|
{
|
|
// Remove whitespace
|
|
$email = strtolower(trim($email));
|
|
|
|
// Test the e-mail address and set it if passes (no )
|
|
// Also make sure it's not the same e-mail which wouldn't need to be saved
|
|
if (filter_var($email, FILTER_VALIDATE_EMAIL) && $email != $this->email)
|
|
{
|
|
// Email is good, make sure it is not in use by another user
|
|
$user = UserRepository::getInstance()->getObjectByEmail($email);
|
|
if (!$user->isExistingObject())
|
|
{
|
|
$this->email = $email;
|
|
$this->_isChanged = true;
|
|
$this->emailVerified = 0;
|
|
return true;
|
|
}
|
|
|
|
$this->_lastError = 'E-mail address is already in use with another user.';
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'E-mail address is not valid.';
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Starts the e-mail verification process
|
|
*
|
|
* @return boolean (True if email with verification code was sent successfully)
|
|
*/
|
|
public function startEmailVerification()
|
|
{
|
|
// Do nothing if not enabled in config
|
|
if (getWebsiteConfig('smtp_server_address') == '') return false;
|
|
|
|
// Test the e-mail address before allowing the verification flag to be set to true
|
|
if (!isset($this->email) || !filter_var($this->email, FILTER_VALIDATE_EMAIL))
|
|
{
|
|
$this->_lastError = 'Cannot start e-mail verification, e-mail address is not valid.';
|
|
return false;
|
|
}
|
|
|
|
// Generate random reset code
|
|
$code = generateSecureCode(6);
|
|
|
|
// Code expires in 30 minutes
|
|
$expiration = time() + 1800;
|
|
|
|
// Get the site backend database connection
|
|
$pdo = PDOMysqlConnection::getInstance();
|
|
|
|
// Save the code
|
|
$sql = 'INSERT INTO email_verification (user_id, code, expiration)
|
|
VALUES (?, ?, FROM_UNIXTIME(?))';
|
|
|
|
$stmt = $pdo->prepareAndExec($sql, [$this->_id, $code, $expiration]);
|
|
$id = $pdo->lastInsertId();
|
|
if ($id)
|
|
{
|
|
// Set to unverified
|
|
$this->emailVerified = false;
|
|
$this->_isChanged = true;
|
|
$this->_lastError = $code;
|
|
|
|
$confirmation_link_with_code = '';
|
|
$confirmation_link_without_code = '';
|
|
|
|
// Send the confirmation e-mail
|
|
$use_secure_smtp = !empty(getWebsiteConfig('smtp_server_password')) ? true : false;
|
|
|
|
// Note: passing `true` enables exceptions
|
|
$mail = new PHPMailer(true);
|
|
try
|
|
{
|
|
// PHP_Mailer Server settings
|
|
//$mail->SMTPDebug = \PHPMailer\PHPMailer\SMTP::DEBUG_SERVER; // Enable verbose debug output
|
|
$mail->isSMTP(); // Send using SMTP
|
|
$mail->Host = getWebsiteConfig('smtp_server_address'); // Set the SMTP server to send through
|
|
$mail->SMTPAuth = $use_secure_smtp; // Enable SMTP authentication
|
|
$mail->Username = getWebsiteConfig('smtp_server_username'); // SMTP username
|
|
$mail->Password = getWebsiteConfig('smtp_server_password'); // SMTP password
|
|
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // Enable implicit TLS encryption
|
|
$mail->Port = getWebsiteConfig('smtp_server_port'); // TCP port to connect to; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS`
|
|
|
|
// Set to & from addresses
|
|
$mail->setFrom(getWebsiteConfig('smtp_from_email'), getWebsiteConfig('smtp_from_email'));
|
|
$mail->addAddress($this->email, $this->userName);
|
|
|
|
// Content
|
|
$mail->isHTML(true); // Set email format to HTML
|
|
$mail->Subject = 'Confirm your ' . getWebsiteConfig('title') . ' Account';
|
|
$mail->Body = 'You are receiving this e-mail because an account has been associated with this e-mail address at the <a href="' . getWebsiteConfig('cookie_domain') . '">'
|
|
. getWebsiteConfig('title') . "</a>\r\nIf you believe that this has been received in error, you may safely disregard this message.\r\n"
|
|
. "\r\n\r\n<br><br>If you recognize this account, please complete your account activation by confirming your e-mail address.\r\n"
|
|
. 'Follow this link to <a href="' . getWebsiteConfig('email_link_url') . '/views/validate.php?a=email&code=' . $code . '">verify your account</a>. If asked, enter code <b>' . $code . "</b>\r\n\r\n"
|
|
. "\r\n" . '<br><br>Questions? Please <a href="' . getWebsiteConfig('email_link_url') . '/views/contact.php">contact us</a>.' . "\r\n";
|
|
|
|
$mail->AltBody = 'You are receiving this e-mail because an account has been associated with this e-mail address at the ' . getWebsiteConfig('title')
|
|
. getWebsiteConfig('cookie_domain') . "\r\nIf you believe that this has been received in error, you may safely disregard this message.\e\n"
|
|
. "\r\n\r\nIf you recognize this account, please complete your account activation by confirming your e-mail address.\r\n"
|
|
. "Go to " . getWebsiteConfig('email_link_url') . "/views/validate.php?a=email and enter code $code.\r\n\r\n"
|
|
. "Questions? Please contact us at " . getWebsiteConfig('email_link_url') . "/views/contact.php\r\n";
|
|
|
|
$mail->send();
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
$this->_lastError = "Activation e-mail could not be sent. Mailer Error: {$mail->ErrorInfo}";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
$this->_lastError = 'An error occurred in generating or sending the email verification.';
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Completes e-mail verification.
|
|
** This is based on whether the user has completed e-mail verification by
|
|
** following the link in the e-mail or entering the code.
|
|
*
|
|
* @param string $code (Verification code to test)
|
|
* @return boolean (True if email address is verification is complete, otherwise false)
|
|
*/
|
|
public function completeEmailVerification($code)
|
|
{
|
|
$code = trim($code);
|
|
|
|
// Test the e-mail address before allowing the verification flag to be set to true
|
|
if ($code && (!isset($this->email) || !filter_var($this->email, FILTER_VALIDATE_EMAIL)))
|
|
{
|
|
$this->_lastError = 'Cannot complete e-mail verification, e-mail address is not valid.';
|
|
return false;
|
|
}
|
|
|
|
// Get the lastest email verification record for the user
|
|
$sql = 'SELECT code, UNIX_TIMESTAMP(expiration) AS expires FROM email_verification WHERE user_id = ? ORDER BY id DESC LIMIT 1';
|
|
$pdo = PDOMysqlConnection::getInstance();
|
|
$stmt = $pdo->prepareAndExec($sql, [$this->_id]);
|
|
$verification = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if ($verification)
|
|
{
|
|
// Does the code match, and is it not expired?
|
|
if (time() < $verification['expires'])
|
|
{
|
|
if ($code == $verification['code'])
|
|
{
|
|
$this->emailVerified = true;
|
|
$this->_isChanged = true;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'Cannot complete e-mail verification, verification code is incorrect.';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'Cannot complete e-mail verification, verification code has expired.';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'Cannot complete e-mail verification, no verification exists for this user.';
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the e-mail verification status as text
|
|
*
|
|
* @return string (Verified or Unverified)
|
|
*/
|
|
public function getEmailVerificationStatus()
|
|
{
|
|
return $this->emailVerified ? 'Verified' : 'Unverified';
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Completes callsign verification.
|
|
** Contacts QRZ.com and pulls the users profile and verifies the e-mail address.
|
|
*
|
|
* @return boolean (True if callsign is verification is complete, otherwise false)
|
|
*/
|
|
public function completeCallsignVerification()
|
|
{
|
|
if (@is_null($this->callsign) || strlen($this->callsign) > 3)
|
|
{
|
|
// E-Mail address must first be verified
|
|
if ($this->emailVerified == 1)
|
|
{
|
|
// Create a new instance of the QRZ helper class
|
|
$qrz = new QRZ();
|
|
if ($qrz->queryCall($this->callsign))
|
|
{
|
|
if (isset($qrz->Callsign) && isset($qrz->Callsign->email))
|
|
{
|
|
if (strtolower($qrz->Callsign->email) == strtolower($this->email))
|
|
{
|
|
$this->callsignVerified = 1;
|
|
$this->_isChanged = true;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'The e-mail address does not match your public QRZ.com profile.';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'No response or missing data reported by QRZ.com.';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Pass the error from QRZ through
|
|
$this->_lastError = $qrz->_lastError;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'E-mail address must be verified first.';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'Invalid callsign.';
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns thecallsign verification status as text
|
|
*
|
|
* @return string (Verified or Unverified)
|
|
*/
|
|
public function getCallsignVerificationStatus()
|
|
{
|
|
return $this->callsignVerified ? 'Verified' : 'Unverified';
|
|
}
|
|
|
|
|
|
/**
|
|
* Starts the password recovery process
|
|
*
|
|
* @return boolean (True if email with verification code was sent successfully)
|
|
*/
|
|
public function startPasswordRecovery()
|
|
{
|
|
// Do nothing if not enabled in config
|
|
if (getWebsiteConfig('smtp_server_address') == '') return false;
|
|
|
|
// Generate random reset code
|
|
$code = generateSecureCode(6);
|
|
|
|
// Code expires in 30 minutes
|
|
$expiration = time() + 1800;
|
|
|
|
// Get the site backend database connection
|
|
$pdo = PDOMysqlConnection::getInstance();
|
|
|
|
// Save the code
|
|
$sql = 'INSERT INTO password_verification (user_id, code, expiration)
|
|
VALUES (?, ?, FROM_UNIXTIME(?))';
|
|
|
|
$stmt = $pdo->prepareAndExec($sql, [$this->_id, $code, $expiration]);
|
|
$id = $pdo->lastInsertId();
|
|
if ($id)
|
|
{
|
|
$this->_lastError = $code;
|
|
|
|
$confirmation_link_with_code = '';
|
|
$confirmation_link_without_code = '';
|
|
|
|
// Send the confirmation e-mail
|
|
$use_secure_smtp = !empty(getWebsiteConfig('smtp_server_password')) ? true : false;
|
|
|
|
// Note: passing `true` enables exceptions
|
|
$mail = new PHPMailer(true);
|
|
try
|
|
{
|
|
// PHP_Mailer Server settings
|
|
//$mail->SMTPDebug = \PHPMailer\PHPMailer\SMTP::DEBUG_SERVER; // Enable verbose debug output
|
|
$mail->isSMTP(); // Send using SMTP
|
|
$mail->Host = getWebsiteConfig('smtp_server_address'); // Set the SMTP server to send through
|
|
$mail->SMTPAuth = $use_secure_smtp; // Enable SMTP authentication
|
|
$mail->Username = getWebsiteConfig('smtp_server_username'); // SMTP username
|
|
$mail->Password = getWebsiteConfig('smtp_server_password'); // SMTP password
|
|
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // Enable implicit TLS encryption
|
|
$mail->Port = getWebsiteConfig('smtp_server_port'); // TCP port to connect to; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS`
|
|
|
|
// Set to & from addresses
|
|
$mail->setFrom(getWebsiteConfig('smtp_from_email'), getWebsiteConfig('smtp_from_email'));
|
|
$mail->addAddress($this->email, $this->userName);
|
|
|
|
// Content
|
|
$mail->isHTML(true); // Set email format to HTML
|
|
$mail->Subject = 'Password recovery for your ' . getWebsiteConfig('title') . ' account.';
|
|
$mail->Body = 'You are receiving this e-mail because an account which has been associated with this e-mail address at the <a href="' . getWebsiteConfig('cookie_domain') . '">'
|
|
. getWebsiteConfig('title') . "</a> has requested a password reset.\r\nIf you believe that this has been received in error, you may safely disregard this message.\r\n"
|
|
. "\r\n\r\n<br><br>If you recognize this account, please complete your password recovery by visitng the link below.\r\n"
|
|
. 'Follow this link to <a href="' . getWebsiteConfig('email_link_url') . '/views/validate.php?a=password&code=' . $code . '&email=' . urlencode($this->email) . '">reset your password</a>. If asked, enter code <b>' . $code . "</b>\r\n\r\n"
|
|
. "\r\n" . '<br><br>Questions? Please <a href="' . getWebsiteConfig('email_link_url') . '/views/contact.php">contact us</a>.' . "\r\n";
|
|
|
|
$mail->AltBody = 'You are receiving this e-mail because an account which has been associated with this e-mail address at the ' . getWebsiteConfig('title')
|
|
. getWebsiteConfig('cookie_domain') . " has requested a password reset.\r\nIf you believe that this has been received in error, you may safely disregard this message.\e\n"
|
|
. "\r\n\r\nIf you recognize this account, please complete your password recovery by visitng the link below.\r\n"
|
|
. "Go to " . getWebsiteConfig('email_link_url') . "/views/validate.php?a=pass and enter code $code.\r\n\r\n"
|
|
. "Questions? Please contact us at " . getWebsiteConfig('email_link_url') . "/views/contact.php\r\n";
|
|
|
|
$mail->send();
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
$this->_lastError = "Password reset e-mail could not be sent. Mailer Error: {$mail->ErrorInfo}";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
$this->_lastError = 'An error occurred in generating or sending the password reset email.';
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Completes password recovery process.
|
|
** This process validates the 6 digit code, and if confirmed
|
|
** return true to alow the password reset process to be completed.
|
|
*
|
|
* @param string $code (Verification code to test)
|
|
* @param string $newpassword (New password to set if the code matches)
|
|
* @return boolean (True if email address is verification is complete, otherwise false)
|
|
*/
|
|
public function completePasswordRecovery($code, $email, $newPassword)
|
|
{
|
|
$code = trim($code);
|
|
$email = trim($email);
|
|
$newPassword = trim($newPassword);
|
|
|
|
// Get the lastest email verification record for the user
|
|
$sql = 'SELECT user_id, UNIX_TIMESTAMP(expiration) AS expires FROM password_verification WHERE code = ? ORDER BY id DESC LIMIT 1';
|
|
$pdo = PDOMysqlConnection::getInstance();
|
|
$stmt = $pdo->prepareAndExec($sql, [$code]);
|
|
$verification = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if ($verification)
|
|
{
|
|
// Does the code match, and is it not expired?
|
|
if (time() < $verification['expires'])
|
|
{
|
|
// Code is valid, load the user record
|
|
$user = UserRepository::getInstance()->getObjectById($verification['user_id']);
|
|
|
|
// Set the password if the user account is found
|
|
if ($user->isExistingObject())
|
|
{
|
|
// Compare the e-mail address provided
|
|
if ($email == $user->email)
|
|
{
|
|
// Set the new password if it's valid then delete the reset code so it cannot be used again
|
|
if ($user->setPassword($newPassword))
|
|
{
|
|
$sql = 'DELETE FROM password_verification WHERE user_id = ?';
|
|
$pdo = PDOMysqlConnection::getInstance();
|
|
$stmt = $pdo->prepareAndExec($sql, [$user->_id]);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = $user->getLastError();
|
|
return false; // _lastError is set by setPassword();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'Cannot complete password recovery, e-mail address provided is not on record.';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'Cannot complete password recovery, verification code is incorrect.';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'Cannot complete password recovery, verification code has expired.';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->_lastError = 'Cannot complete password recovery, no verification exists for this user.';
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Save the user record if there are any changes pending
|
|
*
|
|
* @return boolean (True if save was successful, otherwise false)
|
|
*/
|
|
public function save()
|
|
{
|
|
// Don't save unless something has been changed
|
|
if ($this->_isChanged === false)
|
|
{
|
|
$this->_lastError = 'No changes have been made to save.';
|
|
return false;
|
|
}
|
|
|
|
// Make sure all critical properties are completed
|
|
if ($this->userName == null || $this->password == null
|
|
|| $this->email == null)
|
|
{
|
|
$this->_lastError = 'Cannot save, one or more user properties are missing.';
|
|
return false;
|
|
}
|
|
|
|
// Get the site backend database connection
|
|
$pdo = PDOMysqlConnection::getInstance();
|
|
|
|
// Inserting or saving?
|
|
if ($this->isExistingObject())
|
|
{
|
|
$sql = 'UPDATE users
|
|
SET user_name = ?, password = ?, callsign = ?, email = ?, email_verified = ?, callsign_verified = ?
|
|
WHERE id = ?
|
|
LIMIT 1';
|
|
$stmt = $pdo->prepareAndExec($sql, [$this->userName, $this->password, $this->callsign, $this->email, $this->emailVerified, $this->callsignVerified, $this->_id]);
|
|
if ($stmt)
|
|
{
|
|
$this->_isChanged = false;
|
|
return true;
|
|
}
|
|
$this->_lastError = 'An error occurred saving the user record.';
|
|
}
|
|
else
|
|
{
|
|
$sql = 'INSERT INTO users (user_name, password, callsign, email, email_verified, callsign_verified)
|
|
VALUES (?, ?, ?, ?, ?, ?)';
|
|
|
|
$stmt = $pdo->prepareAndExec($sql, [$this->userName, $this->password, $this->callsign, $this->email, $this->emailVerified, $this->callsignVerified]);
|
|
$id = $pdo->lastInsertId();
|
|
if ($id)
|
|
{
|
|
$this->_id = $id;
|
|
$this->_isChanged = false;
|
|
return true;
|
|
}
|
|
|
|
$this->_lastError = 'An error occurred creating the user record.';
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Checks the saved user favorite stations to see if the station is a favorite station
|
|
*
|
|
* @param int $station_id
|
|
* @return boolean (True if station is a favorite, otherwise false)
|
|
*/
|
|
public function isStationAFavorite($station_id)
|
|
{
|
|
static $favorites = null;
|
|
static $favoritesTS = null;
|
|
|
|
if (!isInt($station_id)) return false;
|
|
|
|
// Cache favorites for 30 seconds
|
|
if (is_null($favorites) || (!is_null($favoritesTS) && $favoritesTS < time() - 30))
|
|
{
|
|
$favorites = FavoriteStationRepository::getInstance()->getObjectListByUserId($this->id);
|
|
$favoritesTS = time();
|
|
}
|
|
|
|
if (is_null($favorites) || sizeof($favorites) == 0) return false;
|
|
|
|
foreach($favorites AS $favorite)
|
|
{
|
|
if ($favorite->stationId == $station_id) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Records the a station browse (view) event
|
|
*/
|
|
public function recordStationBrowseHistory($station_id)
|
|
{
|
|
if (!isInt($station_id)) return false;
|
|
|
|
// Only record history for valid users
|
|
if ($this->isExistingObject())
|
|
{
|
|
// Get the site backend database connection
|
|
$pdo = PDOMysqlConnection::getInstance();
|
|
|
|
$sql = 'INSERT INTO station_browse_history SET user_id = ?, station_id = ?, view_time = NOW()';
|
|
$stmt = $pdo->prepareAndExec($sql, [$this->_id, $station_id]);
|
|
if ($stmt)
|
|
{
|
|
return true;
|
|
}
|
|
$this->_lastError = 'An error occurred updating the user station browse history.';
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a list of the stations the user has viewed
|
|
*
|
|
* @return Array (Station IDs with Last View Time)
|
|
*/
|
|
public function getStationBrowseHistory($iconScale = 16, $limit = 5)
|
|
{
|
|
if (!isInt($limit)) $limit = 5;
|
|
|
|
// Only record history for valid users
|
|
if ($this->isExistingObject())
|
|
{
|
|
// Get the site backend database connection
|
|
$pdoMysql = PDOMysqlConnection::getInstance();
|
|
|
|
$sql = "SELECT DISTINCT(station_id) FROM station_browse_history WHERE user_id = ? ORDER BY view_time DESC LIMIT $limit";
|
|
$stmt = $pdoMysql->prepareAndExec($sql, [$this->_id]);
|
|
if ($stmt)
|
|
{
|
|
$records = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
$idList = implode(',', array_column($records, 'station_id'));
|
|
|
|
// Main Database
|
|
$pdo = PDOConnection::getInstance();
|
|
|
|
// Last viewed stations
|
|
$history = [];
|
|
|
|
if (count($records) > 0)
|
|
{
|
|
// Get full station details
|
|
$query = $pdo->prepareAndExec
|
|
(
|
|
"SELECT id,source_id,name,station_type_id,latest_confirmed_symbol,latest_confirmed_symbol_table,latest_confirmed_packet_timestamp
|
|
FROM station
|
|
WHERE latest_confirmed_packet_timestamp IS NOT NULL AND ID IN($idList)
|
|
ORDER BY latest_confirmed_packet_timestamp DESC, name ASC;"
|
|
);
|
|
|
|
$stationCache = [];
|
|
while ($result = $query->fetch(PDO::FETCH_ASSOC))
|
|
{
|
|
// Symbol
|
|
if (strlen($result['latest_confirmed_symbol']) >= 1 && strlen($result['latest_confirmed_symbol_table']) >= 1) {
|
|
$symbolAsciiValue = ord(substr($result['latest_confirmed_symbol'], 0, 1));
|
|
$symbolTableAsciiValue = ord(substr($result['latest_confirmed_symbol_table'], 0, 1));
|
|
} else {
|
|
// Default values
|
|
$symbolAsciiValue = 125;
|
|
$symbolTableAsciiValue = 47;
|
|
}
|
|
|
|
// Symbol URL
|
|
$symbolPath = '/symbols/symbol-' . $symbolAsciiValue . '-' . $symbolTableAsciiValue . '-scale'.$iconScale.'x'.$iconScale.'.svg';
|
|
$isFavorite = $this->isStationAFavorite($result['id']) ? 1 : 0;
|
|
|
|
$stationCache[$result['id']] = ['id'=>$result['id'], 'source_id'=>$result['source_id'], 'type'=>$result['station_type_id'], 'text'=>$result['name'], 'icon'=>$symbolPath, 'fav'=>$isFavorite];
|
|
}
|
|
|
|
foreach ($records AS $record)
|
|
{
|
|
if (isset($stationCache[$record['station_id']])) $history[] = $stationCache[$record['station_id']];
|
|
}
|
|
}
|
|
|
|
return $history;
|
|
}
|
|
$this->_lastError = 'An error occurred updating the user station browse history.';
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Delete the user record
|
|
*
|
|
* @return boolean (True if delete was successful, otherwise false)
|
|
*/
|
|
public function delete()
|
|
{
|
|
if ($this->isExistingObject())
|
|
{
|
|
// Get the site backend database connection
|
|
$pdo = PDOMysqlConnection::getInstance();
|
|
|
|
$sql = 'DELETE FROM users WHERE id = ? LIMIT 1';
|
|
$stmt = $pdo->prepareAndExec($sql, [$this->_id]);
|
|
if ($stmt)
|
|
{
|
|
return true;
|
|
}
|
|
$this->_lastError = 'An error occurred deleting the user record.';
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the last error message set during a user action then
|
|
* sets _lastError to an empty string.
|
|
*
|
|
* @return string (Text of the last recorded error)
|
|
*/
|
|
public function getLastError()
|
|
{
|
|
$last_error = $this->_lastError;
|
|
$this->_lastError = '';
|
|
return $last_error;
|
|
}
|
|
}
|