Add phpstan and resolve almost all issues
This commit is contained in:
parent
c5531d21cf
commit
1f3257424a
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "fwdekker/death-notifier",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"homepage": "https://git.fwdekker.com/tools/death-notifier",
|
||||
|
@ -18,8 +18,11 @@
|
|||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-sqlite3": "*",
|
||||
"monolog/monolog": "^3.2.0",
|
||||
"phpmailer/phpmailer": "^6.6.3"
|
||||
"monolog/monolog": "^3.2",
|
||||
"phpmailer/phpmailer": "^6.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
Binary file not shown.
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "death-notifier",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"author": "Florine W. Dekker",
|
||||
"browser": "dist/bundle.js",
|
||||
|
@ -13,7 +13,8 @@
|
|||
"clean": "grunt clean",
|
||||
"dev": "grunt dev",
|
||||
"dev:server": "grunt dev:server",
|
||||
"deploy": "grunt deploy"
|
||||
"deploy": "grunt deploy",
|
||||
"stan": "vendor/bin/phpstan analyse -l 8 src/main"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "^1.5.3",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
parameters:
|
||||
excludePaths:
|
||||
- src/main/config.default.ini.php
|
||||
- src/main/config.ini.php
|
|
@ -15,9 +15,10 @@ require_once __DIR__ . "/vendor/autoload.php";
|
|||
|
||||
/// Preparations
|
||||
// Load config
|
||||
$config = parse_ini_file("config.default.ini.php", process_sections: true, scanner_mode: INI_SCANNER_TYPED);
|
||||
$config = parse_ini_file("config.default.ini.php", process_sections: true, scanner_mode: INI_SCANNER_TYPED) or exit(1);
|
||||
if (file_exists("config.ini.php")) {
|
||||
$config_custom = parse_ini_file("config.ini.php", process_sections: true, scanner_mode: INI_SCANNER_TYPED);
|
||||
$config_custom =
|
||||
parse_ini_file("config.ini.php", process_sections: true, scanner_mode: INI_SCANNER_TYPED) or exit(1);
|
||||
$config = array_merge($config, $config_custom);
|
||||
}
|
||||
|
||||
|
@ -50,12 +51,12 @@ if (!file_exists($config["database"]["filename"])) {
|
|||
*
|
||||
* @return string the generated CSRF token
|
||||
*/
|
||||
function generate_csrf_token(): string
|
||||
function generate_csrf_token(Logger $logger): string
|
||||
{
|
||||
try {
|
||||
return bin2hex(random_bytes(32));
|
||||
} catch (Exception $exception) {
|
||||
$log->emergency("Failed to generate token.", [$exception]);
|
||||
$logger->emergency("Failed to generate token.", [$exception]);
|
||||
http_response_code(500);
|
||||
exit();
|
||||
}
|
||||
|
@ -63,10 +64,11 @@ function generate_csrf_token(): string
|
|||
|
||||
session_start();
|
||||
if (!isset($_SESSION["token"]))
|
||||
$_SESSION["token"] = generate_csrf_token();
|
||||
$_SESSION["token"] = generate_csrf_token($logger);
|
||||
|
||||
// Read JSON from POST
|
||||
$_POST = json_decode(file_get_contents("php://input"), associative: true);
|
||||
$post_input = file_get_contents("php://input");
|
||||
if ($post_input !== false) $_POST = json_decode($post_input, associative: true);
|
||||
|
||||
|
||||
/// Define validation helpers
|
||||
|
@ -153,7 +155,7 @@ if (isset($_POST["action"])) {
|
|||
|
||||
session_destroy();
|
||||
session_start();
|
||||
$_SESSION["token"] = generate_csrf_token();
|
||||
$_SESSION["token"] = generate_csrf_token($logger);
|
||||
$response = new Response(null, true);
|
||||
break;
|
||||
case "update-email":
|
||||
|
@ -194,7 +196,7 @@ if (isset($_POST["action"])) {
|
|||
$response = validate_csrf() ?? $mailer->send_test_mail();
|
||||
break;
|
||||
default:
|
||||
$response["message"] = "Unknown POST action '" . $_POST["action"] . "'.";
|
||||
$response = new Response("Unknown POST action '" . $_POST["action"] . "'.", false);
|
||||
break;
|
||||
}
|
||||
} else if (isset($_GET["action"])) {
|
||||
|
|
|
@ -42,11 +42,11 @@ class Mediawiki
|
|||
/**
|
||||
* Sends a request to Wikipedia's API and returns its response as a JSON object.
|
||||
*
|
||||
* @param array $url_param the query parameters to send to the API
|
||||
* @return array a JSON object containing the API's response
|
||||
* @param array<string, mixed> $url_param the query parameters to send to the API
|
||||
* @return mixed a JSON object containing the API's response
|
||||
* @throws Exception if the API could not be reached
|
||||
*/
|
||||
private function api_fetch(array $url_param): array
|
||||
private function api_fetch(array $url_param): mixed
|
||||
{
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
|
@ -55,7 +55,7 @@ class Mediawiki
|
|||
|
||||
$output = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
if (curl_error($ch))
|
||||
if (is_bool($output) || curl_error($ch))
|
||||
throw new Exception(curl_error($ch));
|
||||
|
||||
return json_decode($output, associative: true);
|
||||
|
@ -64,7 +64,7 @@ class Mediawiki
|
|||
/**
|
||||
* Checks for each person whether they are alive according to Wikipedia's categorization.
|
||||
*
|
||||
* @param array $people_names the names of the people to check aliveness of
|
||||
* @param array<string> $people_names the names of the people to check aliveness of
|
||||
* @return Response responds positively with each person's aliveness, or a negative response explaining what went
|
||||
* wrong
|
||||
*/
|
||||
|
|
|
@ -18,7 +18,7 @@ class Response
|
|||
public bool $satisfied;
|
||||
|
||||
|
||||
public function __construct($message, $satisfied)
|
||||
public function __construct(mixed $message, bool $satisfied)
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->satisfied = $satisfied;
|
||||
|
|
|
@ -57,6 +57,11 @@ class TrackingManager
|
|||
{
|
||||
$db = new SQLite3($this->db_filename, SQLITE3_OPEN_READWRITE);
|
||||
$stmt = $db->prepare("INSERT INTO trackings (user_uuid, person_name) VALUES (:user_uuid, :person_name);");
|
||||
if ($stmt === false) {
|
||||
$db->close();
|
||||
$this->logger->error("Failed to prepare query in 'add_tracking'.");
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
$stmt->bindValue(":user_uuid", $user_uuid);
|
||||
$stmt->bindValue(":person_name", $person_name);
|
||||
$inserted = $stmt->execute() !== false;
|
||||
|
@ -76,6 +81,11 @@ class TrackingManager
|
|||
{
|
||||
$db = new SQLite3($this->db_filename, SQLITE3_OPEN_READWRITE);
|
||||
$stmt = $db->prepare("DELETE FROM trackings WHERE user_uuid=:user_uuid AND person_name=:person_name;");
|
||||
if ($stmt === false) {
|
||||
$db->close();
|
||||
$this->logger->error("Failed to prepare query in 'remove_tracking'.");
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
$stmt->bindValue(":user_uuid", $user_uuid);
|
||||
$stmt->bindValue(":person_name", $person_name);
|
||||
$inserted = $stmt->execute() !== false;
|
||||
|
@ -94,8 +104,18 @@ class TrackingManager
|
|||
{
|
||||
$db = new SQLite3($this->db_filename, SQLITE3_OPEN_READONLY);
|
||||
$stmt = $db->prepare("SELECT * FROM trackings WHERE user_uuid=:user_uuid;");
|
||||
if ($stmt === false) {
|
||||
$db->close();
|
||||
$this->logger->error("Failed to prepare query in 'list_trackings'.");
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
$stmt->bindValue(":user_uuid", $user_uuid);
|
||||
$results = $stmt->execute();
|
||||
if ($results === false) {
|
||||
$db->close();
|
||||
$this->logger->error("Failed to read query results in 'list_trackings'.");
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
|
||||
$trackings = [];
|
||||
while ($row = $results->fetchArray(SQLITE3_ASSOC))
|
||||
|
|
|
@ -74,8 +74,20 @@ class UserManager
|
|||
|
||||
// Check if email address is already in use
|
||||
$stmt = $db->prepare("SELECT 1 FROM users WHERE email=:email;");
|
||||
if ($stmt === false) {
|
||||
$db->exec("COMMIT;");
|
||||
$db->close();
|
||||
$this->logger->error("Failed to prepare query in 'register'.");
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
$stmt->bindValue(":email", $email);
|
||||
$email_available = $stmt->execute()->fetchArray(SQLITE3_ASSOC) === false;
|
||||
$result = $stmt->execute();
|
||||
if ($result === false) {
|
||||
$db->exec("COMMIT;");
|
||||
$db->close();
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
$email_available = $result->fetchArray(SQLITE3_ASSOC) === false;
|
||||
if (!$email_available) {
|
||||
$db->exec("COMMIT;");
|
||||
$db->close();
|
||||
|
@ -93,6 +105,12 @@ class UserManager
|
|||
|
||||
// Register user
|
||||
$stmt = $db->prepare("INSERT INTO users (uuid, email, password) VALUES (:uuid, :email, :password);");
|
||||
if ($stmt === false) {
|
||||
$db->exec("COMMIT;");
|
||||
$db->close();
|
||||
$this->logger->error("Failed to prepare query in 'register'.");
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
$stmt->bindValue(":uuid", $uuid);
|
||||
$stmt->bindValue(":email", $email);
|
||||
$stmt->bindValue(":password", password_hash($password, PASSWORD_DEFAULT));
|
||||
|
@ -101,7 +119,7 @@ class UserManager
|
|||
if (!$user_registered) {
|
||||
$db->exec("COMMIT;");
|
||||
$db->close();
|
||||
return new Response("Unknown database error.", false);
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
|
||||
// Respond
|
||||
|
@ -122,6 +140,11 @@ class UserManager
|
|||
{
|
||||
$db = new SQLite3($this->db_filename, SQLITE3_OPEN_READWRITE);
|
||||
$stmt = $db->prepare("DELETE FROM users WHERE uuid=:uuid;");
|
||||
if ($stmt === false) {
|
||||
$db->close();
|
||||
$this->logger->error("Failed to prepare query in 'delete'.");
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
$stmt->bindValue(":uuid", $uuid);
|
||||
$result = $stmt->execute();
|
||||
$db->close();
|
||||
|
@ -136,16 +159,27 @@ class UserManager
|
|||
*
|
||||
* @param string $email the email address of the user whose password should be checked
|
||||
* @param string $password the password to check against the specified user
|
||||
* @return array the first element is a response with message `null` if the login was successful, or a response with
|
||||
* a message explaining what went wrong otherwise; the second element is the UUID of the user that was logged in as,
|
||||
* or `null` if the login should not be performed
|
||||
* @return array{Response, ?string} the first element is a response with message `null` if the login was successful,
|
||||
* or a response with a message explaining what went wrong otherwise; the second element is the UUID of the user
|
||||
* that was logged in as, or `null` if the login should not be performed
|
||||
*/
|
||||
public function check_login(string $email, string $password): array
|
||||
{
|
||||
$db = new SQLite3($this->db_filename, SQLITE3_OPEN_READONLY);
|
||||
$stmt = $db->prepare("SELECT uuid, password FROM users WHERE email=:email;");
|
||||
if ($stmt === false) {
|
||||
$db->close();
|
||||
$this->logger->error("Failed to prepare query in 'check_login'.");
|
||||
return [new Response("Unexpected database error.", false), null];
|
||||
}
|
||||
$stmt->bindValue(":email", $email);
|
||||
$user = $stmt->execute()->fetchArray(SQLITE3_ASSOC);
|
||||
$result = $stmt->execute();
|
||||
if ($result === false) {
|
||||
$db->close();
|
||||
$this->logger->error("Failed to execute query in 'check_login'.");
|
||||
return [new Response("Unexpected database error.", false), null];
|
||||
}
|
||||
$user = $result->fetchArray(SQLITE3_ASSOC);
|
||||
$db->close();
|
||||
|
||||
return $user === false || !password_verify($password, $user["password"])
|
||||
|
@ -163,8 +197,19 @@ class UserManager
|
|||
{
|
||||
$db = new SQLite3($this->db_filename, SQLITE3_OPEN_READONLY);
|
||||
$stmt = $db->prepare("SELECT * FROM users WHERE uuid=:uuid;");
|
||||
if ($stmt === false) {
|
||||
$db->close();
|
||||
$this->logger->error("Failed to prepare query in 'get_user_data'.");
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
$stmt->bindValue(":uuid", $uuid);
|
||||
$user = $stmt->execute()->fetchArray(SQLITE3_ASSOC);
|
||||
$result = $stmt->execute();
|
||||
if ($result === false) {
|
||||
$db->close();
|
||||
$this->logger->error("Failed to execute query in 'get_user_data'.");
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
$user = $result->fetchArray(SQLITE3_ASSOC);
|
||||
$db->close();
|
||||
|
||||
return $user === false
|
||||
|
@ -192,8 +237,21 @@ class UserManager
|
|||
|
||||
// Check if email address is already in use
|
||||
$stmt = $db->prepare("SELECT 1 FROM users WHERE email=:email;");
|
||||
if ($stmt === false) {
|
||||
$db->exec("COMMIT;");
|
||||
$db->close();
|
||||
$this->logger->error("Failed to prepare query in 'set_email'.");
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
$stmt->bindValue(":email", $email);
|
||||
$email_available = $stmt->execute()->fetchArray(SQLITE3_ASSOC) === false;
|
||||
$result = $stmt->execute();
|
||||
if ($result === false) {
|
||||
$db->exec("COMMIT;");
|
||||
$db->close();
|
||||
$this->logger->error("Failed to execute query in 'set_email'.");
|
||||
return new Response("Email address already in use.", false);
|
||||
}
|
||||
$email_available = $result->fetchArray(SQLITE3_ASSOC) === false;
|
||||
if (!$email_available) {
|
||||
$db->exec("COMMIT;");
|
||||
$db->close();
|
||||
|
@ -202,6 +260,12 @@ class UserManager
|
|||
|
||||
// Update email address
|
||||
$stmt = $db->prepare("UPDATE users SET email=:email WHERE uuid=:uuid;");
|
||||
if ($stmt === false) {
|
||||
$db->exec("COMMIT;");
|
||||
$db->close();
|
||||
$this->logger->error("Failed to prepare query in 'set_email'.");
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
$stmt->bindValue(":uuid", $uuid);
|
||||
$stmt->bindValue(":email", $email);
|
||||
$email_updated = $stmt->execute() !== false;
|
||||
|
@ -248,8 +312,21 @@ class UserManager
|
|||
|
||||
// Validate old password
|
||||
$stmt = $db->prepare("SELECT * FROM users WHERE uuid=:uuid;");
|
||||
if ($stmt === false) {
|
||||
$db->exec("COMMIT;");
|
||||
$db->close();
|
||||
$this->logger->error("Failed to prepare query in 'set_password'.");
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
$stmt->bindValue(":uuid", $uuid);
|
||||
$user = $stmt->execute()->fetchArray(SQLITE3_ASSOC);
|
||||
$result = $stmt->execute();
|
||||
if ($result === false) {
|
||||
$db->exec("COMMIT;");
|
||||
$db->close();
|
||||
$this->logger->error("Failed to execute query in 'set_password'.");
|
||||
return new Response("Invalid user.", false);
|
||||
}
|
||||
$user = $result->fetchArray(SQLITE3_ASSOC);
|
||||
if ($user === false) {
|
||||
$db->exec("COMMIT;");
|
||||
$db->close();
|
||||
|
@ -263,6 +340,12 @@ class UserManager
|
|||
|
||||
// Update password
|
||||
$stmt = $db->prepare("UPDATE users SET password=:password WHERE uuid=:uuid;");
|
||||
if ($stmt === false) {
|
||||
$db->exec("COMMIT;");
|
||||
$db->close();
|
||||
$this->logger->error("Failed to prepare query in 'set_password'.");
|
||||
return new Response("Unexpected database error.", false);
|
||||
}
|
||||
$stmt->bindValue(":uuid", $uuid);
|
||||
$stmt->bindValue(":password", password_hash($password_new, PASSWORD_DEFAULT));
|
||||
$password_updated = $stmt->execute() !== false;
|
||||
|
|
Loading…
Reference in New Issue