Compare commits

...

31 Commits

Author SHA1 Message Date
Flatlogic Bot
378cb2cb10 google login 2025-11-08 21:16:26 +00:00
Flatlogic Bot
eca44a4bc7 gestione ruoli e permessi 2025-11-08 20:43:02 +00:00
7c2c9d57f8 Edit edit-asset.php via Editor 2025-11-08 18:51:05 +00:00
Flatlogic Bot
baf8947a57 new func for assign to 2025-11-08 18:49:12 +00:00
Flatlogic Bot
17da95852f added session_start() 2025-11-08 17:43:12 +00:00
c583d9b74d Edit edit-asset.php via Editor 2025-11-08 17:42:35 +00:00
e9a819c843 Edit delete-user.php via Editor 2025-11-08 17:42:22 +00:00
464f82e7b5 Edit delete-asset.php via Editor 2025-11-08 17:42:12 +00:00
ed44714581 Edit add-asset.php via Editor 2025-11-08 17:41:53 +00:00
f253f90e1a Edit add-user.php via Editor 2025-11-08 17:39:56 +00:00
57bad094bb Edit edit-user.php via Editor 2025-11-08 17:37:59 +00:00
1a9ee8902b Edit settings.php via Editor 2025-11-08 17:33:55 +00:00
5f697b63ac Edit users.php via Editor 2025-11-08 17:02:27 +00:00
1a47db78cb Edit users.php via Editor 2025-11-08 17:01:33 +00:00
fd606fca99 Edit users.php via Editor 2025-11-08 17:00:58 +00:00
9d1e967d34 Edit users.php via Editor 2025-11-08 16:59:44 +00:00
77d4a0613d Edit users.php via Editor 2025-11-08 16:58:53 +00:00
Flatlogic Bot
da4cd43fc4 fix 16 2025-11-05 23:52:20 +00:00
Flatlogic Bot
732df2dbf0 fix 14 2025-11-05 23:42:15 +00:00
Flatlogic Bot
1fa3995736 fix 13 2025-11-05 23:41:00 +00:00
Flatlogic Bot
b95f5763dd fix 12 2025-11-05 23:36:37 +00:00
Flatlogic Bot
2497a13797 fix 11 2025-11-05 23:31:33 +00:00
Flatlogic Bot
28184556fb fix 10 2025-11-05 23:25:26 +00:00
Flatlogic Bot
27581cde41 fix 09 2025-11-05 23:24:03 +00:00
Flatlogic Bot
f083b9ac06 fix 07 2025-11-05 23:17:59 +00:00
Flatlogic Bot
017eca9945 fix login 04 2025-11-05 22:56:36 +00:00
Flatlogic Bot
d25aef3090 correzione login 03 2025-11-05 22:54:37 +00:00
Flatlogic Bot
49f989b22a correzione login 02 2025-11-05 22:52:40 +00:00
Flatlogic Bot
f4d677bb9d correzione login 2025-11-05 22:50:07 +00:00
Flatlogic Bot
5f68d7fe5b App creation completed 2025-11-05 22:26:48 +00:00
Flatlogic Bot
53eb27812c v1.1 2025-11-05 22:08:13 +00:00
51 changed files with 3773 additions and 150 deletions

224
add-asset.php Normal file
View File

