Steve White ae9ece5266 *** Initial Commit of Files for APRS.TO Backend Administration Panel ***
This code is non-functional at this point.
2025-02-02 15:53:34 -05:00

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;
}
}