Add phpstan and resolve almost all issues

This commit is contained in:
Florine W. Dekker 2022-08-14 17:47:18 +02:00
parent c5531d21cf
commit 1f3257424a
Signed by: FWDekker
GPG Key ID: D3DCFAA8A4560BE0
9 changed files with 141 additions and 28 deletions

View File

@ -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": {

BIN
composer.lock generated

Binary file not shown.

View File

@ -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",

4
phpstan.neon Normal file
View File

@ -0,0 +1,4 @@
parameters:
excludePaths:
- src/main/config.default.ini.php
- src/main/config.ini.php

View File

@ -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"])) {

View File

@ -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
*/

View File

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

View File

@ -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))

View File

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