@ -0,0 +1,224 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
if (!can($_SESSION['user_role_id'], 'asset', 'create')) {
header('Location: index.php?error=access_denied');
exit;
}
$allowed_fields_str = can($_SESSION['user_role_id'], 'asset', 'create');
$allowed_fields = ($allowed_fields_str === '*') ? ['name', 'status', 'location_id', 'manufacturer', 'model', 'purchase_date', 'category_id', 'assigned_to'] : explode(',', $allowed_fields_str);
$success_message = '';
$error_message = '';
$categories = [];
$locations = [];
$users = [];
try {
$pdo = db();
// Fetch categories for dropdown
$stmt = $pdo->query("SELECT id, name FROM categories ORDER BY name");
$categories = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch locations for dropdown
$stmt = $pdo->query("SELECT id, name FROM locations ORDER BY name");
$locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch users for dropdown
$stmt = $pdo->query("SELECT id, name FROM users ORDER BY name");
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = [];
$placeholders = [];
$columns = [];
// Generate new asset tag
try {
$pdo = db();
$stmt = $pdo->query("SELECT asset_tag FROM assets WHERE asset_tag LIKE 'ASSET-%' ORDER BY CAST(SUBSTRING(asset_tag, 7) AS UNSIGNED) DESC LIMIT 1");
$last_asset_tag = $stmt->fetchColumn();
if ($last_asset_tag) {
$last_number = (int) substr($last_asset_tag, 6);
$new_number = $last_number + 1;
} else {
$new_number = 1;
}
$new_asset_tag = 'ASSET-' . str_pad($new_number, 3, '0', STR_PAD_LEFT);
$data = [$new_asset_tag];
$columns = ['asset_tag'];
$placeholders = '?';
} catch (PDOException $e) {
$error_message = 'Error generating asset tag: ' . $e->getMessage();
}
if (empty($error_message)) {
foreach ($allowed_fields as $field) {
if (isset($_POST[$field])) {
$value = $_POST[$field];
if (($field === 'category_id' || $field === 'location_id' || $field === 'assigned_to') && $value === '') {
$value = null;
}
$data[] = $value;
$columns[] = $field;
}
}
if (count($data) <= 1) { // Only asset tag is present
$error_message = 'No data submitted.';
} else {
try {
$sql = sprintf("INSERT INTO assets (%s) VALUES (%s)", implode(', ', $columns), implode(', ', array_fill(0, count($columns), '?')));
$stmt = $pdo->prepare($sql);
$stmt->execute($data);
header("Location: index.php?success=asset_added");
exit;
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add New Asset - IC-Inventory</title>
<meta name="description" content="Add a new asset to the inventory.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css"/>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Add New Asset</h1>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
<div class="surface p-4">
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<form action="add-asset.php" method="post">
<div class="row">
<?php if (in_array('name', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="name" class="form-label">Asset Name*</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<?php endif; ?>
</div>
<div class="row">
<?php if (in_array('status', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="status" class="form-label">Status</label>
<select class="form-select" id="status" name="status">
<option>In Service</option>
<option>Under Repair</option>
<option>Retired</option>
</select>
</div>
<?php endif; ?>
<?php if (in_array('category_id', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="category_id" class="form-label">Category</label>
<select class="form-select" id="category_id" name="category_id">
<option value="">No Category</option>
<?php foreach ($categories as $category): ?>
<option value="<?php echo $category['id']; ?>">
<?php echo htmlspecialchars($category['name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<?php if (in_array('location_id', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="location_id" class="form-label">Location</label>
<select class="form-select" id="location_id" name="location_id">
<option value="">No Location</option>
<?php foreach ($locations as $location): ?>
<option value="<?php echo $location['id']; ?>">
<?php echo htmlspecialchars($location['name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<?php if (in_array('assigned_to', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="assigned_to" class="form-label">Assigned To</label>
<select class="form-select" id="assigned_to" name="assigned_to">
<option value="">Unassigned</option>
<?php foreach ($users as $user): ?>
<option value="<?php echo $user['id']; ?>">
<?php echo htmlspecialchars($user['name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
</div>
<div class="row">
<?php if (in_array('manufacturer', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="manufacturer" class="form-label">Manufacturer</label>
<input type="text" class="form-control" id="manufacturer" name="manufacturer">
</div>
<?php endif; ?>
<?php if (in_array('model', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="model" class="form-label">Model</label>
<input type="text" class="form-control" id="model" name="model">
</div>
<?php endif; ?>
</div>
<?php if (in_array('purchase_date', $allowed_fields)): ?>
<div class="mb-3">
<label for="purchase_date" class="form-label">Purchase Date*</label>
<input type="date" class="form-control" id="purchase_date" name="purchase_date" required>
</div>
<?php endif; ?>
<button type="submit" class="btn btn-primary">Add Asset</button>
<a href="index.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
<script src="assets/js/choices.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

94
add-category.php Normal file
View File

@ -0,0 +1,94 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
if (!can($_SESSION['user_role_id'], 'category', 'create')) {
header('Location: index.php?error=access_denied');
exit;
}
$error_message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
if (empty($name)) {
$error_message = 'Please fill in the category name.';
} else {
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT id FROM categories WHERE name = ?');
$stmt->execute([$name]);
if ($stmt->fetch()) {
$error_message = 'A category with this name already exists.';
} else {
$sql = "INSERT INTO categories (name) VALUES (?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name]);
header("Location: categories.php?success=category_added");
exit;
}
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add New Category - IC-Inventory</title>
<meta name="description" content="Add a new category.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Add New Category</h1>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
<div class="surface p-4">
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<form action="add-category.php" method="post">
<div class="row">
<div class="col-md-6 mb-3">
<label for="name" class="form-label">Category Name*</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
</div>
<button type="submit" class="btn btn-primary">Add Category</button>
<a href="categories.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

94
add-location.php Normal file
View File

@ -0,0 +1,94 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
if (!can($_SESSION['user_role_id'], 'location', 'create')) {
header('Location: index.php?error=access_denied');
exit;
}
$error_message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
if (empty($name)) {
$error_message = 'Please fill in the location name.';
} else {
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT id FROM locations WHERE name = ?');
$stmt->execute([$name]);
if ($stmt->fetch()) {
$error_message = 'A location with this name already exists.';
} else {
$sql = "INSERT INTO locations (name) VALUES (?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name]);
header("Location: locations.php?success=location_added");
exit;
}
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add New Location - IC-Inventory</title>
<meta name="description" content="Add a new location.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Add New Location</h1>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
<div class="surface p-4">
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<form action="add-location.php" method="post">
<div class="row">
<div class="col-md-6 mb-3">
<label for="name" class="form-label">Location Name*</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
</div>
<button type="submit" class="btn btn-primary">Add Location</button>
<a href="locations.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

94
add-role.php Normal file
View File

@ -0,0 +1,94 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
if (!can($_SESSION['user_role_id'], 'role', 'create')) {
header('Location: index.php?error=access_denied');
exit;
}
$error_message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
if (empty($name)) {
$error_message = 'Please fill in the role name.';
} else {
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT id FROM roles WHERE name = ?');
$stmt->execute([$name]);
if ($stmt->fetch()) {
$error_message = 'A role with this name already exists.';
} else {
$sql = "INSERT INTO roles (name) VALUES (?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name]);
header("Location: roles.php?success=role_added");
exit;
}
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add New Role - IC-Inventory</title>
<meta name="description" content="Add a new role.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Add New Role</h1>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
<div class="surface p-4">
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<form action="add-role.php" method="post">
<div class="row">
<div class="col-md-6 mb-3">
<label for="name" class="form-label">Role Name*</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
</div>
<button type="submit" class="btn btn-primary">Add Role</button>
<a href="roles.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

137
add-user.php Normal file
View File

@ -0,0 +1,137 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
if (!can($_SESSION['user_role_id'], 'user', 'create')) {
header('Location: index.php?error=access_denied');
exit;
}
$error_message = '';
$roles = [];
try {
$pdo = db();
$stmt = $pdo->query('SELECT id, name FROM roles ORDER BY name');
$roles = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($roles === false) {
throw new Exception("Failed to fetch roles from the database.");
}
} catch (PDOException $e) {
error_log('PDO Error in add-user.php: ' . $e->getMessage());
die("Error: A database error occurred while trying to fetch roles. Please check the logs. Message: " . $e->getMessage());
} catch (Exception $e) {
die("Error: " . $e->getMessage());
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
$role_id = $_POST['role_id'] ?? null;
if (empty($name) || empty($email) || empty($password) || empty($role_id)) {
$error_message = 'Please fill in all required fields.';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$error_message = 'Invalid email format.';
} else {
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT id FROM users WHERE email = ?');
$stmt->execute([$email]);
if ($stmt->fetch()) {
$error_message = 'A user with this email address already exists.';
} else {
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$sql = "INSERT INTO users (name, email, password, role_id) VALUES (?, ?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name, $email, $hashed_password, $role_id]);
header("Location: users.php?success=user_added");
exit;
}
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add New User - IC-Inventory</title>
<meta name="description" content="Add a new user.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Add New User</h1>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
<div class="surface p-4">
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<form action="add-user.php" method="post">
<div class="row">
<div class="col-md-6 mb-3">
<label for="name" class="form-label">Full Name*</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="col-md-6 mb-3">
<label for="email" class="form-label">Email Address*</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="password" class="form-label">Password*</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="col-md-6 mb-3">
<label for="role_id" class="form-label">Role</label>
<select class="form-select" id="role_id" name="role_id" required>
<option value="">Select a role</option>
<?php foreach ($roles as $role): ?>
<option value="<?php echo htmlspecialchars($role['id']); ?>">
<?php echo htmlspecialchars($role['name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<button type="submit" class="btn btn-primary">Add User</button>
<a href="users.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

125
assets/css/custom.css Normal file
View File

@ -0,0 +1,125 @@
/* General Styles */
:root {
--primary-color: #4A90E2;
--secondary-color: #50E3C2;
--light-bg: #F7F9FC;
--light-surface: #FFFFFF;
--light-text: #333333;
--dark-bg: #121212;
--dark-surface: #1E1E1E;
--dark-text: #E0E0E0;
--border-radius: 8px;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
transition: background-color 0.3s, color 0.3s;
}
/* Light Mode */
body {
background-color: var(--light-bg);
color: var(--light-text);
}
/* Dark Mode */
body.dark-mode {
background-color: var(--dark-bg);
color: var(--dark-text);
}
.surface {
background-color: var(--light-surface);
border-radius: var(--border-radius);
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
transition: background-color 0.3s;
}
body.dark-mode .surface {
background-color: var(--dark-surface);
box-shadow: 0 4px 6px rgba(0,0,0,0.2);
}
/* Layout */
.wrapper {
display: flex;
min-height: 100vh;
}
#sidebar {
width: 250px;
background-color: var(--light-surface);
transition: background-color 0.3s;
padding: 20px;
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
}
body.dark-mode #sidebar {
background-color: var(--dark-surface);
box-shadow: 2px 0 5px rgba(0,0,0,0.2);
}
#content {
flex-grow: 1;
padding: 2rem;
}
/* Header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
/* Theme Switcher */
.theme-switcher {
cursor: pointer;
}
/* Asset Table */
.asset-table {
width: 100%;
border-collapse: collapse;
}
.asset-table th, .asset-table td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #ddd;
}
body.dark-mode .asset-table th, body.dark-mode .asset-table td {
border-bottom: 1px solid #444;
}
.asset-table th {
background-color: var(--light-bg);
}
body.dark-mode .asset-table th {
background-color: var(--dark-bg);
}
.status {
padding: 5px 10px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: bold;
}
.status-in-service { background-color: #d4edda; color: #155724; }
.status-under-repair { background-color: #fff3cd; color: #856404; }
.status-retired { background-color: #f8d7da; color: #721c24; }
/* Sidebar */
#sidebar .nav-link {
color: var(--light-text);
}
body.dark-mode #sidebar .nav-link {
color: var(--dark-text);
}
#sidebar .nav-link.active {
color: var(--primary-color);
font-weight: bold;
}

22
assets/js/choices.js Normal file
View File

@ -0,0 +1,22 @@
document.addEventListener('DOMContentLoaded', function() {
const assignedTo = document.getElementById('assigned_to');
if (assignedTo) {
new Choices(assignedTo, {
removeItemButton: true,
});
}
const category = document.getElementById('category_id');
if (category) {
new Choices(category, {
removeItemButton: true,
});
}
const location = document.getElementById('location_id');
if (location) {
new Choices(location, {
removeItemButton: true,
});
}
});

17
assets/js/main.js Normal file
View File

@ -0,0 +1,17 @@
document.addEventListener('DOMContentLoaded', () => {
const themeSwitcher = document.getElementById('theme-switcher');
const currentTheme = localStorage.getItem('theme');
if (currentTheme === 'dark') {
document.body.classList.add('dark-mode');
}
themeSwitcher.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
let theme = 'light';
if (document.body.classList.contains('dark-mode')) {
theme = 'dark';
}
localStorage.setItem('theme', theme);
});
});

7
auth-check.php Normal file
View File

@ -0,0 +1,7 @@
<?php
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit;
}
?>

30
auth-helpers.php Normal file
View File

@ -0,0 +1,30 @@
<?php
require_once 'db/config.php';
function can($role_id, $resource, $action) {
static $permissions = null;
if ($permissions === null) {
try {
$pdo = db();
$stmt = $pdo->query('SELECT * FROM role_permissions');
$all_permissions = $stmt->fetchAll(PDO::FETCH_ASSOC);
$permissions = [];
foreach ($all_permissions as $p) {
$permissions[$p['role_id']][$p['resource']][$p['action']] = $p['fields'] ?? '*';
}
} catch (PDOException $e) {
// Handle database errors, maybe return false or log the error
return false;
}
}
if (isset($permissions[$role_id][$resource][$action])) {
if (in_array($action, ['read', 'update', 'create'])) {
return $permissions[$role_id][$resource][$action];
}
return true;
}
return false;
}

118
categories.php Normal file
View File

@ -0,0 +1,118 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
// Permissions check
if (!can($_SESSION['user_role_id'], 'category', 'read')) {
header('Location: index.php?error=access_denied');
exit;
}
function get_categories() {
try {
$pdo = db();
$stmt = $pdo->query("SELECT * FROM categories ORDER BY name ASC");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
return ['error' => 'Database error: ' . $e->getMessage()];
}
}
$categories = get_categories();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Category Management - IC-Inventory</title>
<meta name="description" content="Category management for IC-Inventory.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Category Management</h1>
<div>
<?php if (can($_SESSION['user_role_id'], 'category', 'create')): ?>
<a href="add-category.php" class="btn btn-primary">Add New Category</a>
<?php endif; ?>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
</div>
<?php if (isset($_GET['success'])): ?>
<div class="alert alert-success">
<?php
if ($_GET['success'] === 'category_added') echo 'Category successfully added!';
if ($_GET['success'] === 'category_updated') echo 'Category successfully updated!';
if ($_GET['success'] === 'category_deleted') echo 'Category successfully deleted!';
?>
</div>
<?php endif; ?>
<div class="surface p-4">
<?php if (isset($categories['error'])): ?>
<div class="alert alert-danger">
<?php echo htmlspecialchars($categories['error']); ?>
</div>
<?php elseif (empty($categories)): ?>
<div class="text-center p-5">
<h4>No categories found.</h4>
<?php if (can($_SESSION['user_role_id'], 'category', 'create')): ?>
<p>Get started by adding your first category.</p>
<a href="add-category.php" class="btn btn-primary">Add Category</a>
<?php endif; ?>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($categories as $category): ?>
<tr>
<td><?php echo htmlspecialchars($category['name']); ?></td>
<td>
<?php if (can($_SESSION['user_role_id'], 'category', 'update')): ?>
<a href="edit-category.php?id=<?php echo $category['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a>
<?php endif; ?>
<?php if (can($_SESSION['user_role_id'], 'category', 'delete')): ?>
<a href="delete-category.php?id=<?php echo $category['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to delete this category?');">Delete</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

5
cookie.txt Normal file
View File

@ -0,0 +1,5 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
localhost FALSE / FALSE 0 PHPSESSID m0fvo87s169e7d06irv43qqhfn

View File

@ -6,12 +6,22 @@ define('DB_USER', 'app_31009');
define('DB_PASS', '2c66b530-2a65-423a-a106-6760b49ad1a2');
function db() {
static $pdo;
if (!$pdo) {
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
}
return $pdo;
static $pdo;
if ($pdo) {
return $pdo;
}
try {
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
} catch (PDOException $e) {
// If the database doesn't exist, we can't run migrations.
// The error will be caught and displayed on the page.
throw $e;
}
return $pdo;
}

View File

@ -0,0 +1,27 @@
CREATE TABLE IF NOT EXISTS `assets` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL,
`asset_tag` VARCHAR(255) UNIQUE NOT NULL,
`serial_number` VARCHAR(255),
`model` VARCHAR(255),
`manufacturer` VARCHAR(255),
`category` VARCHAR(255),
`status` VARCHAR(50) NOT NULL,
`location` VARCHAR(255),
`purchase_date` DATE,
`purchase_cost` DECIMAL(10, 2),
`warranty_end` DATE,
`vendor` VARCHAR(255),
`assigned_to` INT,
`notes` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Seed data
INSERT INTO `assets` (`name`, `asset_tag`, `status`, `location`, `purchase_date`, `assigned_to`, `manufacturer`, `model`) VALUES
('Laptop', 'ASSET-001', 'In Service', 'Office A', '2023-01-15', 1, 'Dell', 'XPS 15'),
('Monitor', 'ASSET-002', 'In Service', 'Office A', '2023-01-15', 1, 'Dell', 'UltraSharp 27'),
('Keyboard', 'ASSET-003', 'In Service', 'Office B', '2023-02-20', 2, 'Logitech', 'MX Keys'),
('Mouse', 'ASSET-004', 'Under Repair', 'IT Department', '2023-02-20', NULL, 'Logitech', 'MX Master 3'),
('Projector', 'ASSET-005', 'Retired', 'Storage', '2020-05-10', NULL, 'Epson', 'PowerLite 1781W');

View File

@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
`role` enum('Admin','Asset Manager','IT Technician','Employee') NOT NULL DEFAULT 'Employee',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Seed with a default admin user
INSERT INTO `users` (`name`, `email`, `password`, `role`) VALUES
('Admin User', 'admin@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Admin'); -- password is 'password'

View File

@ -0,0 +1,40 @@
CREATE TABLE IF NOT EXISTS `role_permissions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role` varchar(255) NOT NULL,
`resource` varchar(255) NOT NULL,
`action` varchar(255) NOT NULL,
`fields` text DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `role_resource_action` (`role`,`resource`,`action`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Default Permissions
-- Admin: Can do everything
INSERT INTO `role_permissions` (`role`, `resource`, `action`, `fields`) VALUES
('Admin', 'asset', 'create', '*'),
('Admin', 'asset', 'read', '*'),
('Admin', 'asset', 'update', '*'),
('Admin', 'asset', 'delete', '*'),
('Admin', 'user', 'create', '*'),
('Admin', 'user', 'read', '*'),
('Admin', 'user', 'update', '*'),
('Admin', 'user', 'delete', '*');
-- Asset Manager: Can manage assets
INSERT INTO `role_permissions` (`role`, `resource`, `action`, `fields`) VALUES
('Asset Manager', 'asset', 'create', '*'),
('Asset Manager', 'asset', 'read', '*'),
('Asset Manager', 'asset', 'update', '*'),
('Asset Manager', 'asset', 'delete', '*');
-- IT Technician: Can manage assets
INSERT INTO `role_permissions` (`role`, `resource`, `action`, `fields`) VALUES
('IT Technician', 'asset', 'create', '*'),
('IT Technician', 'asset', 'read', '*'),
('IT Technician', 'asset', 'update', '*'),
('IT Technician', 'asset', 'delete', '*');
-- Employee: Can only read some asset fields
INSERT INTO `role_permissions` (`role`, `resource`, `action`, `fields`) VALUES
('Employee', 'asset', 'read', 'name,asset_tag,status,location,manufacturer,model');

View File

@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS `categories` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL UNIQUE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=INNODB;

View File

@ -0,0 +1,2 @@
ALTER TABLE `assets` ADD COLUMN `category_id` INT NULL AFTER `status`;
ALTER TABLE `assets` ADD FOREIGN KEY (`category_id`) REFERENCES `categories`(`id`) ON DELETE SET NULL;

View File

@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS `locations` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View File

@ -0,0 +1 @@
ALTER TABLE `assets` ADD COLUMN `location_id` INT NULL, ADD FOREIGN KEY (`location_id`) REFERENCES `locations`(`id`) ON DELETE SET NULL;

View File

@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS `roles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Seed with existing roles
INSERT INTO `roles` (`name`) VALUES
('Admin'),
('Asset Manager'),
('IT Technician'),
('Employee');

View File

@ -0,0 +1,16 @@
-- Add role_id column
ALTER TABLE `users` ADD COLUMN `role_id` INT(11) NULL AFTER `password`;
-- Update role_id from existing role name
UPDATE `users` u
JOIN `roles` r ON u.role = r.name
SET u.role_id = r.id;
-- Make role_id not nullable
ALTER TABLE `users` MODIFY `role_id` INT(11) NOT NULL;
-- Add foreign key constraint
ALTER TABLE `users` ADD CONSTRAINT `fk_user_role` FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- Drop the old role column
ALTER TABLE `users` DROP COLUMN `role`;

View File

@ -0,0 +1,22 @@
-- Add role_id column
ALTER TABLE `role_permissions` ADD COLUMN `role_id` INT(11) NULL AFTER `id`;
-- Update role_id from existing role name
UPDATE `role_permissions` rp
JOIN `roles` r ON rp.role = r.name
SET rp.role_id = r.id;
-- Make role_id not nullable
ALTER TABLE `role_permissions` MODIFY `role_id` INT(11) NOT NULL;
-- Drop the old unique key
ALTER TABLE `role_permissions` DROP INDEX `role_resource_action`;
-- Drop the old role column
ALTER TABLE `role_permissions` DROP COLUMN `role`;
-- Add new unique key with role_id
ALTER TABLE `role_permissions` ADD UNIQUE KEY `role_resource_action` (`role_id`, `resource`, `action`);
-- Add foreign key to roles table
ALTER TABLE `role_permissions` ADD CONSTRAINT `fk_permission_role` FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1 @@
INSERT INTO `role_permissions` (`role_id`, `resource`, `action`) VALUES (1, 'permission', 'update');

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS `permissions`;

View File

@ -0,0 +1,12 @@
-- Grant all permissions on roles and permissions to Super Admin (role_id = 1)
INSERT INTO `role_permissions` (`role_id`, `resource`, `action`) VALUES
-- Roles
(1, 'role', 'create'),
(1, 'role', 'read'),
(1, 'role', 'update'),
(1, 'role', 'delete'),
-- Permissions
(1, 'permission', 'read'),
(1, 'permission', 'update')
ON DUPLICATE KEY UPDATE `role_id` = `role_id`; -- Do nothing if the permission already exists

View File

@ -0,0 +1,24 @@
-- Grant remaining permissions to Super Admin (role_id = 1)
INSERT INTO `role_permissions` (`role_id`, `resource`, `action`) VALUES
-- Assets
(1, 'asset', 'create'),
(1, 'asset', 'read'),
(1, 'asset', 'update'),
(1, 'asset', 'delete'),
-- Categories
(1, 'category', 'create'),
(1, 'category', 'read'),
(1, 'category', 'update'),
(1, 'category', 'delete'),
-- Locations
(1, 'location', 'create'),
(1, 'location', 'read'),
(1, 'location', 'update'),
(1, 'location', 'delete'),
-- Users
(1, 'user', 'create'),
(1, 'user', 'read'),
(1, 'user', 'update'),
(1, 'user', 'delete')
ON DUPLICATE KEY UPDATE `role_id` = `role_id`; -- Do nothing if the permission already exists

View File

@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS settings (
id INT AUTO_INCREMENT PRIMARY KEY,
setting_key VARCHAR(255) NOT NULL UNIQUE,
setting_value TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Insert Google API credentials (initially empty)
INSERT INTO settings (setting_key, setting_value) VALUES ('google_client_id', '') ON DUPLICATE KEY UPDATE setting_key = 'google_client_id';
INSERT INTO settings (setting_key, setting_value) VALUES ('google_client_secret', '') ON DUPLICATE KEY UPDATE setting_key = 'google_client_secret';

8
debug_tables.php Normal file
View File

@ -0,0 +1,8 @@
<?php
require_once 'db/config.php';
$pdo = db();
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
echo "<pre>";
print_r($tables);
echo "</pre>";

25
delete-asset.php Normal file
View File

@ -0,0 +1,25 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header("Location: index.php");
exit;
}
$asset_id = $_GET['id'];
try {
$pdo = db();
$stmt = $pdo->prepare("DELETE FROM assets WHERE id = ?");
$stmt->execute([$asset_id]);
header("Location: index.php?success=asset_deleted");
exit;
} catch (PDOException $e) {
header("Location: index.php?error=db_error");
exit;
}
?>

43
delete-category.php Normal file
View File

@ -0,0 +1,43 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
if (!can($_SESSION['user_role_id'], 'category', 'delete')) {
header('Location: index.php?error=access_denied');
exit;
}
$category_id = $_GET['id'] ?? null;
if (!$category_id) {
header('Location: categories.php');
exit;
}
try {
$pdo = db();
$pdo->beginTransaction();
// Set category_id to NULL for assets associated with this category
$stmt = $pdo->prepare('UPDATE assets SET category_id = NULL WHERE category_id = ?');
$stmt->execute([$category_id]);
// Delete the category
$stmt = $pdo->prepare('DELETE FROM categories WHERE id = ?');
$stmt->execute([$category_id]);
$pdo->commit();
header("Location: categories.php?success=category_deleted");
exit;
} catch (PDOException $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
// In a real app, log this error.
header("Location: categories.php?error=db_error");
exit;
}

43
delete-location.php Normal file
View File

@ -0,0 +1,43 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
if (!can($_SESSION['user_role_id'], 'location', 'delete')) {
header('Location: index.php?error=access_denied');
exit;
}
$location_id = $_GET['id'] ?? null;
if (!$location_id) {
header('Location: locations.php');
exit;
}
try {
$pdo = db();
$pdo->beginTransaction();
// Set location_id to NULL for assets associated with this location
$stmt = $pdo->prepare('UPDATE assets SET location_id = NULL WHERE location_id = ?');
$stmt->execute([$location_id]);
// Delete the location
$stmt = $pdo->prepare('DELETE FROM locations WHERE id = ?');
$stmt->execute([$location_id]);
$pdo->commit();
header("Location: locations.php?success=location_deleted");
exit;
} catch (PDOException $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
// In a real app, log this error.
header("Location: locations.php?error=db_error");
exit;
}

41
delete-role.php Normal file
View File

@ -0,0 +1,41 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
if (!can($_SESSION['user_role_id'], 'role', 'delete')) {
header('Location: index.php?error=access_denied');
exit;
}
$role_id = $_GET['id'] ?? null;
if (!$role_id) {
header('Location: roles.php');
exit;
}
try {
$pdo = db();
// Check if any user is assigned to this role
$stmt = $pdo->prepare('SELECT id FROM users WHERE role_id = ?');
$stmt->execute([$role_id]);
if ($stmt->fetch()) {
header("Location: roles.php?error=role_in_use");
exit;
}
// Delete the role
$stmt = $pdo->prepare('DELETE FROM roles WHERE id = ?');
$stmt->execute([$role_id]);
header("Location: roles.php?success=role_deleted");
exit;
} catch (PDOException $e) {
// In a real app, log this error.
header("Location: roles.php?error=db_error");
exit;
}

37
delete-user.php Normal file
View File

@ -0,0 +1,37 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
// Only Admins can access this page
if ($_SESSION['user_role'] !== 'Admin') {
header('Location: index.php?error=access_denied');
exit;
}
$user_id = $_GET['id'] ?? null;
if (!$user_id) {
header('Location: users.php');
exit;
}
// Prevent user from deleting themselves
if ($user_id == $_SESSION['user_id']) {
header('Location: users.php?error=self_delete');
exit;
}
try {
$pdo = db();
$stmt = $pdo->prepare('DELETE FROM users WHERE id = ?');
$stmt->execute([$user_id]);
header("Location: users.php?success=user_deleted");
exit;
} catch (PDOException $e) {
// In a real app, log this error.
header("Location: users.php?error=db_error");
exit;
}

229
edit-asset.php Normal file
View File

@ -0,0 +1,229 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
if (!can($_SESSION['user_role_id'], 'asset', 'update')) {
header('Location: index.php?error=access_denied');
exit;
}
$allowed_fields_str = can($_SESSION['user_role_id'], 'asset', 'update');
$allowed_fields = ($allowed_fields_str === '*') ? ['name', 'asset_tag', 'status', 'location_id', 'manufacturer', 'model', 'purchase_date', 'assigned_to', 'category_id'] : explode(',', $allowed_fields_str);
$success_message = '';
$error_message = '';
$asset = null;
$users = [];
$categories = [];
$locations = [];
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header("Location: index.php");
exit;
}
$asset_id = $_GET['id'];
try {
$pdo = db();
$stmt = $pdo->prepare("SELECT * FROM assets WHERE id = ?");
$stmt->execute([$asset_id]);
$asset = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$asset) {
header("Location: index.php?error=not_found");
exit;
}
// Fetch users for dropdown
$stmt = $pdo->query("SELECT id, name FROM users ORDER BY name");
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch categories for dropdown
$stmt = $pdo->query("SELECT id, name FROM categories ORDER BY name");
$categories = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch locations for dropdown
$stmt = $pdo->query("SELECT id, name FROM locations ORDER BY name");
$locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = [];
$set_parts = [];
foreach ($allowed_fields as $field) {
if (isset($_POST[$field])) {
$value = $_POST[$field];
if (($field === 'assigned_to' || $field === 'category_id' || $field === 'location_id') && $value === '') {
$value = null;
}
$data[] = $value;
$set_parts[] = "$field = ?";
}
}
$data[] = $asset_id;
if (empty($set_parts)) {
$error_message = 'No data submitted.';
} else {
try {
$pdo = db();
$sql = sprintf("UPDATE assets SET %s WHERE id = ?", implode(', ', $set_parts));
$stmt = $pdo->prepare($sql);
$stmt->execute($data);
header("Location: index.php?success=asset_updated");
exit;
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Asset - IC-Inventory</title>
<meta name="description" content="Edit an existing asset in the inventory.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css"/>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Edit Asset</h1>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
<div class="surface p-4">
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<?php if ($asset): ?>
<form action="edit-asset.php?id=<?php echo $asset_id; ?>" method="post">
<div class="row">
<?php if (in_array('name', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="name" class="form-label">Asset Name*</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($asset['name']); ?>" required>
</div>
<?php endif; ?>
<?php if (in_array('asset_tag', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="asset_tag" class="form-label">Asset Tag*</label>
<input type="text" class="form-control" id="asset_tag" name="asset_tag" value="<?php echo htmlspecialchars($asset['asset_tag']); ?>" required>
</div>
<?php endif; ?>
</div>
<div class="row">
<?php if (in_array('status', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="status" class="form-label">Status</label>
<select class="form-select" id="status" name="status">
<option <?php if ($asset['status'] === 'In Service') echo 'selected'; ?>>In Service</option>
<option <?php if ($asset['status'] === 'Under Repair') echo 'selected'; ?>>Under Repair</option>
<option <?php if ($asset['status'] === 'Retired') echo 'selected'; ?>>Retired</option>
</select>
</div>
<?php endif; ?>
<?php if (in_array('category_id', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="category_id" class="form-label">Category</label>
<select class="form-select" id="category_id" name="category_id">
<option value="">No Category</option>
<?php foreach ($categories as $category): ?>
<option value="<?php echo $category['id']; ?>" <?php if ($asset['category_id'] == $category['id']) echo 'selected'; ?>>
<?php echo htmlspecialchars($category['name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<?php if (in_array('location_id', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="location_id" class="form-label">Location</label>
<select class="form-select" id="location_id" name="location_id">
<option value="">No Location</option>
<?php foreach ($locations as $location): ?>
<option value="<?php echo $location['id']; ?>" <?php if ($asset['location_id'] == $location['id']) echo 'selected'; ?>>
<?php echo htmlspecialchars($location['name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
</div>
<div class="row">
<?php if (in_array('manufacturer', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="manufacturer" class="form-label">Manufacturer</label>
<input type="text" class="form-control" id="manufacturer" name="manufacturer" value="<?php echo htmlspecialchars($asset['manufacturer']); ?>">
</div>
<?php endif; ?>
<?php if (in_array('model', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="model" class="form-label">Model</label>
<input type="text" class="form-control" id="model" name="model" value="<?php echo htmlspecialchars($asset['model']); ?>">
</div>
<?php endif; ?>
</div>
<div class="row">
<?php if (in_array('purchase_date', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="purchase_date" class="form-label">Purchase Date*</label>
<input type="date" class="form-control" id="purchase_date" name="purchase_date" value="<?php echo htmlspecialchars($asset['purchase_date']); ?>" required>
</div>
<?php endif; ?>
<?php if (in_array('assigned_to', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="assigned_to" class="form-label">Assigned To</label>
<select class="form-select" id="assigned_to" name="assigned_to">
<option value="">Unassigned</option>
<?php foreach ($users as $user): ?>
<option value="<?php echo $user['id']; ?>" <?php if ($asset['assigned_to'] == $user['id']) echo 'selected'; ?>>
<?php echo htmlspecialchars($user['name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
</div>
<button type="submit" class="btn btn-primary">Update Asset</button>
<a href="index.php" class="btn btn-secondary">Cancel</a>
</form>
<?php endif; ?>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
<script src="assets/js/choices.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

116
edit-category.php Normal file
View File

@ -0,0 +1,116 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
if (!can($_SESSION['user_role_id'], 'category', 'update')) {
header('Location: index.php?error=access_denied');
exit;
}
$error_message = '';
$category_id = $_GET['id'] ?? null;
if (!$category_id) {
header('Location: categories.php');
exit;
}
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT id, name FROM categories WHERE id = ?');
$stmt->execute([$category_id]);
$category = $stmt->fetch();
if (!$category) {
header('Location: categories.php');
exit;
}
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
if (empty($name)) {
$error_message = 'Please fill in the category name.';
} else {
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT id FROM categories WHERE name = ? AND id != ?');
$stmt->execute([$name, $category_id]);
if ($stmt->fetch()) {
$error_message = 'A category with this name already exists.';
} else {
$sql = "UPDATE categories SET name = ? WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name, $category_id]);
header("Location: categories.php?success=category_updated");
exit;
}
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Category - IC-Inventory</title>
<meta name="description" content="Edit an existing category.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Edit Category</h1>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
<div class="surface p-4">
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<?php if ($category): ?>
<form action="edit-category.php?id=<?php echo $category['id']; ?>" method="post">
<div class="row">
<div class="col-md-6 mb-3">
<label for="name" class="form-label">Category Name*</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($category['name']); ?>" required>
</div>
</div>
<button type="submit" class="btn btn-primary">Update Category</button>
<a href="categories.php" class="btn btn-secondary">Cancel</a>
</form>
<?php endif; ?>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

116
edit-location.php Normal file
View File

@ -0,0 +1,116 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
if (!can($_SESSION['user_role_id'], 'location', 'update')) {
header('Location: index.php?error=access_denied');
exit;
}
$error_message = '';
$location_id = $_GET['id'] ?? null;
if (!$location_id) {
header('Location: locations.php');
exit;
}
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT id, name FROM locations WHERE id = ?');
$stmt->execute([$location_id]);
$location = $stmt->fetch();
if (!$location) {
header('Location: locations.php');
exit;
}
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
if (empty($name)) {
$error_message = 'Please fill in the location name.';
} else {
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT id FROM locations WHERE name = ? AND id != ?');
$stmt->execute([$name, $location_id]);
if ($stmt->fetch()) {
$error_message = 'A location with this name already exists.';
} else {
$sql = "UPDATE locations SET name = ? WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name, $location_id]);
header("Location: locations.php?success=location_updated");
exit;
}
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Location - IC-Inventory</title>
<meta name="description" content="Edit an existing location.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Edit Location</h1>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
<div class="surface p-4">
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<?php if ($location): ?>
<form action="edit-location.php?id=<?php echo $location['id']; ?>" method="post">
<div class="row">
<div class="col-md-6 mb-3">
<label for="name" class="form-label">Location Name*</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($location['name']); ?>" required>
</div>
</div>
<button type="submit" class="btn btn-primary">Update Location</button>
<a href="locations.php" class="btn btn-secondary">Cancel</a>
</form>
<?php endif; ?>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

116
edit-role.php Normal file
View File

@ -0,0 +1,116 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
if (!can($_SESSION['user_role_id'], 'role', 'update')) {
header('Location: index.php?error=access_denied');
exit;
}
$error_message = '';
$role_id = $_GET['id'] ?? null;
if (!$role_id) {
header('Location: roles.php');
exit;
}
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT id, name FROM roles WHERE id = ?');
$stmt->execute([$role_id]);
$role = $stmt->fetch();
if (!$role) {
header('Location: roles.php');
exit;
}
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
if (empty($name)) {
$error_message = 'Please fill in the role name.';
} else {
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT id FROM roles WHERE name = ? AND id != ?');
$stmt->execute([$name, $role_id]);
if ($stmt->fetch()) {
$error_message = 'A role with this name already exists.';
} else {
$sql = "UPDATE roles SET name = ? WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name, $role_id]);
header("Location: roles.php?success=role_updated");
exit;
}
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Role - IC-Inventory</title>
<meta name="description" content="Edit an existing role.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Edit Role</h1>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
<div class="surface p-4">
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<?php if ($role): ?>
<form action="edit-role.php?id=<?php echo $role['id']; ?>" method="post">
<div class="row">
<div class="col-md-6 mb-3">
<label for="name" class="form-label">Role Name*</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($role['name']); ?>" required>
</div>
</div>
<button type="submit" class="btn btn-primary">Update Role</button>
<a href="roles.php" class="btn btn-secondary">Cancel</a>
</form>
<?php endif; ?>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

158
edit-user.php Normal file
View File

@ -0,0 +1,158 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
if (!can($_SESSION['user_role_id'], 'user', 'update')) {
header('Location: index.php?error=access_denied');
exit;
}
$allowed_fields_str = can($_SESSION['user_role_id'], 'user', 'update');
$allowed_fields = ($allowed_fields_str === '*') ? ['name', 'email', 'role_id'] : explode(',', $allowed_fields_str);
$error_message = '';
$user_id = $_GET['id'] ?? null;
if (!$user_id) {
header('Location: users.php');
exit;
}
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT id, name, email, role_id FROM users WHERE id = ?');
$stmt->execute([$user_id]);
$user = $stmt->fetch();
if (!$user) {
header('Location: users.php');
exit;
}
// Fetch all roles for the dropdown
$roles_stmt = $pdo->query('SELECT id, name FROM roles ORDER BY name');
$roles = $roles_stmt->fetchAll();
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = [];
$set_parts = [];
foreach ($allowed_fields as $field) {
if (isset($_POST[$field])) {
$data[] = $_POST[$field];
$set_parts[] = "$field = ?";
}
}
$data[] = $user_id;
if (empty($set_parts)) {
$error_message = 'No data submitted.';
} else {
try {
$pdo = db();
// Check if email already exists for another user
if (in_array('email', $allowed_fields)) {
$stmt = $pdo->prepare('SELECT id FROM users WHERE email = ? AND id != ?');
$stmt->execute([$_POST['email'], $user_id]);
if ($stmt->fetch()) {
$error_message = 'A user with this email address already exists.';
}
}
if (!$error_message) {
$sql = sprintf("UPDATE users SET %s WHERE id = ?", implode(', ', $set_parts));
$stmt = $pdo->prepare($sql);
$stmt->execute($data);
header("Location: users.php?success=user_updated");
exit;
}
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit User - IC-Inventory</title>
<meta name="description" content="Edit an existing user.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Edit User</h1>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
<div class="surface p-4">
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<?php if ($user): ?>
<form action="edit-user.php?id=<?php echo $user['id']; ?>" method="post">
<div class="row">
<?php if (in_array('name', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="name" class="form-label">Full Name*</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($user['name']); ?>" required>
</div>
<?php endif; ?>
<?php if (in_array('email', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="email" class="form-label">Email Address*</label>
<input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars($user['email']); ?>" required>
</div>
<?php endif; ?>
</div>
<div class="row">
<?php if (in_array('role_id', $allowed_fields)): ?>
<div class="col-md-6 mb-3">
<label for="role_id" class="form-label">Role</label>
<select class="form-select" id="role_id" name="role_id">
<?php foreach ($roles as $role): ?>
<option value="<?php echo $role['id']; ?>" <?php echo ($user['role_id'] == $role['id']) ? 'selected' : ''; ?>><?php echo htmlspecialchars($role['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
</div>
<button type="submit" class="btn btn-primary">Update User</button>
<a href="users.php" class="btn btn-secondary">Cancel</a>
</form>
<?php endif; ?>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

122
google-callback.php Normal file
View File

@ -0,0 +1,122 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-helpers.php';
$pdo = db();
// Fetch Google credentials from settings
try {
$stmt = $pdo->query("SELECT setting_key, setting_value FROM settings WHERE setting_key IN ('google_client_id', 'google_client_secret')");
$settings_raw = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$google_client_id = $settings_raw['google_client_id'] ?? '';
$google_client_secret = $settings_raw['google_client_secret'] ?? '';
} catch (PDOException $e) {
die('Database error fetching Google credentials.');
}
if (empty($google_client_id) || empty($google_client_secret)) {
die('Google API credentials are not configured. Please ask an administrator to set them up.');
}
if (!isset($_GET['code'])) {
header('Location: login.php?error=google_auth_failed');
exit;
}
$code = $_GET['code'];
$redirect_uri = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . '/google-callback.php';
// 1. Exchange authorization code for an access token
$token_url = 'https://oauth2.googleapis.com/token';
$token_params = [
'code' => $code,
'client_id' => $google_client_id,
'client_secret' => $google_client_secret,
'redirect_uri' => $redirect_uri,
'grant_type' => 'authorization_code'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $token_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($token_params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$token_data = json_decode($response, true);
if (!isset($token_data['access_token'])) {
// Log error: print_r($token_data);
header('Location: login.php?error=google_token_exchange_failed');
exit;
}
// 2. Get user info from Google
$userinfo_url = 'https://www.googleapis.com/oauth2/v2/userinfo';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $userinfo_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $token_data['access_token']]);
$userinfo_response = curl_exec($ch);
curl_close($ch);
$userinfo = json_decode($userinfo_response, true);
if (!isset($userinfo['email'])) {
// Log error: print_r($userinfo);
header('Location: login.php?error=google_userinfo_failed');
exit;
}
$user_email = $userinfo['email'];
$user_name = $userinfo['name'] ?? 'Google User';
// 3. Check if user exists in the database
try {
$stmt = $pdo->prepare("SELECT u.*, r.name as role_name FROM users u JOIN roles r ON u.role_id = r.id WHERE u.email = ?");
$stmt->execute([$user_email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// 4. If user exists, log them in
if ($user) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['name'];
$_SESSION['user_role_id'] = $user['role_id'];
$_SESSION['user_role_name'] = $user['role_name'];
$_SESSION['user_role'] = $user['role_name']; // Backwards compatibility
header('Location: index.php');
exit;
}
// 5. If user does not exist, create a new user with the "Employee" role
$employee_role_id = 3; // Default to 'Employee' role (assuming ID 3)
$stmt_role = $pdo->prepare("SELECT id FROM roles WHERE name = ?");
$stmt_role->execute(['Employee']);
$role_id_from_db = $stmt_role->fetchColumn();
if ($role_id_from_db) {
$employee_role_id = $role_id_from_db;
}
// Generate a random password as it's required by the schema
$random_password = password_hash(bin2hex(random_bytes(16)), PASSWORD_DEFAULT);
$insert_stmt = $pdo->prepare("INSERT INTO users (name, email, password, role_id) VALUES (?, ?, ?, ?)");
$insert_stmt->execute([$user_name, $user_email, $random_password, $employee_role_id]);
$new_user_id = $pdo->lastInsertId();
// Log the new user in
$_SESSION['user_id'] = $new_user_id;
$_SESSION['user_name'] = $user_name;
$_SESSION['user_role_id'] = $employee_role_id;
$_SESSION['user_role_name'] = 'Employee';
$_SESSION['user_role'] = 'Employee';
header('Location: index.php?new_user=true');
exit;
} catch (PDOException $e) {
// Log error: $e->getMessage();
die('Database error during user processing. Please try again.');
}

30
google-login.php Normal file
View File

@ -0,0 +1,30 @@
<?php
session_start();
require_once 'db/config.php';
// Fetch Google Client ID from settings
try {
$pdo = db();
$stmt = $pdo->query("SELECT setting_value FROM settings WHERE setting_key = 'google_client_id'");
$google_client_id = $stmt->fetchColumn();
} catch (PDOException $e) {
die('Database error fetching Google Client ID. Please configure it in the settings.');
}
if (empty($google_client_id)) {
die('Google Client ID is not configured. Please ask an administrator to set it up.');
}
$redirect_uri = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . '/google-callback.php';
$auth_url = 'https://accounts.google.com/o/oauth2/v2/auth?' . http_build_query([
'client_id' => $google_client_id,
'redirect_uri' => $redirect_uri,
'response_type' => 'code',
'scope' => 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
'access_type' => 'offline',
'prompt' => 'select_account'
]);
header('Location: ' . $auth_url);
exit;

473
index.php
View File

@ -1,150 +1,341 @@
<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
// Store role name in session if not already set
if (isset($_SESSION['user_role_id']) && !isset($_SESSION['user_role_name'])) {
try {
$pdo = db();
$stmt = $pdo->prepare("SELECT name FROM roles WHERE id = :role_id");
$stmt->execute([':role_id' => $_SESSION['user_role_id']]);
$role = $stmt->fetch(PDO::FETCH_ASSOC);
if ($role) {
$_SESSION['user_role_name'] = $role['name'];
}
} catch (PDOException $e) {
// Could not fetch role name, do nothing, the check later will handle it
}
}
// Get allowed fields for the current user
$allowed_fields_str = can($_SESSION['user_role_id'], 'asset', 'read');
$allowed_fields = [];
if ($allowed_fields_str === '*') {
// Wildcard means all fields
try {
$pdo = db();
$stmt = $pdo->query("SHOW COLUMNS FROM assets");
$allowed_fields = $stmt->fetchAll(PDO::FETCH_COLUMN);
} catch (PDOException $e) {
// Handle error, maybe log it
$allowed_fields = [];
}
} elseif ($allowed_fields_str) {
$allowed_fields = explode(',', $allowed_fields_str);
}
// Function to count total assets
function count_assets($search = '', $status = '') {
$sql = "SELECT COUNT(*) FROM assets";
$where = [];
$params = [];
// Role-based filtering for 'Employee'
if (isset($_SESSION['user_role_name']) && strtolower($_SESSION['user_role_name']) === 'employee' && isset($_SESSION['user_id'])) {
$where[] = "assigned_to = :user_id";
$params[':user_id'] = $_SESSION['user_id'];
}
if (!empty($search)) {
$where[] = "name LIKE :search";
$params[':search'] = "%$search%";
}
if (!empty($status)) {
$where[] = "status = :status";
$params[':status'] = $status;
}
if (!empty($where)) {
$sql .= " WHERE " . implode(' AND ', $where);
}
try {
$pdo = db();
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchColumn();
} catch (PDOException $e) {
return 0;
}
}
// Function to execute query and return results
function get_assets($fields, $search = '', $status = '', $limit = 10, $offset = 0, $sort_by = 'created_at', $sort_order = 'DESC') {
if (empty($fields)) {
return []; // No read permission
}
// Always include id for edit/delete links
if (!in_array('id', $fields)) {
$fields[] = 'id';
}
$select_fields = [];
$join_users = in_array('assigned_to', $fields);
foreach ($fields as $field) {
if ($field === 'assigned_to') {
// Use a different alias for the user name to avoid conflict with the original column name
$select_fields[] = 'users.name AS assigned_to_name';
}
// Always select the original assigned_to field for reference if needed
$select_fields[] = 'assets.' . $field;
}
// Remove duplicates that might be caused by adding assets.id and assets.assigned_to
$select_fields = array_unique($select_fields);
$select_fields_sql = implode(', ', $select_fields);
$sql = "SELECT $select_fields_sql FROM assets";
if ($join_users) {
$sql .= " LEFT JOIN users ON assets.assigned_to = users.id";
}
$where = [];
$params = [];
// Role-based filtering for 'Employee'
if (isset($_SESSION['user_role_name']) && strtolower($_SESSION['user_role_name']) === 'employee' && isset($_SESSION['user_id'])) {
$where[] = "assets.assigned_to = :user_id";
$params[':user_id'] = $_SESSION['user_id'];
}
if (!empty($search)) {
// Assuming 'name' is a field that can be searched.
if (in_array('name', $fields)) {
$where[] = "assets.name LIKE :search";
$params[':search'] = "%$search%";
}
}
if (!empty($status)) {
if (in_array('status', $fields)) {
$where[] = "assets.status = :status";
$params[':status'] = $status;
}
}
if (!empty($where)) {
$sql .= " WHERE " . implode(' AND ', $where);
}
// Whitelist sortable columns
$sortable_columns = array_merge($fields, ['created_at']);
if ($sort_by === 'assigned_to') {
$sort_by = 'assigned_to_name'; // Sort by the alias
} elseif (in_array($sort_by, $fields)) {
$sort_by = 'assets.' . $sort_by;
} elseif (!in_array($sort_by, $sortable_columns)) {
$sort_by = 'assets.created_at';
}
$sort_order = strtoupper($sort_order) === 'ASC' ? 'ASC' : 'DESC';
$sql .= " ORDER BY $sort_by $sort_order LIMIT :limit OFFSET :offset";
$params[':limit'] = $limit;
$params[':offset'] = $offset;
try {
$pdo = db();
$stmt = $pdo->prepare($sql);
// Bind parameters separately to handle integer binding for LIMIT and OFFSET
foreach ($params as $key => &$val) {
if ($key === ':limit' || $key === ':offset') {
$stmt->bindParam($key, $val, PDO::PARAM_INT);
} else {
$stmt->bindParam($key, $val);
}
}
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
return ['error' => 'Database error: ' . $e->getMessage()];
}
}
$search = $_GET['search'] ?? '';
$status = $_GET['status'] ?? '';
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$sort_by = $_GET['sort_by'] ?? 'created_at';
$sort_order = $_GET['sort_order'] ?? 'DESC';
$total_assets = count_assets($search, $status);
$total_pages = ceil($total_assets / $limit);
$assets = get_assets($allowed_fields, $search, $status, $limit, $offset, $sort_by, $sort_order);
function getStatusClass($status) {
switch (strtolower($status)) {
case 'in service':
return 'status-in-service';
case 'under repair':
return 'status-under-repair';
case 'retired':
return 'status-retired';
default:
return '';
}
}
?>
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New Style</title>
<?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IC-Inventory</title>
<meta name="description" content="Built with Flatlogic Generator">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<main>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
</div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
</div>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Asset Dashboard</h1>
<div>
<?php if (can($_SESSION['user_role_id'], 'asset', 'create')): ?>
<a href="add-asset.php" class="btn btn-primary">Add New Asset</a>
<?php endif; ?>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
</div>
<?php if (isset($_GET['success']) && $_GET['success'] === 'asset_added'): ?>
<div class="alert alert-success">Asset successfully added!</div>
<?php elseif (isset($_GET['success']) && $_GET['success'] === 'asset_updated'): ?>
<div class="alert alert-success">Asset successfully updated!</div>
<?php elseif (isset($_GET['success']) && $_GET['success'] === 'asset_deleted'): ?>
<div class="alert alert-success">Asset successfully deleted!</div>
<?php elseif (isset($_GET['error']) && $_GET['error'] === 'access_denied'): ?>
<div class="alert alert-danger">You do not have permission to access that page.</div>
<?php endif; ?>
<div class="surface p-4">
<form method="get" class="row g-3 mb-4">
<div class="col-md-4">
<input type="text" name="search" class="form-control" placeholder="Search by asset name..." value="<?php echo htmlspecialchars($search); ?>">
</div>
<div class="col-md-3">
<select name="status" class="form-select">
<option value="">All Statuses</option>
<option value="In Service" <?php echo ($status === 'In Service') ? 'selected' : ''; ?>>In Service</option>
<option value="Under Repair" <?php echo ($status === 'Under Repair') ? 'selected' : ''; ?>>Under Repair</option>
<option value="Retired" <?php echo ($status === 'Retired') ? 'selected' : ''; ?>>Retired</option>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary">Filter</button>
</div>
</form>
<?php if (isset($assets['error'])): ?>
<div class="alert alert-danger">
<?php echo htmlspecialchars($assets['error']); ?>
</div>
<?php elseif (empty($assets)): ?>
<div class="text-center p-5">
<h4>No assets found.</h4>
<?php if (can($_SESSION['user_role_id'], 'asset', 'create')): ?>
<p>Get started by adding your first company asset.</p>
<a href="add-asset.php" class="btn btn-primary">Add Asset</a>
<?php endif; ?>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table asset-table">
<thead>
<tr>
<?php
$new_sort_order = ($sort_order === 'DESC') ? 'ASC' : 'DESC';
foreach ($allowed_fields as $field): if($field === 'id') continue;
?>
<th>
<a href="?page=<?php echo $page; ?>&search=<?php echo urlencode($search); ?>&status=<?php echo urlencode($status); ?>&sort_by=<?php echo $field; ?>&sort_order=<?php echo $new_sort_order; ?>">
<?php echo ucfirst(str_replace('_', ' ', $field)); ?>
<?php if ($sort_by === $field): ?>
<i data-feather="<?php echo ($sort_order === 'DESC') ? 'arrow-down' : 'arrow-up'; ?>"></i>
<?php endif; ?>
</a>
</th>
<?php endforeach; ?>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($assets as $asset): ?>
<tr>
<?php foreach ($allowed_fields as $field): if($field === 'id') continue; ?>
<td>
<?php if ($field === 'status'): ?>
<span class="status <?php echo getStatusClass($asset[$field]); ?>"><?php echo htmlspecialchars($asset[$field]); ?></span>
<?php elseif ($field === 'assigned_to'): ?>
<?php echo htmlspecialchars($asset['assigned_to_name'] ?? ($asset['assigned_to'] ?? '')); ?>
<?php else: ?>
<?php echo htmlspecialchars($asset[$field] ?? ''); ?>
<?php endif; ?>
</td>
<?php endforeach; ?>
<td>
<?php if (can($_SESSION['user_role_id'], 'asset', 'update')): ?>
<a href="edit-asset.php?id=<?php echo $asset['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a>
<?php endif; ?>
<?php if (can($_SESSION['user_role_id'], 'asset', 'delete')): ?>
<a href="delete-asset.php?id=<?php echo $asset['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to delete this asset?');">Delete</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
<?php if ($page > 1): ?>
<li class="page-item"><a class="page-link" href="?page=<?php echo $page - 1; ?>&search=<?php echo urlencode($search); ?>&status=<?php echo urlencode($status); ?>&sort_by=<?php echo $sort_by; ?>&sort_order=<?php echo $sort_order; ?>">Previous</a></li>
<?php endif; ?>
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<li class="page-item <?php echo ($i == $page) ? 'active' : ''; ?>"><a class="page-link" href="?page=<?php echo $i; ?>&search=<?php echo urlencode($search); ?>&status=<?php echo urlencode($status); ?>&sort_by=<?php echo $sort_by; ?>&sort_order=<?php echo $sort_order; ?>"><?php echo $i; ?></a></li>
<?php endfor; ?>
<?php if ($page < $total_pages): ?>
<li class="page-item"><a class="page-link" href="?page=<?php echo $page + 1; ?>&search=<?php echo urlencode($search); ?>&status=<?php echo urlencode($status); ?>&sort_by=<?php echo $sort_by; ?>&sort_order=<?php echo $sort_order; ?>">Next</a></li>
<?php endif; ?>
</ul>
</nav>
<?php endif; ?>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

273
index.php.bak Normal file
View File

@ -0,0 +1,273 @@
<?php
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
// Get allowed fields for the current user
$allowed_fields_str = can($_SESSION['user_role'], 'asset', 'read');
$allowed_fields = $allowed_fields_str ? explode(',', $allowed_fields_str) : [];
// Function to count total assets
function count_assets($search = '', $status = '') {
$sql = "SELECT COUNT(*) FROM assets";
$where = [];
$params = [];
if (!empty($search)) {
$where[] = "name LIKE :search";
$params[':search'] = "%$search%";
}
if (!empty($status)) {
$where[] = "status = :status";
$params[':status'] = $status;
}
if (!empty($where)) {
$sql .= " WHERE " . implode(' AND ', $where);
}
try {
$pdo = db();
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchColumn();
} catch (PDOException $e) {
return 0;
}
}
// Function to execute query and return results
function get_assets($fields, $search = '', $status = '', $limit = 10, $offset = 0, $sort_by = 'created_at', $sort_order = 'DESC') {
if (empty($fields)) {
return []; // No read permission
}
// Always include id for edit/delete links
if (!in_array('id', $fields)) {
$fields[] = 'id';
}
$select_fields = implode(', ', $fields);
$sql = "SELECT $select_fields FROM assets";
$where = [];
$params = [];
if (!empty($search)) {
// Assuming 'name' is a field that can be searched.
if (in_array('name', $fields)) {
$where[] = "name LIKE :search";
$params[':search'] = "%$search%";
}
}
if (!empty($status)) {
if (in_array('status', $fields)) {
$where[] = "status = :status";
$params[':status'] = $status;
}
}
if (!empty($where)) {
$sql .= " WHERE " . implode(' AND ', $where);
}
// Whitelist sortable columns
$sortable_columns = array_merge($fields, ['created_at']);
if (!in_array($sort_by, $sortable_columns)) {
$sort_by = 'created_at';
}
$sort_order = strtoupper($sort_order) === 'ASC' ? 'ASC' : 'DESC';
$sql .= " ORDER BY $sort_by $sort_order LIMIT :limit OFFSET :offset";
$params[':limit'] = $limit;
$params[':offset'] = $offset;
try {
$pdo = db();
$stmt = $pdo->prepare($sql);
// Bind parameters separately to handle integer binding for LIMIT and OFFSET
foreach ($params as $key => &$val) {
if ($key === ':limit' || $key === ':offset') {
$stmt->bindParam($key, $val, PDO::PARAM_INT);
} else {
$stmt->bindParam($key, $val);
}
}
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
return ['error' => 'Database error: ' . $e->getMessage()];
}
}
$search = $_GET['search'] ?? '';
$status = $_GET['status'] ?? '';
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$sort_by = $_GET['sort_by'] ?? 'created_at';
$sort_order = $_GET['sort_order'] ?? 'DESC';
$total_assets = count_assets($search, $status);
$total_pages = ceil($total_assets / $limit);
$assets = get_assets($allowed_fields, $search, $status, $limit, $offset, $sort_by, $sort_order);
function getStatusClass($status) {
switch (strtolower($status)) {
case 'in service':
return 'status-in-service';
case 'under repair':
return 'status-under-repair';
case 'retired':
return 'status-retired';
default:
return '';
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IC-Inventory</title>
<meta name="description" content="Built with Flatlogic Generator">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Asset Dashboard</h1>
<div>
<?php if (can($_SESSION['user_role'], 'asset', 'create')): ?>
<a href="add-asset.php" class="btn btn-primary">Add New Asset</a>
<?php endif; ?>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
</div>
<?php if (isset($_GET['success']) && $_GET['success'] === 'asset_added'): ?>
<div class="alert alert-success">Asset successfully added!</div>
<?php elseif (isset($_GET['success']) && $_GET['success'] === 'asset_updated'): ?>
<div class="alert alert-success">Asset successfully updated!</div>
<?php elseif (isset($_GET['success']) && $_GET['success'] === 'asset_deleted'): ?>
<div class="alert alert-success">Asset successfully deleted!</div>
<?php elseif (isset($_GET['error']) && $_GET['error'] === 'access_denied'): ?>
<div class="alert alert-danger">You do not have permission to access that page.</div>
<?php endif; ?>
<div class="surface p-4">
<form method="get" class="row g-3 mb-4">
<div class="col-md-4">
<input type="text" name="search" class="form-control" placeholder="Search by asset name..." value="<?php echo htmlspecialchars($search); ?>">
</div>
<div class="col-md-3">
<select name="status" class="form-select">
<option value="">All Statuses</option>
<option value="In Service" <?php echo ($status === 'In Service') ? 'selected' : ''; ?>>In Service</option>
<option value="Under Repair" <?php echo ($status === 'Under Repair') ? 'selected' : ''; ?>>Under Repair</option>
<option value="Retired" <?php echo ($status === 'Retired') ? 'selected' : ''; ?>>Retired</option>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary">Filter</button>
</div>
</form>
<?php if (isset($assets['error'])): ?>
<div class="alert alert-danger">
<?php echo htmlspecialchars($assets['error']); ?>
</div>
<?php elseif (empty($assets)): ?>
<div class="text-center p-5">
<h4>No assets found.</h4>
<?php if (can($_SESSION['user_role'], 'asset', 'create')): ?>
<p>Get started by adding your first company asset.</p>
<a href="add-asset.php" class="btn btn-primary">Add Asset</a>
<?php endif; ?>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table asset-table">
<thead>
<tr>
<?php
$new_sort_order = ($sort_order === 'DESC') ? 'ASC' : 'DESC';
foreach ($allowed_fields as $field): if($field === 'id') continue;
?>
<th>
<a href="?page=<?php echo $page; ?>&search=<?php echo urlencode($search); ?>&status=<?php echo urlencode($status); ?>&sort_by=<?php echo $field; ?>&sort_order=<?php echo $new_sort_order; ?>">
<?php echo ucfirst(str_replace('_', ' ', $field)); ?>
<?php if ($sort_by === $field): ?>
<i data-feather="<?php echo ($sort_order === 'DESC') ? 'arrow-down' : 'arrow-up'; ?>"></i>
<?php endif; ?>
</a>
</th>
<?php endforeach; ?>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($assets as $asset): ?>
<tr>
<?php foreach ($allowed_fields as $field): if($field === 'id') continue; ?>
<td>
<?php if ($field === 'status'): ?>
<span class="status <?php echo getStatusClass($asset[$field]); ?>"><?php echo htmlspecialchars($asset[$field]); ?></span>
<?php else: ?>
<?php echo htmlspecialchars($asset[$field] ?? ''); ?>
<?php endif; ?>
</td>
<?php endforeach; ?>
<td>
<?php if (can($_SESSION['user_role'], 'asset', 'update')): ?>
<a href="edit-asset.php?id=<?php echo $asset['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a>
<?php endif; ?>
<?php if (can($_SESSION['user_role'], 'asset', 'delete')): ?>
<a href="delete-asset.php?id=<?php echo $asset['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to delete this asset?');">Delete</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
<?php if ($page > 1): ?>
<li class="page-item"><a class="page-link" href="?page=<?php echo $page - 1; ?>&search=<?php echo urlencode($search); ?>&status=<?php echo urlencode($status); ?>&sort_by=<?php echo $sort_by; ?>&sort_order=<?php echo $sort_order; ?>">Previous</a></li>
<?php endif; ?>
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<li class="page-item <?php echo ($i == $page) ? 'active' : ''; ?>"><a class="page-link" href="?page=<?php echo $i; ?>&search=<?php echo urlencode($search); ?>&status=<?php echo urlencode($status); ?>&sort_by=<?php echo $sort_by; ?>&sort_order=<?php echo $sort_order; ?>"><?php echo $i; ?></a></li>
<?php endfor; ?>
<?php if ($page < $total_pages): ?>
<li class="page-item"><a class="page-link" href="?page=<?php echo $page + 1; ?>&search=<?php echo urlencode($search); ?>&status=<?php echo urlencode($status); ?>&sort_by=<?php echo $sort_by; ?>&sort_order=<?php echo $sort_order; ?>">Next</a></li>
<?php endif; ?>
</ul>
</nav>
<?php endif; ?>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

118
locations.php Normal file
View File

@ -0,0 +1,118 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
// Permissions check
if (!can($_SESSION['user_role_id'], 'location', 'read')) {
header('Location: index.php?error=access_denied');
exit;
}
function get_locations() {
try {
$pdo = db();
$stmt = $pdo->query("SELECT * FROM locations ORDER BY name ASC");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
return ['error' => 'Database error: ' . $e->getMessage()];
}
}
$locations = get_locations();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Location Management - IC-Inventory</title>
<meta name="description" content="Location management for IC-Inventory.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Location Management</h1>
<div>
<?php if (can($_SESSION['user_role_id'], 'location', 'create')): ?>
<a href="add-location.php" class="btn btn-primary">Add New Location</a>
<?php endif; ?>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
</div>
<?php if (isset($_GET['success'])): ?>
<div class="alert alert-success">
<?php
if ($_GET['success'] === 'location_added') echo 'Location successfully added!';
if ($_GET['success'] === 'location_updated') echo 'Location successfully updated!';
if ($_GET['success'] === 'location_deleted') echo 'Location successfully deleted!';
?>
</div>
<?php endif; ?>
<div class="surface p-4">
<?php if (isset($locations['error'])): ?>
<div class="alert alert-danger">
<?php echo htmlspecialchars($locations['error']); ?>
</div>
<?php elseif (empty($locations)): ?>
<div class="text-center p-5">
<h4>No locations found.</h4>
<?php if (can($_SESSION['user_role_id'], 'location', 'create')): ?>
<p>Get started by adding your first location.</p>
<a href="add-location.php" class="btn btn-primary">Add Location</a>
<?php endif; ?>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($locations as $location): ?>
<tr>
<td><?php echo htmlspecialchars($location['name']); ?></td>
<td>
<?php if (can($_SESSION['user_role_id'], 'location', 'update')): ?>
<a href="edit-location.php?id=<?php echo $location['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a>
<?php endif; ?>
<?php if (can($_SESSION['user_role_id'], 'location', 'delete')): ?>
<a href="delete-location.php?id=<?php echo $location['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to delete this location?');">Delete</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

97
login.php Normal file
View File

@ -0,0 +1,97 @@
<?php
session_start();
require_once 'db/config.php';
$error_message = '';
// If user is already logged in, redirect to dashboard
if (isset($_SESSION['user_id'])) {
header("Location: index.php");
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
if (empty($email) || empty($password)) {
$error_message = 'Please enter both email and password.';
} else {
try {
$pdo = db();
$stmt = $pdo->prepare("SELECT u.*, r.name as role_name FROM users u JOIN roles r ON u.role_id = r.id WHERE u.email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['name'];
$_SESSION['user_role_id'] = $user['role_id'];
$_SESSION['user_role_name'] = $user['role_name'];
// For backwards compatibility with old code expecting a role name
$_SESSION['user_role'] = $user['role_name'];
header("Location: index.php");
exit;
} else {
$error_message = 'Invalid email or password.';
}
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - IC-Inventory</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<style>
body {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background-color: #f7f9fc;
}
.login-card {
max-width: 400px;
width: 100%;
}
</style>
</head>
<body>
<div class="card login-card shadow-sm">
<div class="card-body p-5">
<h1 class="card-title text-center mb-4">IC-Inventory</h1>
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<form action="login.php" method="post">
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary w-100">Login</button>
</form>
<div class="divider d-flex align-items-center my-4">
<p class="text-center fw-bold mx-3 mb-0 text-muted">OR</p>
</div>
<a class="btn btn-danger w-100" href="google-login.php" role="button">
<i class="fab fa-google me-2"></i>Sign in with Google
</a>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

7
logout.php Normal file
View File

@ -0,0 +1,7 @@
<?php
session_start();
session_unset();
session_destroy();
header("Location: login.php");
exit;
?>

126
roles.php Normal file
View File

@ -0,0 +1,126 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
// Permissions check
if (!can($_SESSION['user_role_id'], 'role', 'read')) {
// First, check if the user has permissions for this resource
$user_role_id = $_SESSION['user_role'];
$sql = "SELECT 1 FROM role_permissions WHERE role_id = :role_id AND resource = 'role' AND action = 'read'";
$stmt = db()->prepare($sql);
$stmt->execute(['role_id' => $user_role_id]);
if ($stmt->rowCount() == 0) {
// If no permissions, redirect
header('Location: index.php?error=access_denied');
exit;
}
}
function get_roles() {
try {
$pdo = db();
$stmt = $pdo->query("SELECT * FROM roles ORDER BY name ASC");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
return ['error' => 'Database error: ' . $e->getMessage()];
}
}
$roles = get_roles();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Role Management - IC-Inventory</title>
<meta name="description" content="Role management for IC-Inventory.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Role Management</h1>
<div>
<?php if (can($_SESSION['user_role_id'], 'role', 'create')): ?>
<a href="add-role.php" class="btn btn-primary">Add New Role</a>
<?php endif; ?>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
</div>
<?php if (isset($_GET['success'])): ?>
<div class="alert alert-success">
<?php
if ($_GET['success'] === 'role_added') echo 'Role successfully added!';
if ($_GET['success'] === 'role_updated') echo 'Role successfully updated!';
if ($_GET['success'] === 'role_deleted') echo 'Role successfully deleted!';
?>
</div>
<?php endif; ?>
<div class="surface p-4">
<?php if (isset($roles['error'])): ?>
<div class="alert alert-danger">
<?php echo htmlspecialchars($roles['error']); ?>
</div>
<?php elseif (empty($roles)): ?>
<div class="text-center p-5">
<h4>No roles found.</h4>
<?php if (can($_SESSION['user_role_id'], 'role', 'create')): ?>
<p>Get started by adding your first role.</p>
<a href="add-role.php" class="btn btn-primary">Add Role</a>
<?php endif; ?>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($roles as $role): ?>
<tr>
<td><?php echo htmlspecialchars($role['name']); ?></td>
<td>
<?php if (can($_SESSION['user_role_id'], 'role', 'update')): ?>
<a href="edit-role.php?id=<?php echo $role['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a>
<?php endif; ?>
<?php if (can($_SESSION['user_role_id'], 'role', 'delete')): ?>
<a href="delete-role.php?id=<?php echo $role['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to delete this role?');">Delete</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

78
run-migrations.php Normal file
View File

@ -0,0 +1,78 @@
<?php
require_once 'db/config.php';
try {
$pdo = db();
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 1. Create migrations table if it doesn't exist
$pdo->exec("CREATE TABLE IF NOT EXISTS migrations (
id INT AUTO_INCREMENT PRIMARY KEY,
migration_file VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY (migration_file)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
// 2. Pre-populate with migrations that are known to have run already
$known_migrations = [
'001_create_assets_table.sql',
'002_create_users_table.sql',
'003_create_permissions_table.sql',
'004_create_categories_table.sql',
'005_add_category_id_to_assets.sql'
];
$stmt = $pdo->prepare("INSERT IGNORE INTO migrations (migration_file) VALUES (?)");
foreach ($known_migrations as $migration) {
$stmt->execute([$migration]);
}
// 3. Get all migrations that have already been run
$ran_migrations_stmt = $pdo->query("SELECT migration_file FROM migrations");
$ran_migrations = $ran_migrations_stmt->fetchAll(PDO::FETCH_COLUMN);
// 4. Get all available migration files
$migration_files = glob('db/migrations/*.sql');
sort($migration_files);
// 5. Determine which migrations to run
$migrations_to_run = [];
foreach ($migration_files as $file) {
if (!in_array(basename($file), $ran_migrations)) {
$migrations_to_run[] = $file;
}
}
if (empty($migrations_to_run)) {
echo "Database is already up to date.\n";
} else {
// 6. Run the new migrations
foreach ($migrations_to_run as $file) {
try {
$sql = file_get_contents($file);
if (empty(trim($sql))) {
echo "Skipping empty migration file: $file\n";
$stmt = $pdo->prepare("INSERT INTO migrations (migration_file) VALUES (?)");
$stmt->execute([basename($file)]);
continue;
}
$pdo->exec($sql);
$stmt = $pdo->prepare("INSERT INTO migrations (migration_file) VALUES (?)");
$stmt->execute([basename($file)]);
echo "Successfully ran migration: " . basename($file) . "\n";
} catch (PDOException $e) {
echo "Error running migration " . basename($file) . ": " . $e->getMessage() . "\n";
exit(1);
}
}
echo "All new migrations ran successfully.\n";
}
} catch (PDOException $e) {
die("Database error: " . $e->getMessage());
}
?>

241
settings.php Normal file
View File

@ -0,0 +1,241 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
// Only Super Admins can manage settings
$is_super_admin = ($_SESSION['user_role_id'] == 1);
if (!$is_super_admin) {
// Redirect non-super-admins or users who can't update permissions
if (!can($_SESSION['user_role_id'], 'permission', 'update')) {
header('Location: index.php?error=access_denied');
exit;
}
}
$success_message = '';
$error_message = '';
$pdo = db();
// Handle Google Settings form submission
if ($is_super_admin && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_google_settings'])) {
$google_client_id = $_POST['google_client_id'] ?? '';
$google_client_secret = $_POST['google_client_secret'] ?? '';
try {
$stmt = $pdo->prepare('UPDATE settings SET setting_value = ? WHERE setting_key = ?');
$stmt->execute([$google_client_id, 'google_client_id']);
$stmt->execute([$google_client_secret, 'google_client_secret']);
$success_message = 'Google settings updated successfully!';
} catch (PDOException $e) {
$error_message = 'Database error updating Google settings: ' . $e->getMessage();
}
}
// Handle Permissions form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_permissions'])) {
if (!can($_SESSION['user_role_id'], 'permission', 'update')) {
header('Location: index.php?error=access_denied');
exit;
}
try {
$pdo->beginTransaction();
$permissions = $_POST['permissions'] ?? [];
$stmt_roles = $pdo->query('SELECT id FROM roles');
$db_roles_ids = $stmt_roles->fetchAll(PDO::FETCH_COLUMN);
$delete_stmt = $pdo->prepare('DELETE FROM role_permissions WHERE role_id = ?');
$insert_stmt = $pdo->prepare('INSERT INTO role_permissions (role_id, resource, action, fields) VALUES (?, ?, ?, ?)');
foreach ($db_roles_ids as $role_id) {
$delete_stmt->execute([$role_id]);
if (isset($permissions[$role_id])) {
foreach (['asset', 'user', 'category', 'location', 'role', 'permission'] as $resource) {
foreach (['create', 'read', 'update', 'delete'] as $action) {
if (isset($permissions[$role_id][$resource][$action]['enabled']) && $permissions[$role_id][$resource][$action]['enabled'] == '1') {
$fields = (in_array($action, ['read', 'update', 'create'])) ? ($permissions[$role_id][$resource][$action]['fields'] ?? '*') : null;
if (empty($fields)) $fields = '*';
$insert_stmt->execute([$role_id, $resource, $action, $fields]);
}
}
}
}
}
$pdo->commit();
$success_message = 'Permissions updated successfully!';
} catch (PDOException $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
$error_message = 'Database error: ' . $e->getMessage();
}
}
// Fetch data for display
$google_settings = [];
if ($is_super_admin) {
try {
$stmt = $pdo->query("SELECT setting_key, setting_value FROM settings WHERE setting_key IN ('google_client_id', 'google_client_secret')");
$google_settings_raw = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($google_settings_raw as $row) {
$google_settings[$row['setting_key']] = $row['setting_value'];
}
} catch (PDOException $e) {
$error_message = "Database error fetching Google settings: " . $e->getMessage();
}
}
try {
$roles_stmt = $pdo->query('SELECT * FROM roles ORDER BY name');
$roles = $roles_stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$error_message = "Database error fetching roles: " . $e->getMessage();
$roles = [];
}
$resources = ['asset', 'user', 'category', 'location', 'role', 'permission'];
$actions = ['create', 'read', 'update', 'delete'];
function get_permissions() {
try {
$pdo_func = db();
$stmt = $pdo_func->query('SELECT * FROM role_permissions ORDER BY role_id, resource, action');
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
return ['error' => 'Database error: ' . $e->getMessage()];
}
}
$permissions_from_db = get_permissions();
$grouped_permissions = [];
if (!isset($permissions_from_db['error'])) {
foreach ($permissions_from_db as $p) {
$grouped_permissions[$p['role_id']][$p['resource']][$p['action']] = $p['fields'];
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Settings - IC-Inventory</title>
<meta name="description" content="Manage application settings, including Google OAuth and role permissions.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>Settings</h1>
</div>
<div class="surface p-4">
<?php if ($success_message): ?>
<div class="alert alert-success"><?php echo htmlspecialchars($success_message); ?></div>
<?php endif; ?>
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<?php if ($is_super_admin): ?>
<div class="card mb-4">
<div class="card-header">
<h3>Google OAuth 2.0 Settings</h3>
</div>
<div class="card-body">
<p>These credentials are required for Google Login. You can get them from the <a href="https://console.cloud.google.com/apis/credentials" target="_blank">Google Cloud Console</a>.</p>
<p>The authorized redirect URI is: <code><?php echo htmlspecialchars(str_replace('settings.php', 'google-callback.php', (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'])); ?></code></p>
<form action="settings.php" method="post">
<div class="mb-3">
<label for="google_client_id" class="form-label">Client ID</label>
<input type="text" class="form-control" id="google_client_id" name="google_client_id" value="<?php echo htmlspecialchars($google_settings['google_client_id'] ?? ''); ?>">
</div>
<div class="mb-3">
<label for="google_client_secret" class="form-label">Client Secret</label>
<input type="password" class="form-control" id="google_client_secret" name="google_client_secret" value="<?php echo htmlspecialchars($google_settings['google_client_secret'] ?? ''); ?>">
</div>
<button type="submit" name="save_google_settings" class="btn btn-primary">Save Google Settings</button>
</form>
</div>
</div>
<?php endif; ?>
<?php if (can($_SESSION['user_role_id'], 'permission', 'update')): ?>
<div class="card">
<div class="card-header">
<h3>Role Permissions</h3>
</div>
<div class="card-body">
<form action="settings.php" method="post">
<div class="table-responsive">
<table class="table table-bordered permission-table">
<thead>
<tr>
<th>Role</th>
<th>Resource</th>
<th>Create</th>
<th>Read (Fields)</th>
<th>Update (Fields)</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<?php foreach ($roles as $role): ?>
<?php foreach ($resources as $resource_idx => $resource): ?>
<tr>
<?php if ($resource_idx === 0): ?>
<td rowspan="<?php echo count($resources); ?>" class="align-middle">
<strong><?php echo htmlspecialchars($role['name']); ?></strong>
<?php if ($role['id'] == 1): ?>
<span class="badge bg-primary">Super Admin</span>
<?php endif; ?>
</td>
<?php endif; ?>
<td class="align-middle"><?php echo ucfirst($resource); ?></td>
<?php foreach ($actions as $action):
$is_checked = isset($grouped_permissions[$role['id']][$resource]) && array_key_exists($action, $grouped_permissions[$role['id']][$resource]);
?>
<td class="align-middle">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[<?php echo $role['id']; ?>][<?php echo $resource; ?>][<?php echo $action; ?>][enabled]" value="1" <?php echo $is_checked ? 'checked' : ''; ?>>
</div>
<?php if (in_array($action, ['read', 'update', 'create'])): ?>
<input type="text" class="form-control form-control-sm mt-1" name="permissions[<?php echo $role['id']; ?>][<?php echo $resource; ?>][<?php echo $action; ?>][fields]" placeholder="* for all" value="<?php echo htmlspecialchars($grouped_permissions[$role['id']][$resource][$action] ?? '*'); ?>">
<?php endif; ?>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
<?php endforeach; ?>
</tbody>
</table>
</div>
<button type="submit" name="save_permissions" class="btn btn-primary">Save Permissions</button>
</form>
</div>
</div>
<?php endif; ?>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>

71
templates/sidebar.php Normal file
View File

@ -0,0 +1,71 @@
<?php
$current_page = basename($_SERVER['PHP_SELF']);
?>
<nav id="sidebar" class="d-flex flex-column flex-shrink-0 p-3">
<a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-decoration-none">
<span class="fs-4">IC-Inventory</span>
</a>
<hr>
<ul class="nav nav-pills flex-column mb-auto">
<li class="nav-item">
<a href="index.php" class="nav-link <?php echo ($current_page === 'index.php') ? 'active' : ''; ?>" aria-current="page">
<i data-feather="home" class="me-2"></i>
Dashboard
</a>
</li>
<li>
<a href="index.php" class="nav-link <?php echo ($current_page === 'add-asset.php' || $current_page === 'edit-asset.php') ? 'active' : ''; ?>">
<i data-feather="box" class="me-2"></i>
Assets
</a>
</li>
<?php if (can($_SESSION['user_role_id'], 'user', 'read')): ?>
<li>
<a href="users.php" class="nav-link <?php echo ($current_page === 'users.php' || $current_page === 'add-user.php' || $current_page === 'edit-user.php') ? 'active' : ''; ?>">
<i data-feather="users" class="me-2"></i>
Users
</a>
</li>
<?php endif; ?>
<?php if (can($_SESSION['user_role_id'], 'category', 'read')): ?>
<li>
<a href="categories.php" class="nav-link <?php echo ($current_page === 'categories.php' || $current_page === 'add-category.php' || $current_page === 'edit-category.php') ? 'active' : ''; ?>">
<i data-feather="grid" class="me-2"></i>
Categories
</a>
</li>
<?php endif; ?>
<?php if (can($_SESSION['user_role_id'], 'location', 'read')): ?>
<li>
<a href="locations.php" class="nav-link <?php echo ($current_page === 'locations.php' || $current_page === 'add-location.php' || $current_page === 'edit-location.php') ? 'active' : ''; ?>">
<i data-feather="map-pin" class="me-2"></i>
Locations
</a>
</li>
<?php endif; ?>
<?php if (can($_SESSION['user_role_id'], 'role', 'read')): ?>
<li>
<a href="roles.php" class="nav-link <?php echo ($current_page === 'roles.php' || $current_page === 'add-role.php' || $current_page === 'edit-role.php') ? 'active' : ''; ?>">
<i data-feather="shield" class="me-2"></i>
Roles
</a>
</li>
<?php endif; ?>
<?php if (can($_SESSION['user_role_id'], 'permission', 'update')): ?>
<li>
<a href="settings.php" class="nav-link <?php echo ($current_page === 'settings.php') ? 'active' : ''; ?>">
<i data-feather="settings" class="me-2"></i>
Settings
</a>
</li>
<?php endif; ?>
</ul>
<hr>
<div>
<span class="d-flex align-items-center text-decoration-none"><strong><?php echo htmlspecialchars($_SESSION['user_name']); ?></strong></span>
<a href="logout.php" class="d-flex align-items-center text-decoration-none">
<i data-feather="log-out" class="me-2"></i>
<strong>Logout</strong>
</a>
</div>
</nav>

165
users.php Normal file
View File

@ -0,0 +1,165 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'auth-check.php';
require_once 'auth-helpers.php';
// Only Admins can access this page
if (!can($_SESSION['user_role_id'], 'user', 'read')) {
header('Location: index.php?error=access_denied');
exit;
}
// Get allowed fields for the current user
$allowed_fields_str = can($_SESSION['user_role_id'], 'user', 'read');
$allowed_fields = ($allowed_fields_str && $allowed_fields_str !== '*') ? explode(',', $allowed_fields_str) : [];
if ($allowed_fields_str === '*') {
try {
$pdo = db();
$stmt = $pdo->query("SHOW COLUMNS FROM users");
$columns = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Exclude sensitive fields like password
$allowed_fields = array_diff($columns, ['password']);
} catch (PDOException $e) {
// Handle error, maybe default to a safe subset of fields
$allowed_fields = ['id', 'name', 'email', 'role'];
}
}
function get_users($fields) {
if (empty($fields)) {
return []; // No read permission
}
// Always include id for edit/delete links
if (!in_array('id', $fields)) {
$fields[] = 'id';
}
// Replace role_id with a join to get the role name
$select_parts = [];
foreach ($fields as $field) {
if ($field === 'role_id') {
$select_parts[] = 'r.name as role_name';
} else {
$select_parts[] = 'u.' . $field;
}
}
$select_fields = implode(', ', $select_parts);
try {
$pdo = db();
$sql = "SELECT $select_fields
FROM users u
LEFT JOIN roles r ON u.role_id = r.id
ORDER BY u.created_at DESC";
$stmt = $pdo->query($sql);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
return ['error' => 'Database error: ' . $e->getMessage()];
}
}
$users = get_users($allowed_fields);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>User Management - IC-Inventory</title>
<meta name="description" content="User management for IC-Inventory.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="wrapper">
<?php require_once 'templates/sidebar.php'; ?>
<main id="content">
<div class="header">
<h1>User Management</h1>
<div>
<?php if (can($_SESSION['user_role_id'], 'user', 'create')): ?>
<a href="add-user.php" class="btn btn-primary">Add New User</a>
<?php endif; ?>
<div class="theme-switcher" id="theme-switcher">
<i data-feather="moon"></i>
</div>
</div>
</div>
<?php if (isset($_GET['success']) && $_GET['success'] === 'user_added'): ?>
<div class="alert alert-success">User successfully added!</div>
<?php elseif (isset($_GET['success']) && $_GET['success'] === 'user_updated'): ?>
<div class="alert alert-success">User successfully updated!</div>
<?php elseif (isset($_GET['success']) && $_GET['success'] === 'user_deleted'): ?>
<div class="alert alert-success">User successfully deleted!</div>
<?php elseif (isset($_GET['error']) && $_GET['error'] === 'self_delete'): ?>
<div class="alert alert-danger">You cannot delete your own account.</div>
<?php endif; ?>
<div class="surface p-4">
<?php if (isset($users['error'])): ?>
<div class="alert alert-danger">
<?php echo htmlspecialchars($users['error']); ?>
</div>
<?php elseif (empty($users)): ?>
<div class="text-center p-5">
<h4>No users found.</h4>
<?php if (can($_SESSION['user_role_id'], 'user', 'create')): ?>
<p>Get started by adding your first user.</p>
<a href="add-user.php" class="btn btn-primary">Add User</a>
<?php endif; ?>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<?php foreach ($allowed_fields as $field): if($field === 'id') continue; ?>
<th><?php echo ucfirst(str_replace('_', ' ', ($field === 'role_id' ? 'role' : $field))); ?></th>
<?php endforeach; ?>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<?php foreach ($allowed_fields as $field): if($field === 'id') continue; ?>
<td><?php echo htmlspecialchars($user[$field === 'role_id' ? 'role_name' : $field]); ?></td>
<?php endforeach; ?>
<td>
<?php if (can($_SESSION['user_role_id'], 'user', 'update')): ?>
<a href="edit-user.php?id=<?php echo $user['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a>
<?php endif; ?>
<?php if (can($_SESSION['user_role_id'], 'user', 'delete')): ?>
<a href="delete-user.php?id=<?php echo $user['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to delete this user?');">Delete</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
feather.replace();
</script>
</body>
</html>