Auto commit: 2025-12-19T15:08:47.391Z
This commit is contained in:
parent
30eacc0737
commit
40a0d866cd
28
admin/auth.php
Normal file
28
admin/auth.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
$admin_timeout = 3600; // 1 hour
|
||||
|
||||
// Check if user is logged in and is an admin
|
||||
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'admin') {
|
||||
header('Location: ../login.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Session timeout logic
|
||||
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > $admin_timeout)) {
|
||||
session_unset();
|
||||
session_destroy();
|
||||
header('Location: ../login.php?timeout');
|
||||
exit();
|
||||
}
|
||||
|
||||
// IP binding logic
|
||||
if (isset($_SESSION['admin_ip_address']) && $_SESSION['admin_ip_address'] !== $_SERVER['REMOTE_ADDR']) {
|
||||
session_unset();
|
||||
session_destroy();
|
||||
header('Location: ../login.php?ip_changed');
|
||||
exit();
|
||||
}
|
||||
|
||||
$_SESSION['last_activity'] = time(); // Update last activity time
|
||||
@ -1,10 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'admin') {
|
||||
header("Location: ../login.php");
|
||||
exit;
|
||||
}
|
||||
require_once __DIR__ . '/auth.php';
|
||||
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
@ -78,6 +73,7 @@ while ($row = $stmt->fetch()) {
|
||||
<a class="nav-link active" href="categories.php">Categories</a>
|
||||
<a class="nav-link" href="users.php">Users</a>
|
||||
<a class="nav-link" href="links.php">Links</a>
|
||||
<a class="nav-link" href="settings.php">Settings</a>
|
||||
</nav>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
@ -1,10 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'admin') {
|
||||
header("Location: ../login.php");
|
||||
exit;
|
||||
}
|
||||
require_once __DIR__ . '/auth.php';
|
||||
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
@ -44,6 +39,7 @@ $category_count = $pdo->query("SELECT count(*) FROM categories")->fetchColumn();
|
||||
<a class="nav-link" href="categories.php">Categories</a>
|
||||
<a class="nav-link" href="users.php">Users</a>
|
||||
<a class="nav-link" href="links.php">Links</a>
|
||||
<a class="nav-link" href="settings.php">Settings</a>
|
||||
</nav>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
@ -1,10 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'admin') {
|
||||
header("Location: ../login.php");
|
||||
exit;
|
||||
}
|
||||
require_once __DIR__ . '/auth.php';
|
||||
|
||||
// Debug block for POST data - visible only to admin
|
||||
|
||||
@ -184,6 +179,7 @@ $subcategories = $pdo->query("SELECT sc.id, sc.name AS subcategory_name, c.name
|
||||
<a class="nav-link" href="categories.php">Categories</a>
|
||||
<a class="nav-link" href="users.php">Users</a>
|
||||
<a class="nav-link active" href="links.php">Links</a>
|
||||
<a class="nav-link" href="settings.php">Settings</a>
|
||||
</nav>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
154
admin/settings.php
Normal file
154
admin/settings.php
Normal file
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/auth.php';
|
||||
require_once __DIR__ . '/../includes/Settings.php';
|
||||
|
||||
$allSettings = Settings::getAllWithMetadata();
|
||||
|
||||
// Handle form submission to update settings
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// CSRF Protection (consider adding this if not already in auth.php or a global handler)
|
||||
// if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||
// // Handle CSRF token mismatch
|
||||
// $_SESSION['error_message'] = 'CSRF token validation failed.';
|
||||
// header('Location: settings.php');
|
||||
// exit;
|
||||
// }
|
||||
|
||||
foreach ($allSettings as $setting) {
|
||||
$key = $setting['setting_key'];
|
||||
$newValue = $_POST[$key] ?? null;
|
||||
|
||||
// Special handling for checkboxes: if not present in POST, it means unchecked
|
||||
if ($setting['setting_type'] === 'checkbox') {
|
||||
$newValue = isset($_POST[$key]) ? '1' : '0';
|
||||
}
|
||||
|
||||
// Update the setting using the Settings class
|
||||
Settings::set(
|
||||
$key,
|
||||
$newValue,
|
||||
$setting['setting_type'],
|
||||
$setting['default_value'],
|
||||
$setting['validation_rules'],
|
||||
$setting['description']
|
||||
);
|
||||
}
|
||||
Settings::clearCache(); // Clear cache after updating settings
|
||||
$_SESSION['success_message'] = 'Settings updated successfully.';
|
||||
header('Location: settings.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Re-fetch settings after potential update
|
||||
$allSettings = Settings::getAllWithMetadata();
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Manage Settings - Admin Panel</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="../assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="header">
|
||||
<h1><a href="/" style="text-decoration: none; color: inherit;">Admin Panel</a></h1>
|
||||
<div class="auth-links">
|
||||
<a href="../index.php">View Site</a>
|
||||
<a href="../logout.php">Logout</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container my-4">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<aside class="admin-nav">
|
||||
<h3>Menu</h3>
|
||||
<nav class="nav flex-column">
|
||||
<a class="nav-link" href="index.php">Dashboard</a>
|
||||
<a class="nav-link" href="categories.php">Categories</a>
|
||||
<a class="nav-link" href="users.php">Users</a>
|
||||
<a class="nav-link" href="links.php">Links</a>
|
||||
<a class="nav-link active" href="settings.php">Settings</a>
|
||||
</nav>
|
||||
</aside>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<main class="content">
|
||||
<h2>Manage Settings</h2>
|
||||
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="alert alert-success">
|
||||
<?php echo $_SESSION['success_message']; unset($_SESSION['success_message']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($_SESSION['error_message'])): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?php echo $_SESSION['error_message']; unset($_SESSION['error_message']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="settings.php" method="POST">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?? ''; // Assuming CSRF token is available globally or generated here ?>">
|
||||
<?php foreach ($allSettings as $setting): ?>
|
||||
<div class="mb-3">
|
||||
<label for="<?php echo htmlspecialchars($setting['setting_key']); ?>" class="form-label">
|
||||
<?php echo htmlspecialchars($setting['description'] ?: ucwords(str_replace('_', ' ', $setting['setting_key']))); ?>
|
||||
</label>
|
||||
<?php
|
||||
$currentValue = Settings::get($setting['setting_key'], $setting['default_value']);
|
||||
switch ($setting['setting_type']) {
|
||||
case 'text':
|
||||
case 'string':
|
||||
echo '<input type="text" class="form-control" id="' . htmlspecialchars($setting['setting_key']) . '" name="' . htmlspecialchars($setting['setting_key']) . '" value="' . htmlspecialchars($currentValue) . '">';
|
||||
break;
|
||||
case 'number':
|
||||
case 'integer':
|
||||
echo '<input type="number" class="form-control" id="' . htmlspecialchars($setting['setting_key']) . '" name="' . htmlspecialchars($setting['setting_key']) . '" value="' . htmlspecialchars($currentValue) . '">';
|
||||
break;
|
||||
case 'textarea':
|
||||
echo '<textarea class="form-control" id="' . htmlspecialchars($setting['setting_key']) . '" name="' . htmlspecialchars($setting['setting_key']) . '" rows="3">' . htmlspecialchars($currentValue) . '</textarea>';
|
||||
break;
|
||||
case 'checkbox':
|
||||
case 'boolean':
|
||||
echo '<div class="form-check form-switch">';
|
||||
echo '<input class="form-check-input" type="checkbox" id="' . htmlspecialchars($setting['setting_key']) . '" name="' . htmlspecialchars($setting['setting_key']) . '" value="1" ' . ($currentValue ? 'checked' : '') . '>';
|
||||
echo '<label class="form-check-label" for="' . htmlspecialchars($setting['setting_key']) . '"></label>';
|
||||
echo '</div>';
|
||||
break;
|
||||
case 'dropdown':
|
||||
case 'select':
|
||||
$options = json_decode($setting['validation_rules'], true)['options'] ?? []; // Assuming options are stored in validation_rules as JSON
|
||||
echo '<select class="form-select" id="' . htmlspecialchars($setting['setting_key']) . '" name="' . htmlspecialchars($setting['setting_key']) . '>';
|
||||
foreach ($options as $optionValue => $optionLabel) {
|
||||
echo '<option value="' . htmlspecialchars($optionValue) . '" ' . ($currentValue == $optionValue ? 'selected' : '') . '>' . htmlspecialchars($optionLabel) . '</option>';
|
||||
}
|
||||
echo '</select>';
|
||||
break;
|
||||
// Add more types as needed (e.g., color, date, email)
|
||||
default:
|
||||
echo '<input type="text" class="form-control" id="' . htmlspecialchars($setting['setting_key']) . '" name="' . htmlspecialchars($setting['setting_key']) . '" value="' . htmlspecialchars($currentValue) . '">';
|
||||
break;
|
||||
}
|
||||
?>
|
||||
<?php if (!empty($setting['description'])): ?>
|
||||
<div class="form-text"><?php echo htmlspecialchars($setting['description']); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<button type="submit" class="btn btn-primary">Save Settings</button>
|
||||
</form>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<p>© <?php echo date("Y"); ?> <?php echo htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'Web Directory'); ?>. All Rights Reserved.</p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -1,10 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'admin') {
|
||||
header("Location: ../login.php");
|
||||
exit;
|
||||
}
|
||||
require_once __DIR__ . '/auth.php';
|
||||
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
@ -54,6 +49,7 @@ $users = $pdo->query("SELECT id, username, role, created_at FROM users ORDER BY
|
||||
<a class="nav-link" href="categories.php">Categories</a>
|
||||
<a class="nav-link active" href="users.php">Users</a>
|
||||
<a class="nav-link" href="links.php">Links</a>
|
||||
<a class="nav-link" href="settings.php">Settings</a>
|
||||
</nav>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
13
db/migrations/003_create_settings_table.sql
Normal file
13
db/migrations/003_create_settings_table.sql
Normal file
@ -0,0 +1,13 @@
|
||||
-- db/migrations/003_create_settings_table.sql
|
||||
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
setting_key VARCHAR(255) NOT NULL UNIQUE,
|
||||
setting_value TEXT,
|
||||
setting_type VARCHAR(50) NOT NULL DEFAULT 'text',
|
||||
default_value TEXT NULL,
|
||||
validation_rules TEXT NULL,
|
||||
description TEXT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
10
db/migrations/004_add_initial_settings.sql
Normal file
10
db/migrations/004_add_initial_settings.sql
Normal file
@ -0,0 +1,10 @@
|
||||
-- db/migrations/004_add_initial_settings.sql
|
||||
|
||||
-- Insert initial settings if they do not already exist
|
||||
INSERT IGNORE INTO settings (setting_key, setting_value, setting_type, default_value, description)
|
||||
VALUES
|
||||
('site_title', 'My Web Directory', 'text', 'My Web Directory', 'The main title of the website.'),
|
||||
('maintenance_mode', '0', 'checkbox', '0', 'Enable or disable maintenance mode for the site.'),
|
||||
('items_per_page', '10', 'number', '10', 'Number of items to display per page in listings.'),
|
||||
('admin_email', 'admin@example.com', 'text', 'admin@example.com', 'The email address for administrative notifications.'),
|
||||
('tar_pit_delay', '0', 'number', '0', 'Delay (in seconds) for tar pit defense against brute-force attacks.');
|
||||
178
includes/Settings.php
Normal file
178
includes/Settings.php
Normal file
@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
class Settings
|
||||
{
|
||||
private static $cache = [];
|
||||
|
||||
/**
|
||||
* Get a setting value.
|
||||
*
|
||||
* @param string $key The setting key.
|
||||
* @param mixed $default The default value to return if the setting is not found.
|
||||
* @return mixed The setting value or the default value.
|
||||
*/
|
||||
public static function get(string $key, $default = null)
|
||||
{
|
||||
if (isset(self::$cache[$key])) {
|
||||
return self::$cache[$key];
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT setting_value, setting_type FROM settings WHERE setting_key = ?");
|
||||
$stmt->execute([$key]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
$value = self::castValue($row['setting_value'], $row['setting_type']);
|
||||
self::$cache[$key] = $value;
|
||||
return $value;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error getting setting '{$key}': " . $e->getMessage());
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or update a setting value.
|
||||
*
|
||||
* @param string $key The setting key.
|
||||
* @param mixed $value The setting value.
|
||||
* @param string $type The type of the setting (e.g., 'text', 'checkbox', 'number').
|
||||
* @param mixed $defaultValue The default value for the setting.
|
||||
* @param string|null $validationRules JSON string of validation rules.
|
||||
* @param string|null $description A description for the setting.
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public static function set(
|
||||
string $key,
|
||||
$value,
|
||||
string $type = 'text',
|
||||
$defaultValue = null,
|
||||
?string $validationRules = null,
|
||||
?string $description = null
|
||||
): bool {
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Check if setting exists
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM settings WHERE setting_key = ?");
|
||||
$stmt->execute([$key]);
|
||||
$exists = $stmt->fetchColumn();
|
||||
|
||||
$stringValue = self::valueToString($value);
|
||||
$defaultValue = self::valueToString($defaultValue);
|
||||
|
||||
if ($exists) {
|
||||
$stmt = $pdo->prepare("UPDATE settings SET setting_value = ?, setting_type = ?, default_value = ?, validation_rules = ?, description = ?, updated_at = NOW() WHERE setting_key = ?");
|
||||
$stmt->execute([$stringValue, $type, $defaultValue, $validationRules, $description, $key]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value, setting_type, default_value, validation_rules, description) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$key, $stringValue, $type, $defaultValue, $validationRules, $description]);
|
||||
}
|
||||
|
||||
// Clear cache for this key
|
||||
unset(self::$cache[$key]);
|
||||
return true;
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error setting '{$key}': " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts a string value to its appropriate PHP type based on the setting type.
|
||||
*
|
||||
* @param string $value The string value from the database.
|
||||
* @param string $type The declared type of the setting.
|
||||
* @return mixed The type-casted value.
|
||||
*/
|
||||
private static function castValue(string $value, string $type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'boolean':
|
||||
case 'checkbox':
|
||||
return (bool)$value;
|
||||
case 'integer':
|
||||
case 'number':
|
||||
return (int)$value;
|
||||
case 'float':
|
||||
return (float)$value;
|
||||
case 'json':
|
||||
return json_decode($value, true);
|
||||
case 'array': // Simple comma-separated array
|
||||
return explode(',', $value);
|
||||
default:
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a PHP value to its string representation for storage.
|
||||
*
|
||||
* @param mixed $value The PHP value.
|
||||
* @return string The string representation.
|
||||
*/
|
||||
private static function valueToString($value): string
|
||||
{
|
||||
if (is_bool($value)) {
|
||||
return $value ? '1' : '0';
|
||||
} elseif (is_array($value) || is_object($value)) {
|
||||
return json_encode($value);
|
||||
} else {
|
||||
return (string)$value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the entire settings cache.
|
||||
*/
|
||||
public static function clearCache(): void
|
||||
{
|
||||
self::$cache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all settings from the database.
|
||||
*
|
||||
* @return array An associative array of all settings.
|
||||
*/
|
||||
public static function getAll(): array
|
||||
{
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT setting_key, setting_value, setting_type FROM settings");
|
||||
$allSettings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$result = [];
|
||||
foreach ($allSettings as $setting) {
|
||||
$result[$setting['setting_key']] = self::castValue($setting['setting_value'], $setting['setting_type']);
|
||||
}
|
||||
return $result;
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error getting all settings: " . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all settings with their metadata from the database.
|
||||
*
|
||||
* @return array An associative array of all settings including metadata.
|
||||
*/
|
||||
public static function getAllWithMetadata(): array
|
||||
{
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT * FROM settings");
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error getting all settings with metadata: " . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -31,6 +31,11 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
$_SESSION['user_role'] = $user['role'];
|
||||
// For admin panel security features
|
||||
if ($user['role'] === 'admin') {
|
||||
$_SESSION['admin_ip_address'] = $_SERVER['REMOTE_ADDR'];
|
||||
$_SESSION['last_activity'] = time();
|
||||
}
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
} else {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user