diff --git a/admin/auth.php b/admin/auth.php new file mode 100644 index 0000000..b61bbee --- /dev/null +++ b/admin/auth.php @@ -0,0 +1,28 @@ + $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 diff --git a/admin/categories.php b/admin/categories.php index 06d720a..c95ed30 100644 --- a/admin/categories.php +++ b/admin/categories.php @@ -1,10 +1,5 @@ fetch()) { Categories Users Links + Settings diff --git a/admin/index.php b/admin/index.php index 147462f..5b98ac8 100644 --- a/admin/index.php +++ b/admin/index.php @@ -1,10 +1,5 @@ query("SELECT count(*) FROM categories")->fetchColumn(); Categories Users Links + Settings diff --git a/admin/links.php b/admin/links.php index bf27a83..268982c 100644 --- a/admin/links.php +++ b/admin/links.php @@ -1,10 +1,5 @@ query("SELECT sc.id, sc.name AS subcategory_name, c.name Categories Users Links + Settings diff --git a/admin/settings.php b/admin/settings.php new file mode 100644 index 0000000..6f1dc96 --- /dev/null +++ b/admin/settings.php @@ -0,0 +1,154 @@ + + + + + + + Manage Settings - Admin Panel + + + + + +
+

Admin Panel

+ +
+ +
+
+
+ +
+
+
+

Manage Settings

+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + '; + break; + case 'number': + case 'integer': + echo ''; + break; + case 'textarea': + echo ''; + break; + case 'checkbox': + case 'boolean': + echo '
'; + echo ''; + echo ''; + echo '
'; + break; + case 'dropdown': + case 'select': + $options = json_decode($setting['validation_rules'], true)['options'] ?? []; // Assuming options are stored in validation_rules as JSON + echo ''; + break; + // Add more types as needed (e.g., color, date, email) + default: + echo ''; + break; + } + ?> + +
+ +
+ + +
+
+
+
+
+ + + + + diff --git a/admin/users.php b/admin/users.php index 24a5967..50902b9 100644 --- a/admin/users.php +++ b/admin/users.php @@ -1,10 +1,5 @@ query("SELECT id, username, role, created_at FROM users ORDER BY Categories Users Links + Settings diff --git a/db/migrations/003_create_settings_table.sql b/db/migrations/003_create_settings_table.sql new file mode 100644 index 0000000..a690510 --- /dev/null +++ b/db/migrations/003_create_settings_table.sql @@ -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 +); diff --git a/db/migrations/004_add_initial_settings.sql b/db/migrations/004_add_initial_settings.sql new file mode 100644 index 0000000..e5dd9e5 --- /dev/null +++ b/db/migrations/004_add_initial_settings.sql @@ -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.'); diff --git a/includes/Settings.php b/includes/Settings.php new file mode 100644 index 0000000..6ca168f --- /dev/null +++ b/includes/Settings.php @@ -0,0 +1,178 @@ +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 []; + } + } +} diff --git a/login.php b/login.php index 638e8a5..fd3a4f3 100644 --- a/login.php +++ b/login.php @@ -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 {