Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
378cb2cb10 | ||
|
|
eca44a4bc7 | ||
| 7c2c9d57f8 | |||
|
|
baf8947a57 | ||
|
|
17da95852f | ||
| c583d9b74d | |||
| e9a819c843 | |||
| 464f82e7b5 | |||
| ed44714581 | |||
| f253f90e1a | |||
| 57bad094bb | |||
| 1a9ee8902b | |||
| 5f697b63ac | |||
| 1a47db78cb | |||
| fd606fca99 | |||
| 9d1e967d34 | |||
| 77d4a0613d | |||
|
|
da4cd43fc4 | ||
|
|
732df2dbf0 | ||
|
|
1fa3995736 | ||
|
|
b95f5763dd | ||
|
|
2497a13797 | ||
|
|
28184556fb | ||
|
|
27581cde41 | ||
|
|
f083b9ac06 | ||
|
|
017eca9945 | ||
|
|
d25aef3090 | ||
|
|
49f989b22a | ||
|
|
f4d677bb9d | ||
|
|
5f68d7fe5b | ||
|
|
53eb27812c |
224
add-asset.php
Normal file
224
add-asset.php
Normal 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
94
add-category.php
Normal 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
94
add-location.php
Normal 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
94
add-role.php
Normal 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
137
add-user.php
Normal 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
125
assets/css/custom.css
Normal 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
22
assets/js/choices.js
Normal 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
17
assets/js/main.js
Normal 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
7
auth-check.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header("Location: login.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
30
auth-helpers.php
Normal file
30
auth-helpers.php
Normal 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
118
categories.php
Normal 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
5
cookie.txt
Normal 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
|
||||||
@ -7,11 +7,21 @@ define('DB_PASS', '2c66b530-2a65-423a-a106-6760b49ad1a2');
|
|||||||
|
|
||||||
function db() {
|
function db() {
|
||||||
static $pdo;
|
static $pdo;
|
||||||
if (!$pdo) {
|
if ($pdo) {
|
||||||
|
return $pdo;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
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;
|
return $pdo;
|
||||||
}
|
}
|
||||||
|
|||||||
27
db/migrations/001_create_assets_table.sql
Normal file
27
db/migrations/001_create_assets_table.sql
Normal 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');
|
||||||
14
db/migrations/002_create_users_table.sql
Normal file
14
db/migrations/002_create_users_table.sql
Normal 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'
|
||||||
40
db/migrations/003_create_permissions_table.sql
Normal file
40
db/migrations/003_create_permissions_table.sql
Normal 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');
|
||||||
5
db/migrations/004_create_categories_table.sql
Normal file
5
db/migrations/004_create_categories_table.sql
Normal 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;
|
||||||
2
db/migrations/005_add_category_id_to_assets.sql
Normal file
2
db/migrations/005_add_category_id_to_assets.sql
Normal 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;
|
||||||
5
db/migrations/006_create_locations_table.sql
Normal file
5
db/migrations/006_create_locations_table.sql
Normal 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
|
||||||
|
);
|
||||||
1
db/migrations/007_add_location_id_to_assets.sql
Normal file
1
db/migrations/007_add_location_id_to_assets.sql
Normal 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;
|
||||||
14
db/migrations/008_create_roles_table.sql
Normal file
14
db/migrations/008_create_roles_table.sql
Normal 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');
|
||||||
16
db/migrations/009_add_role_id_to_users.sql
Normal file
16
db/migrations/009_add_role_id_to_users.sql
Normal 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`;
|
||||||
22
db/migrations/010_update_role_permissions.sql
Normal file
22
db/migrations/010_update_role_permissions.sql
Normal 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;
|
||||||
1
db/migrations/011_add_permission_resource.sql
Normal file
1
db/migrations/011_add_permission_resource.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
INSERT INTO `role_permissions` (`role_id`, `resource`, `action`) VALUES (1, 'permission', 'update');
|
||||||
1
db/migrations/012_cleanup_permissions_table.sql
Normal file
1
db/migrations/012_cleanup_permissions_table.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS `permissions`;
|
||||||
12
db/migrations/013_grant_full_permissions_to_super_admin.sql
Normal file
12
db/migrations/013_grant_full_permissions_to_super_admin.sql
Normal 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
|
||||||
@ -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
|
||||||
11
db/migrations/015_create_settings_table.sql
Normal file
11
db/migrations/015_create_settings_table.sql
Normal 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
8
debug_tables.php
Normal 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
25
delete-asset.php
Normal 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
43
delete-category.php
Normal 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
43
delete-location.php
Normal 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
41
delete-role.php
Normal 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
37
delete-user.php
Normal 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
229
edit-asset.php
Normal 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
116
edit-category.php
Normal 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
116
edit-location.php
Normal 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
116
edit-role.php
Normal 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
158
edit-user.php
Normal 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
122
google-callback.php
Normal 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
30
google-login.php
Normal 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;
|
||||||
463
index.php
463
index.php
@ -1,150 +1,341 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
session_start();
|
||||||
@ini_set('display_errors', '1');
|
require_once 'db/config.php';
|
||||||
@error_reporting(E_ALL);
|
require_once 'auth-check.php';
|
||||||
@date_default_timezone_set('UTC');
|
require_once 'auth-helpers.php';
|
||||||
|
|
||||||
$phpVersion = PHP_VERSION;
|
// Store role name in session if not already set
|
||||||
$now = date('Y-m-d H:i:s');
|
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">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>New Style</title>
|
<title>IC-Inventory</title>
|
||||||
<?php
|
<meta name="description" content="Built with Flatlogic Generator">
|
||||||
// Read project preview data from environment
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||||
$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.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<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">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<style>
|
<script src="https://unpkg.com/feather-icons"></script>
|
||||||
: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>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
|
||||||
<div class="card">
|
<div class="wrapper">
|
||||||
<h1>Analyzing your requirements and generating your website…</h1>
|
<?php require_once 'templates/sidebar.php'; ?>
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
|
||||||
<span class="sr-only">Loading…</span>
|
<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>
|
||||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
</div>
|
||||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
</div>
|
||||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
|
||||||
|
<?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>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
</div>
|
||||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
|
||||||
</footer>
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
273
index.php.bak
Normal file
273
index.php.bak
Normal 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
118
locations.php
Normal 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
97
login.php
Normal 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
7
logout.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
session_unset();
|
||||||
|
session_destroy();
|
||||||
|
header("Location: login.php");
|
||||||
|
exit;
|
||||||
|
?>
|
||||||
126
roles.php
Normal file
126
roles.php
Normal 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
78
run-migrations.php
Normal 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
241
settings.php
Normal 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
71
templates/sidebar.php
Normal 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
165
users.php
Normal 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>
|
||||||
Loading…
x
Reference in New Issue
Block a user