Add error handling for external dependencies. (#55)
Protect functions that interact with databases and filesystems. Reviewed-on: https://gitea.subcultureofone.org/greg/tkr/pulls/55 Co-authored-by: Greg Sarjeant <greg@subcultureofone.org> Co-committed-by: Greg Sarjeant <greg@subcultureofone.org>
This commit is contained in:
parent
51abf33ad1
commit
d9d0ed9571
@ -34,16 +34,26 @@ $db = $prerequisites->getDatabase();
|
|||||||
|
|
||||||
// Make sure the initial setup is complete unless we're already heading to setup
|
// Make sure the initial setup is complete unless we're already heading to setup
|
||||||
if (!(preg_match('/setup$/', $path))) {
|
if (!(preg_match('/setup$/', $path))) {
|
||||||
// Make sure required tables (user, settings) are populated
|
try {
|
||||||
$user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn();
|
// Make sure required tables (user, settings) are populated
|
||||||
$settings_count = (int) $db->query("SELECT COUNT(*) FROM settings")->fetchColumn();
|
$user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn();
|
||||||
|
$settings_count = (int) $db->query("SELECT COUNT(*) FROM settings")->fetchColumn();
|
||||||
|
|
||||||
// If either required table has no records, redirect to setup.
|
// If either required table has no records, redirect to setup.
|
||||||
if ($user_count === 0 || $settings_count === 0){
|
if ($user_count === 0 || $settings_count === 0){
|
||||||
$init = require APP_ROOT . '/config/init.php';
|
$init = require APP_ROOT . '/config/init.php';
|
||||||
header('Location: ' . $init['base_path'] . 'setup');
|
header('Location: ' . $init['base_path'] . 'setup');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Database error during setup validation - show error page
|
||||||
|
error_log("Database error during setup validation: " . $e->getMessage());
|
||||||
|
http_response_code(500);
|
||||||
|
echo "<h1>Database Error</h1>";
|
||||||
|
echo "<p>Cannot validate setup status. The database may be corrupted or locked.</p>";
|
||||||
|
echo "<p>Please check your installation or contact your hosting provider.</p>";
|
||||||
exit;
|
exit;
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -83,7 +93,7 @@ if ($method === 'POST' && $path != 'setup') {
|
|||||||
if (!Session::isValid($_POST['csrf_token'])) {
|
if (!Session::isValid($_POST['csrf_token'])) {
|
||||||
// Invalid session - redirect to /login
|
// Invalid session - redirect to /login
|
||||||
Log::info('Attempt to POST with invalid session. Redirecting to login.');
|
Log::info('Attempt to POST with invalid session. Redirecting to login.');
|
||||||
header('Location: ' . Util::buildRelativeUrl($app->config->basePath, 'login'));
|
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath, 'login'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -112,29 +112,35 @@ class CssController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the theme back to default
|
// Set the theme back to default
|
||||||
$app['config']->cssId = null;
|
try {
|
||||||
$app['config'] = $app['config']->save();
|
$app['config']->cssId = null;
|
||||||
|
$app['config'] = $app['config']->save();
|
||||||
// Set flash message
|
Session::setFlashMessage('success', 'Theme ' . $cssFilename . ' deleted.');
|
||||||
Session::setFlashMessage('success', 'Theme ' . $cssFilename . ' deleted.');
|
} catch (Exception $e) {
|
||||||
|
Log::error("Failed to update config after deleting theme: " . $e->getMessage());
|
||||||
|
Session::setFlashMessage('error', 'Theme deleted but failed to update settings');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleSetTheme() {
|
private function handleSetTheme() {
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
if ($_POST['selectCssFile']){
|
try {
|
||||||
// Set custom theme
|
if ($_POST['selectCssFile']){
|
||||||
$app['config']->cssId = $_POST['selectCssFile'];
|
// Set custom theme
|
||||||
} else {
|
$app['config']->cssId = $_POST['selectCssFile'];
|
||||||
// Set default theme
|
} else {
|
||||||
$app['config']->cssId = null;
|
// Set default theme
|
||||||
|
$app['config']->cssId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the site theme
|
||||||
|
$app['config'] = $app['config']->save();
|
||||||
|
Session::setFlashMessage('success', 'Theme applied.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::error("Failed to save theme setting: " . $e->getMessage());
|
||||||
|
Session::setFlashMessage('error', 'Failed to apply theme');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the site theme
|
|
||||||
$app['config'] = $app['config']->save();
|
|
||||||
|
|
||||||
// Set flash message
|
|
||||||
Session::setFlashMessage('success', 'Theme applied.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleUpload() {
|
private function handleUpload() {
|
||||||
|
@ -5,10 +5,15 @@ class FeedController extends Controller {
|
|||||||
public function __construct() {
|
public function __construct() {
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
$tickModel = new TickModel($app['db'], $app['config']);
|
try {
|
||||||
$this->ticks = $tickModel->getPage($app['config']->itemsPerPage);
|
$tickModel = new TickModel($app['db'], $app['config']);
|
||||||
|
$this->ticks = $tickModel->getPage($app['config']->itemsPerPage);
|
||||||
Log::debug("Loaded " . count($this->ticks) . " ticks for feeds");
|
Log::debug("Loaded " . count($this->ticks) . " ticks for feeds");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::error("Failed to load ticks for feed: " . $e->getMessage());
|
||||||
|
// Provide empty feed rather than crashing - RSS readers can handle this
|
||||||
|
$this->ticks = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rss(){
|
public function rss(){
|
||||||
|
@ -19,7 +19,12 @@ class Log {
|
|||||||
// (should be handled by Prerequisites, but doesn't hurt)
|
// (should be handled by Prerequisites, but doesn't hurt)
|
||||||
$logDir = dirname(self::$logFile);
|
$logDir = dirname(self::$logFile);
|
||||||
if (!is_dir($logDir)) {
|
if (!is_dir($logDir)) {
|
||||||
mkdir($logDir, 0770, true);
|
try {
|
||||||
|
mkdir($logDir, 0770, true);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Fall back to error_log if we can't create log directory
|
||||||
|
error_log("Failed to create log directory {$logDir}: " . $e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,35 +66,45 @@ class Log {
|
|||||||
$logEntry = "[{$timestamp}] {$level}: " . Util::getClientIp() . "{$context} - {$message}\n";
|
$logEntry = "[{$timestamp}] {$level}: " . Util::getClientIp() . "{$context} - {$message}\n";
|
||||||
|
|
||||||
// Rotate if we're at the max file size (1000 lines)
|
// Rotate if we're at the max file size (1000 lines)
|
||||||
if (file_exists(self::$logFile)) {
|
try {
|
||||||
$lineCount = count(file(self::$logFile));
|
if (file_exists(self::$logFile)) {
|
||||||
if ($lineCount >= self::$maxLines) {
|
$lineCount = count(file(self::$logFile));
|
||||||
self::rotate();
|
if ($lineCount >= self::$maxLines) {
|
||||||
Log::info("Log rotated at {$timestamp}");
|
self::rotate();
|
||||||
|
Log::info("Log rotated at {$timestamp}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents(self::$logFile, $logEntry, FILE_APPEND | LOCK_EX);
|
file_put_contents(self::$logFile, $logEntry, FILE_APPEND | LOCK_EX);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Fall back to error_log if file operations fail
|
||||||
|
error_log("Log write failed: " . $e->getMessage() . " - Original message: " . trim($logEntry));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function rotate() {
|
private static function rotate() {
|
||||||
// Rotate existing history files: tkr.4.log -> tkr.5.log, etc.
|
try {
|
||||||
for ($i = self::$maxFiles - 1; $i >= 1; $i--) {
|
// Rotate existing history files: tkr.4.log -> tkr.5.log, etc.
|
||||||
$oldFile = self::$logFile . '.' . $i;
|
for ($i = self::$maxFiles - 1; $i >= 1; $i--) {
|
||||||
$newFile = self::$logFile . '.' . ($i + 1);
|
$oldFile = self::$logFile . '.' . $i;
|
||||||
|
$newFile = self::$logFile . '.' . ($i + 1);
|
||||||
|
|
||||||
if (file_exists($oldFile)) {
|
if (file_exists($oldFile)) {
|
||||||
if ($i == self::$maxFiles - 1) {
|
if ($i == self::$maxFiles - 1) {
|
||||||
unlink($oldFile); // Delete oldest log if we already have 5 files of history
|
unlink($oldFile); // Delete oldest log if we already have 5 files of history
|
||||||
} else {
|
} else {
|
||||||
rename($oldFile, $newFile); // Bump the file number up by one
|
rename($oldFile, $newFile); // Bump the file number up by one
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Move current active log to .1
|
// Move current active log to .1
|
||||||
if (file_exists(self::$logFile)) {
|
if (file_exists(self::$logFile)) {
|
||||||
rename(self::$logFile, self::$logFile . '.1');
|
rename(self::$logFile, self::$logFile . '.1');
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Log rotation failure - not critical, just continue
|
||||||
|
error_log("Log rotation failed: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user