dodanie csrf, oauth 2, dockerfile

Marcin Cieślikowski 3 weeks ago
parent 56c7ecff6d
commit df448ea7c1

@ -0,0 +1,7 @@
.git
.gitignore
node_modules
vendor
docker-compose.override.yml
source/uploads
mysql_data

@ -0,0 +1,15 @@
APP_NAME=Zeszyt 2.0
DB_PREFIX=xyz_
MYSQL_DATABASE=zeszyt
MYSQL_USER=zeszyt
MYSQL_PASSWORD=zeszyt
MYSQL_ROOT_PASSWORD=root
MICROSOFT_OAUTH_ENABLED=false
MICROSOFT_OAUTH_TENANT=common
MICROSOFT_OAUTH_CLIENT_ID=
MICROSOFT_OAUTH_CLIENT_SECRET=
MICROSOFT_OAUTH_REDIRECT_URI=http://localhost:8080/oauth_callback.php
MICROSOFT_OAUTH_ALLOWED_TENANT=
MICROSOFT_OAUTH_AUTO_PROVISION=false

@ -0,0 +1,24 @@
FROM php:8.2-apache
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libzip-dev \
libpng-dev \
libjpeg62-turbo-dev \
libfreetype6-dev \
libonig-dev \
unzip \
curl \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j"$(nproc)" pdo_mysql mysqli mbstring gd zip \
&& a2enmod rewrite headers \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /var/www/html
COPY source/ /var/www/html/
RUN mkdir -p /var/www/html/uploads \
&& chown -R www-data:www-data /var/www/html
EXPOSE 80

@ -79,8 +79,14 @@ CREATE TABLE `xyz_settings` (
CREATE TABLE `xyz_users` (
`id` int NOT NULL,
`username` varchar(50) NOT NULL,
`email` varchar(255) DEFAULT NULL,
`display_name` varchar(255) DEFAULT NULL,
`password` varchar(255) NOT NULL,
`role` enum('admin','user') DEFAULT 'user'
`role` enum('admin','user') DEFAULT 'user',
`oauth_provider` varchar(50) DEFAULT NULL,
`oauth_subject` varchar(191) DEFAULT NULL,
`oauth_tenant_id` varchar(64) DEFAULT NULL,
`last_login_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- --------------------------------------------------------
@ -106,7 +112,9 @@ ALTER TABLE `xyz_settings`
ALTER TABLE `xyz_users`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `username` (`username`);
ADD UNIQUE KEY `username` (`username`),
ADD UNIQUE KEY `uniq_users_email` (`email`),
ADD KEY `idx_users_oauth_identity` (`oauth_provider`,`oauth_subject`,`oauth_tenant_id`);
--
-- AUTO_INCREMENT dla zrzuconych tabel

@ -0,0 +1,55 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: zeszyt_app
depends_on:
db:
condition: service_healthy
ports:
- "8080:80"
environment:
APP_NAME: "${APP_NAME:-Zeszyt 2.0}"
DB_HOST: db
DB_NAME: "${MYSQL_DATABASE:-zeszyt}"
DB_USER: "${MYSQL_USER:-zeszyt}"
DB_PASS: "${MYSQL_PASSWORD:-zeszyt}"
DB_PREFIX: "${DB_PREFIX:-xyz_}"
MICROSOFT_OAUTH_ENABLED: "${MICROSOFT_OAUTH_ENABLED:-false}"
MICROSOFT_OAUTH_TENANT: "${MICROSOFT_OAUTH_TENANT:-common}"
MICROSOFT_OAUTH_CLIENT_ID: "${MICROSOFT_OAUTH_CLIENT_ID:-}"
MICROSOFT_OAUTH_CLIENT_SECRET: "${MICROSOFT_OAUTH_CLIENT_SECRET:-}"
MICROSOFT_OAUTH_REDIRECT_URI: "${MICROSOFT_OAUTH_REDIRECT_URI:-http://localhost:8080/oauth_callback.php}"
MICROSOFT_OAUTH_ALLOWED_TENANT: "${MICROSOFT_OAUTH_ALLOWED_TENANT:-}"
MICROSOFT_OAUTH_AUTO_PROVISION: "${MICROSOFT_OAUTH_AUTO_PROVISION:-false}"
volumes:
- ./source:/var/www/html
- uploads_data:/var/www/html/uploads
restart: unless-stopped
db:
image: mysql:8.0
container_name: zeszyt_db
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_DATABASE: "${MYSQL_DATABASE:-zeszyt}"
MYSQL_USER: "${MYSQL_USER:-zeszyt}"
MYSQL_PASSWORD: "${MYSQL_PASSWORD:-zeszyt}"
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:-root}"
ports:
- "3307:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./db/zeszyt.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u${MYSQL_USER:-zeszyt}", "-p${MYSQL_PASSWORD:-zeszyt}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
restart: unless-stopped
volumes:
mysql_data:
uploads_data:

@ -5,18 +5,20 @@ checkAuth();
$message = '';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$product_name = trim($_POST['product_name']);
$quantity = (int)$_POST['quantity'];
$purchase_place = trim($_POST['purchase_place']);
$price = (float)str_replace(',', '.', $_POST['price_per_unit']);
$delivery_date = $_POST['delivery_date'];
$notes = trim($_POST['notes']);
$recipient = trim($_POST['recipient']);
$delivery_address = trim($_POST['delivery_address']);
$company = trim($_POST['company'] ?? ''); // NOWE POLE
if ($_SERVER["REQUEST_METHOD"] === "POST") {
requireCsrfToken();
if (!empty($product_name) && $quantity > 0) {
$product_name = trim($_POST['product_name'] ?? '');
$quantity = (int)($_POST['quantity'] ?? 0);
$purchase_place = trim($_POST['purchase_place'] ?? '');
$price = (float)str_replace(',', '.', $_POST['price_per_unit'] ?? '0');
$delivery_date = $_POST['delivery_date'] ?? '';
$notes = trim($_POST['notes'] ?? '');
$recipient = trim($_POST['recipient'] ?? '');
$delivery_address = trim($_POST['delivery_address'] ?? '');
$company = trim($_POST['company'] ?? '');
if ($product_name !== '' && $quantity > 0) {
try {
$pdo->beginTransaction();
@ -30,17 +32,17 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
$hist_sql = "INSERT INTO " . DB_PREFIX . "order_history (order_id, user_id, action) VALUES (?, ?, ?)";
$hist_stmt = $pdo->prepare($hist_sql);
$hist_stmt->execute([$order_id, $_SESSION['user_id'], 'Utworzono nowe zamówienie.']);
$hist_stmt->execute([$order_id, $_SESSION['user_id'], 'Utworzono nowe zamowienie.']);
$pdo->commit();
$message = "<div class='alert alert-success'>Zamówienie dodane pomyślnie!</div>";
$message = "<div class='alert alert-success'>Zamowienie dodane pomyslnie.</div>";
} catch (PDOException $e) {
$pdo->rollBack();
$message = "<div class='alert alert-danger'>Błąd: " . $e->getMessage() . "</div>";
error_log($e->getMessage());
$message = "<div class='alert alert-danger'>Nie udalo sie zapisac zamowienia.</div>";
}
} else {
$message = "<div class='alert alert-warning'>Wypełnij nazwę produktu i ilość.</div>";
$message = "<div class='alert alert-warning'>Wypelnij nazwe produktu i ilosc.</div>";
}
}
?>
@ -49,7 +51,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>Dodaj zamówienie - <?php echo APP_NAME; ?></title>
<title>Dodaj zamowienie - <?php echo e(APP_NAME); ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
</head>
@ -57,18 +59,19 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
<div class="container py-5">
<div class="card shadow mx-auto" style="max-width: 600px;">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="bi bi-plus-circle"></i> Nowe zamówienie</h5>
<h5 class="mb-0"><i class="bi bi-plus-circle"></i> Nowe zamowienie</h5>
</div>
<div class="card-body">
<?php echo $message; ?>
<form method="POST">
<?php echo csrfInput(); ?>
<div class="mb-3 p-2 bg-success bg-opacity-10 border border-success rounded">
<label class="form-label small fw-bold text-success"><i class="bi bi-building"></i> Firma kupująca</label>
<label class="form-label small fw-bold text-success"><i class="bi bi-building"></i> Firma kupujaca</label>
<select name="company" class="form-select border-success">
<option value="">Wybierz firmę...</option>
<option value="Przedsiębiorstwo">Przedsiębiorstwo</option>
<option value="Spółka">Spółka</option>
<option value="">Wybierz firme...</option>
<option value="Przedsiebiorstwo">Przedsiebiorstwo</option>
<option value="Spolka">Spolka</option>
</select>
</div>
@ -78,14 +81,14 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold">Ilość *</label>
<label class="form-label small fw-bold">Ilosc *</label>
<input type="number" name="quantity" class="form-control" value="1" min="1" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold">Cena za sztukę</label>
<label class="form-label small fw-bold">Cena za sztuke</label>
<div class="input-group">
<input type="text" name="price_per_unit" class="form-control" placeholder="0.00">
<span class="input-group-text">zł</span>
<span class="input-group-text">zl</span>
</div>
</div>
</div>
@ -115,8 +118,8 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
</div>
<div class="d-flex justify-content-between">
<a href="index.php" class="btn btn-secondary">Powrót do listy</a>
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg"></i> Zapisz zamówienie</button>
<a href="index.php" class="btn btn-secondary">Powrot do listy</a>
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg"></i> Zapisz zamowienie</button>
</div>
</form>
</div>

@ -3,56 +3,56 @@ require_once 'includes/db.php';
require_once 'includes/auth.php';
checkAuth();
checkAdmin(); // Tylko admin tu wejdzie
checkAdmin();
$message = '';
// ---------------------------------------------------------
// 0. INICJALIZACJA DOMYŚLNYCH USTAWIEŃ FIRM (jeśli nie istnieją)
// ---------------------------------------------------------
$default_settings = [
'company_1_name' => 'Moje Przedsiębiorstwo IT',
'company_1_details' => 'ul. Główna 1, 00-000 Warszawa | NIP: 0987654321',
'company_2_name' => 'Spółka Celowa Sp. z o.o.',
'company_2_details' => 'ul. Przykładowa 10, 00-000 Warszawa | NIP: 1234567890 | KRS: 0000123456'
'company_1_name' => 'Moje Przedsiebiorstwo IT',
'company_1_details' => 'ul. Glowna 1, 00-000 Warszawa | NIP: 0987654321',
'company_2_name' => 'Spolka Celowa Sp. z o.o.',
'company_2_details' => 'ul. Przykladowa 10, 00-000 Warszawa | NIP: 1234567890 | KRS: 0000123456'
];
foreach ($default_settings as $key => $val) {
// INSERT IGNORE dodaje wpis tylko wtedy, gdy taki klucz jeszcze nie istnieje w tabeli
$stmt = $pdo->prepare("INSERT IGNORE INTO " . DB_PREFIX . "settings (setting_key, setting_value) VALUES (?, ?)");
$stmt->execute([$key, $val]);
}
// ---------------------------------------------------------
// 1. WGRYWANIE LOGO
// ---------------------------------------------------------
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES['logo'])) {
if ($_SERVER["REQUEST_METHOD"] === "POST") {
requireCsrfToken();
if (isset($_FILES['logo'])) {
if ($_FILES['logo']['error'] === 0) {
$upload_dir = 'uploads/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0777, true);
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0755, true);
}
$tmpPath = $_FILES['logo']['tmp_name'];
$mime = mime_content_type($tmpPath);
$allowedMimes = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
];
$file_ext = strtolower(pathinfo($_FILES['logo']['name'], PATHINFO_EXTENSION));
if(in_array($file_ext, ['jpg', 'jpeg', 'png', 'gif', 'svg'])) {
$new_name = 'logo_' . time() . '.' . $file_ext;
if (isset($allowedMimes[$mime]) && @getimagesize($tmpPath) !== false) {
$new_name = 'logo_' . bin2hex(random_bytes(8)) . '.' . $allowedMimes[$mime];
$dest_path = $upload_dir . $new_name;
if (move_uploaded_file($_FILES['logo']['tmp_name'], $dest_path)) {
if (move_uploaded_file($tmpPath, $dest_path)) {
$stmt = $pdo->prepare("REPLACE INTO " . DB_PREFIX . "settings (setting_key, setting_value) VALUES ('logo_path', ?)");
$stmt->execute([$dest_path]);
$message = "<div class='alert alert-success'>Pomyślnie zaktualizowano logo!</div>";
$message = "<div class='alert alert-success'>Pomyslnie zaktualizowano logo.</div>";
} else {
$message = "<div class='alert alert-danger'>Błąd: Nie udało się zapisać pliku. Sprawdź uprawnienia folderu 'uploads'.</div>";
$message = "<div class='alert alert-danger'>Nie udalo sie zapisac pliku.</div>";
}
} else {
$message = "<div class='alert alert-warning'>Błąd: Dozwolone są tylko pliki graficzne (JPG, PNG, GIF, SVG).</div>";
}
$message = "<div class='alert alert-warning'>Dozwolone sa tylko prawidlowe pliki JPG, PNG i GIF.</div>";
}
}
// ---------------------------------------------------------
// 1B. ZAPISYWANIE DANYCH FIRM
// ---------------------------------------------------------
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_company_settings') {
} elseif (isset($_POST['action']) && $_POST['action'] === 'update_company_settings') {
$settings_to_update = ['company_1_name', 'company_1_details', 'company_2_name', 'company_2_details'];
try {
@ -65,72 +65,58 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
}
}
$pdo->commit();
$message = "<div class='alert alert-success'>Ustawienia firmowe zostały zapisane!</div>";
$message = "<div class='alert alert-success'>Ustawienia firmowe zostaly zapisane.</div>";
} catch (PDOException $e) {
$pdo->rollBack();
$message = "<div class='alert alert-danger'>Błąd zapisu: " . $e->getMessage() . "</div>";
error_log($e->getMessage());
$message = "<div class='alert alert-danger'>Nie udalo sie zapisac ustawien.</div>";
}
}
// ---------------------------------------------------------
// 2. DODAWANIE UŻYTKOWNIKA
// ---------------------------------------------------------
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['add_user'])) {
$new_user = trim($_POST['new_username']);
$new_pass = $_POST['new_password'];
$new_role = $_POST['new_role'];
} elseif (isset($_POST['add_user'])) {
$new_user = trim($_POST['new_username'] ?? '');
$new_pass = $_POST['new_password'] ?? '';
$new_role = $_POST['new_role'] ?? 'user';
if (!empty($new_user) && !empty($new_pass)) {
if ($new_user !== '' && $new_pass !== '') {
try {
$new_email = trim($_POST['new_email'] ?? '');
$hashed_pass = password_hash($new_pass, PASSWORD_BCRYPT);
$stmt = $pdo->prepare("INSERT INTO " . DB_PREFIX . "users (username, password, role) VALUES (?, ?, ?)");
$stmt->execute([$new_user, $hashed_pass, $new_role]);
$message = "<div class='alert alert-success'>Dodano nowego użytkownika: <strong>$new_user</strong></div>";
$stmt = $pdo->prepare("INSERT INTO " . DB_PREFIX . "users (username, email, password, role) VALUES (?, ?, ?, ?)");
$stmt->execute([$new_user, $new_email !== '' ? $new_email : null, $hashed_pass, $new_role === 'admin' ? 'admin' : 'user']);
$message = "<div class='alert alert-success'>Dodano nowego uzytkownika: <strong>" . e($new_user) . "</strong></div>";
} catch (PDOException $e) {
$message = "<div class='alert alert-danger'>Błąd: Użytkownik o takiej nazwie prawdopodobnie już istnieje.</div>";
}
error_log($e->getMessage());
$message = "<div class='alert alert-danger'>Nie udalo sie dodac uzytkownika.</div>";
}
}
} elseif (isset($_POST['reset_password'])) {
$user_id = (int)($_POST['user_id'] ?? 0);
$new_pass = $_POST['new_password'] ?? '';
// ---------------------------------------------------------
// 3. RESET HASŁA UŻYTKOWNIKA
// ---------------------------------------------------------
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['reset_password'])) {
$user_id = (int)$_POST['user_id'];
$new_pass = $_POST['new_password'];
if (!empty($new_pass)) {
if ($user_id > 0 && $new_pass !== '') {
$hashed_pass = password_hash($new_pass, PASSWORD_BCRYPT);
$stmt = $pdo->prepare("UPDATE " . DB_PREFIX . "users SET password = ? WHERE id = ?");
$stmt->execute([$hashed_pass, $user_id]);
$message = "<div class='alert alert-info'>Zmieniono hasło dla wybranego użytkownika.</div>";
}
$message = "<div class='alert alert-info'>Zmieniono haslo dla wybranego uzytkownika.</div>";
}
// ---------------------------------------------------------
// 4. USUWANIE UŻYTKOWNIKA
// ---------------------------------------------------------
if (isset($_GET['delete'])) {
$id_to_delete = (int)$_GET['delete'];
if ($id_to_delete != $_SESSION['user_id']) {
} elseif (isset($_POST['delete_user'])) {
$id_to_delete = (int)($_POST['user_id'] ?? 0);
if ($id_to_delete !== (int)$_SESSION['user_id']) {
$stmt = $pdo->prepare("DELETE FROM " . DB_PREFIX . "users WHERE id = ?");
$stmt->execute([$id_to_delete]);
$message = "<div class='alert alert-success'>Użytkownik został pomyślnie usunięty.</div>";
$message = "<div class='alert alert-success'>Uzytkownik zostal pomyslnie usuniety.</div>";
} else {
$message = "<div class='alert alert-warning'>Odmowa: Nie możesz usunąć własnego konta administratora!</div>";
$message = "<div class='alert alert-warning'>Nie mozesz usunac wlasnego konta administratora.</div>";
}
}
}
// ---------------------------------------------------------
// POBIERANIE DANYCH DO WIDOKU
// ---------------------------------------------------------
$users = $pdo->query("SELECT id, username, role FROM " . DB_PREFIX . "users")->fetchAll();
$users = $pdo->query("SELECT id, username, email, role, oauth_provider, last_login_at FROM " . DB_PREFIX . "users ORDER BY username ASC")->fetchAll();
$settings = [];
try {
$settings = $pdo->query("SELECT setting_key, setting_value FROM " . DB_PREFIX . "settings")->fetchAll(PDO::FETCH_KEY_PAIR);
} catch (PDOException $e) {
// Zabezpieczenie na wypadek awarii tabeli settings
error_log($e->getMessage());
}
$current_logo = $settings['logo_path'] ?? '';
@ -141,7 +127,7 @@ $current_logo = $settings['logo_path'] ?? '';
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Panel Administratora - <?php echo defined('APP_NAME') ? APP_NAME : 'System'; ?></title>
<title>Panel administratora - <?php echo e(defined('APP_NAME') ? APP_NAME : 'System'); ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<style>
@ -155,19 +141,19 @@ $current_logo = $settings['logo_path'] ?? '';
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="index.php">
<?php if ($current_logo && file_exists($current_logo)): ?>
<img src="<?php echo $current_logo; ?>" alt="Logo" class="logo-img me-2">
<img src="<?php echo e($current_logo); ?>" alt="Logo" class="logo-img me-2">
<?php else: ?>
<span class="me-2">💻</span>
<span class="me-2">IT</span>
<?php endif; ?>
<strong><?php echo defined('APP_NAME') ? APP_NAME : 'System IT'; ?></strong>
<strong><?php echo e(defined('APP_NAME') ? APP_NAME : 'System IT'); ?></strong>
</a>
<div class="navbar-nav ms-auto align-items-center">
<button class="btn btn-sm btn-outline-secondary me-3" onclick="toggleDarkMode()">
<i class="bi bi-moon-stars" id="themeIcon"></i>
</button>
<span class="nav-link text-light me-3 small">Admin: <strong><?php echo $_SESSION['username']; ?></strong></span>
<a class="btn btn-outline-light btn-sm me-2" href="index.php"><i class="bi bi-arrow-left"></i> Powrót do zamówień</a>
<span class="nav-link text-light me-3 small">Admin: <strong><?php echo e($_SESSION['username']); ?></strong></span>
<a class="btn btn-outline-light btn-sm me-2" href="index.php"><i class="bi bi-arrow-left"></i> Powrot do zamowien</a>
<a class="btn btn-outline-danger btn-sm" href="logout.php">Wyloguj</a>
</div>
</div>
@ -175,30 +161,30 @@ $current_logo = $settings['logo_path'] ?? '';
<div class="container pb-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Panel Zarządzania Systemem</h2>
<h2>Panel zarzadzania systemem</h2>
</div>
<?php echo $message; ?>
<div class="row">
<div class="col-md-5 mb-4">
<div class="card shadow-sm mb-4 border-0">
<div class="card-header bg-secondary text-white">
<i class="bi bi-image"></i> Ustawienia Wizualne
<i class="bi bi-image"></i> Ustawienia wizualne
</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data">
<?php echo csrfInput(); ?>
<div class="text-center mb-3">
<?php if ($current_logo && file_exists($current_logo)): ?>
<img src="<?php echo $current_logo; ?>" class="img-thumbnail" style="max-height: 80px;">
<img src="<?php echo e($current_logo); ?>" class="img-thumbnail" style="max-height: 80px;" alt="Logo">
<?php else: ?>
<div class="text-muted small">Brak wgranego logo</div>
<?php endif; ?>
</div>
<div class="mb-3">
<label class="form-label small">Zmień logotyp (PNG/JPG)</label>
<input type="file" name="logo" class="form-control form-control-sm" accept="image/*" required>
<label class="form-label small">Zmien logotyp (PNG/JPG/GIF)</label>
<input type="file" name="logo" class="form-control form-control-sm" accept=".png,.jpg,.jpeg,.gif" required>
</div>
<button type="submit" class="btn btn-dark w-100 btn-sm">Wgraj plik na serwer</button>
</form>
@ -207,28 +193,29 @@ $current_logo = $settings['logo_path'] ?? '';
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-info text-dark fw-bold">
<i class="bi bi-buildings"></i> Dane firm (Stopki wydruków)
<i class="bi bi-buildings"></i> Dane firm
</div>
<div class="card-body">
<form method="POST">
<?php echo csrfInput(); ?>
<input type="hidden" name="action" value="update_company_settings">
<div class="mb-3">
<label class="form-label small fw-bold text-primary">1. Przedsiębiorstwo (Nazwa)</label>
<input type="text" name="company_1_name" class="form-control form-control-sm border-primary" value="<?php echo htmlspecialchars($settings['company_1_name'] ?? ''); ?>" required>
<label class="form-label small fw-bold text-primary">1. Przedsiebiorstwo (Nazwa)</label>
<input type="text" name="company_1_name" class="form-control form-control-sm border-primary" value="<?php echo e($settings['company_1_name'] ?? ''); ?>" required>
</div>
<div class="mb-4">
<label class="form-label small fw-bold text-primary">Przedsiębiorstwo (Adres/NIP)</label>
<input type="text" name="company_1_details" class="form-control form-control-sm" value="<?php echo htmlspecialchars($settings['company_1_details'] ?? ''); ?>">
<label class="form-label small fw-bold text-primary">Przedsiebiorstwo (Adres/NIP)</label>
<input type="text" name="company_1_details" class="form-control form-control-sm" value="<?php echo e($settings['company_1_details'] ?? ''); ?>">
</div>
<div class="mb-3 border-top pt-3">
<label class="form-label small fw-bold text-success">2. Spółka (Nazwa)</label>
<input type="text" name="company_2_name" class="form-control form-control-sm border-success" value="<?php echo htmlspecialchars($settings['company_2_name'] ?? ''); ?>" required>
<label class="form-label small fw-bold text-success">2. Spolka (Nazwa)</label>
<input type="text" name="company_2_name" class="form-control form-control-sm border-success" value="<?php echo e($settings['company_2_name'] ?? ''); ?>" required>
</div>
<div class="mb-4">
<label class="form-label small fw-bold text-success">Spółka (Adres/NIP/KRS)</label>
<input type="text" name="company_2_details" class="form-control form-control-sm" value="<?php echo htmlspecialchars($settings['company_2_details'] ?? ''); ?>">
<label class="form-label small fw-bold text-success">Spolka (Adres/NIP/KRS)</label>
<input type="text" name="company_2_details" class="form-control form-control-sm" value="<?php echo e($settings['company_2_details'] ?? ''); ?>">
</div>
<button type="submit" class="btn btn-info w-100 btn-sm fw-bold text-dark"><i class="bi bi-save"></i> Zapisz stopki firm</button>
@ -242,26 +229,30 @@ $current_logo = $settings['logo_path'] ?? '';
</div>
<div class="card-body">
<form method="POST">
<?php echo csrfInput(); ?>
<div class="mb-3">
<label class="form-label small">Login użytkownika</label>
<label class="form-label small">Login uzytkownika</label>
<input type="text" name="new_username" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label small">Hasło początkowe</label>
<input type="password" name="new_password" class="form-control" required>
<label class="form-label small">Haslo poczatkowe</label>
<input type="password" name="new_password" class="form-control" required minlength="8">
</div>
<div class="mb-3">
<label class="form-label small">Email do powiazania z Microsoft 365</label>
<input type="email" name="new_email" class="form-control" placeholder="np. user@firma.pl">
</div>
<div class="mb-3">
<label class="form-label small">Rola w systemie</label>
<select name="new_role" class="form-select">
<option value="user">Zwykły pracownik</option>
<option value="user">Zwykly pracownik</option>
<option value="admin">Administrator</option>
</select>
</div>
<button type="submit" name="add_user" class="btn btn-primary w-100"><i class="bi bi-check2-circle"></i> Utwórz konto</button>
<button type="submit" name="add_user" class="btn btn-primary w-100"><i class="bi bi-check2-circle"></i> Utworz konto</button>
</form>
</div>
</div>
</div>
<div class="col-md-7">
@ -276,49 +267,62 @@ $current_logo = $settings['logo_path'] ?? '';
<tr>
<th class="px-3">ID</th>
<th>Login</th>
<th>Email</th>
<th>Uprawnienia</th>
<th class="text-end px-4">Zarządzaj</th>
<th>Zrodlo</th>
<th class="text-end px-4">Zarzadzaj</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $u): ?>
<tr>
<td class="px-3 text-muted"><?php echo $u['id']; ?></td>
<td><strong><?php echo htmlspecialchars($u['username']); ?></strong></td>
<td class="px-3 text-muted"><?php echo (int)$u['id']; ?></td>
<td><strong><?php echo e($u['username']); ?></strong></td>
<td class="small text-muted"><?php echo e($u['email'] ?? ''); ?></td>
<td>
<span class="badge <?php echo $u['role'] == 'admin' ? 'bg-danger' : 'bg-primary'; ?>">
<?php echo $u['role'] == 'admin' ? 'Administrator' : 'Użytkownik'; ?>
<span class="badge <?php echo $u['role'] === 'admin' ? 'bg-danger' : 'bg-primary'; ?>">
<?php echo $u['role'] === 'admin' ? 'Administrator' : 'Uzytkownik'; ?>
</span>
</td>
<td>
<span class="badge <?php echo $u['oauth_provider'] === 'microsoft' ? 'bg-success' : 'bg-secondary'; ?>">
<?php echo $u['oauth_provider'] === 'microsoft' ? 'Microsoft 365' : 'Lokalne'; ?>
</span>
</td>
<td class="text-end px-3">
<button class="btn btn-sm btn-outline-info me-1" data-bs-toggle="modal" data-bs-target="#resetModal<?php echo $u['id']; ?>">
<i class="bi bi-shield-lock"></i> Reset hasła
<button class="btn btn-sm btn-outline-info me-1" data-bs-toggle="modal" data-bs-target="#resetModal<?php echo (int)$u['id']; ?>">
<i class="bi bi-shield-lock"></i> Reset hasla
</button>
<?php if ($u['id'] != $_SESSION['user_id']): ?>
<a href="?delete=<?php echo $u['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Czy na pewno chcesz bezpowrotnie usunąć to konto?')">
<i class="bi bi-trash"></i> Usuń
</a>
<?php if ((int)$u['id'] !== (int)$_SESSION['user_id']): ?>
<form method="POST" class="d-inline" onsubmit="return confirm('Czy na pewno chcesz bezpowrotnie usunac to konto?')">
<?php echo csrfInput(); ?>
<input type="hidden" name="user_id" value="<?php echo (int)$u['id']; ?>">
<button type="submit" name="delete_user" class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i> Usun
</button>
</form>
<?php else: ?>
<button class="btn btn-sm btn-outline-secondary disabled"><i class="bi bi-person-check"></i> To ty</button>
<?php endif; ?>
<div class="modal fade" id="resetModal<?php echo $u['id']; ?>" tabindex="-1" aria-hidden="true">
<div class="modal fade" id="resetModal<?php echo (int)$u['id']; ?>" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-sm modal-dialog-centered">
<div class="modal-content text-start">
<form method="POST">
<?php echo csrfInput(); ?>
<div class="modal-header bg-light">
<h6 class="modal-title"><i class="bi bi-key"></i> Nowe hasło: <?php echo $u['username']; ?></h6>
<h6 class="modal-title"><i class="bi bi-key"></i> Nowe haslo: <?php echo e($u['username']); ?></h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" name="user_id" value="<?php echo $u['id']; ?>">
<label class="form-label small">Wpisz nowe hasło:</label>
<input type="password" name="new_password" class="form-control" required minlength="4">
<input type="hidden" name="user_id" value="<?php echo (int)$u['id']; ?>">
<label class="form-label small">Wpisz nowe haslo:</label>
<input type="password" name="new_password" class="form-control" required minlength="8">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Anuluj</button>
<button type="submit" name="reset_password" class="btn btn-info btn-sm text-white">Zapisz hasło</button>
<button type="submit" name="reset_password" class="btn btn-info btn-sm text-white">Zapisz haslo</button>
</div>
</form>
</div>

@ -10,6 +10,11 @@ if (!$data || empty($data['action']) || empty($data['id'])) {
die(json_encode(['success' => false, 'error' => 'Brak danych']));
}
if (!isValidCsrfToken($data['csrf_token'] ?? null)) {
http_response_code(403);
die(json_encode(['success' => false, 'error' => 'Nieprawidlowy token bezpieczenstwa']));
}
$id = (int)$data['id'];
$action = $data['action'];
@ -61,6 +66,7 @@ try {
echo json_encode(['success' => false, 'error' => 'Nieznana akcja']);
}
} catch (PDOException $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
error_log($e->getMessage());
echo json_encode(['success' => false, 'error' => 'Wystapil blad serwera']);
}
?>

@ -10,6 +10,11 @@ if (!$data || empty(trim($data['product_name']))) {
die(json_encode(['success' => false, 'error' => 'Nazwa produktu jest wymagana!']));
}
if (!isValidCsrfToken($data['csrf_token'] ?? null)) {
http_response_code(403);
die(json_encode(['success' => false, 'error' => 'Nieprawidlowy token bezpieczenstwa']));
}
try {
$pdo->beginTransaction();
@ -34,6 +39,7 @@ try {
echo json_encode(['success' => true]);
} catch (PDOException $e) {
$pdo->rollBack();
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
error_log($e->getMessage());
echo json_encode(['success' => false, 'error' => 'Wystapil blad serwera']);
}
?>

@ -10,6 +10,11 @@ if (!$data || !isset($data['id'], $data['field'], $data['value'])) {
die(json_encode(['success' => false, 'error' => 'Brak danych']));
}
if (!isValidCsrfToken($data['csrf_token'] ?? null)) {
http_response_code(403);
die(json_encode(['success' => false, 'error' => 'Nieprawidlowy token bezpieczenstwa']));
}
$id = (int)$data['id'];
$field = $data['field'];
$value = trim($data['value']);
@ -58,6 +63,7 @@ try {
echo json_encode(['success' => true]);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
error_log($e->getMessage());
echo json_encode(['success' => false, 'error' => 'Wystapil blad serwera']);
}
?>

@ -5,27 +5,28 @@ checkAuth();
$message = '';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$old_pass = $_POST['old_password'];
$new_pass = $_POST['new_password'];
$confirm_pass = $_POST['confirm_password'];
if ($_SERVER["REQUEST_METHOD"] === "POST") {
requireCsrfToken();
$old_pass = $_POST['old_password'] ?? '';
$new_pass = $_POST['new_password'] ?? '';
$confirm_pass = $_POST['confirm_password'] ?? '';
if ($new_pass !== $confirm_pass) {
$message = '<div class="alert alert-danger">Nowe hasła nie są identyczne!</div>';
$message = '<div class="alert alert-danger">Nowe hasla nie sa identyczne.</div>';
} else {
// Pobierz aktualne hasło z bazy
$stmt = $pdo->prepare("SELECT password FROM " . DB_PREFIX . "users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch();
if (password_verify($old_pass, $user['password'])) {
// Hasło poprawne - aktualizujemy
if ($user && password_verify($old_pass, $user['password'])) {
$new_hash = password_hash($new_pass, PASSWORD_BCRYPT);
$update = $pdo->prepare("UPDATE " . DB_PREFIX . "users SET password = ? WHERE id = ?");
$update->execute([$new_hash, $_SESSION['user_id']]);
$message = '<div class="alert alert-success">Hasło zostało zmienione pomyślnie!</div>';
session_regenerate_id(true);
$message = '<div class="alert alert-success">Haslo zostalo zmienione pomyslnie.</div>';
} else {
$message = '<div class="alert alert-danger">Obecne hasło jest nieprawidłowe.</div>';
$message = '<div class="alert alert-danger">Obecne haslo jest nieprawidlowe.</div>';
}
}
}
@ -35,34 +36,35 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>Zmiana hasła - <?php echo APP_NAME; ?></title>
<title>Zmiana hasla - <?php echo e(APP_NAME); ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container py-5">
<div class="card shadow mx-auto" style="max-width: 450px;">
<div class="card-header bg-dark text-white">
<h5 class="mb-0">Zmiana hasła</h5>
<h5 class="mb-0">Zmiana hasla</h5>
</div>
<div class="card-body">
<?php echo $message; ?>
<form method="POST">
<?php echo csrfInput(); ?>
<div class="mb-3">
<label class="form-label">Obecne hasło</label>
<label class="form-label">Obecne haslo</label>
<input type="password" name="old_password" class="form-control" required>
</div>
<hr>
<div class="mb-3">
<label class="form-label">Nowe hasło</label>
<input type="password" name="new_password" class="form-control" required minlength="5">
<label class="form-label">Nowe haslo</label>
<input type="password" name="new_password" class="form-control" required minlength="8">
</div>
<div class="mb-3">
<label class="form-label">Powtórz nowe hasło</label>
<label class="form-label">Powtorz nowe haslo</label>
<input type="password" name="confirm_password" class="form-control" required>
</div>
<div class="d-flex justify-content-between">
<a href="index.php" class="btn btn-secondary">Powrót</a>
<button type="submit" class="btn btn-primary">Zmień hasło</button>
<a href="index.php" class="btn btn-secondary">Powrot</a>
<button type="submit" class="btn btn-primary">Zmien haslo</button>
</div>
</form>
</div>

@ -16,6 +16,7 @@ if (!$order) {
// 1. OBSŁUGA ZMIANY STATUSU ARCHIWUM
if (isset($_POST['archive_action'])) {
requireCsrfToken();
$new_archive_status = $order['is_archived'] ? 0 : 1;
$update_arch = $pdo->prepare("UPDATE " . DB_PREFIX . "orders SET is_archived = ? WHERE id = ?");
@ -33,6 +34,7 @@ if (isset($_POST['archive_action'])) {
// 2. OBSŁUGA DODAWANIA KOMENTARZA
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['add_comment'])) {
requireCsrfToken();
$comment_text = trim($_POST['comment_text'] ?? '');
if (!empty($comment_text)) {
@ -51,6 +53,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['add_comment'])) {
// 3. OBSŁUGA STANDARDOWEJ EDYCJI
if ($_SERVER["REQUEST_METHOD"] == "POST" && !isset($_POST['archive_action']) && !isset($_POST['add_comment'])) {
requireCsrfToken();
$product_name = trim($_POST['product_name'] ?? '');
$part_number = trim($_POST['part_number'] ?? '');
$quantity = (int)$_POST['quantity'];
@ -170,6 +173,7 @@ $comments = $comm_stmt->fetchAll();
<div class="card-body">
<?php echo $message; ?>
<form method="POST">
<?php echo csrfInput(); ?>
<div class="mb-3 p-2 bg-success bg-opacity-10 border border-success rounded">
<label class="form-label small fw-bold text-success"><i class="bi bi-building"></i> Firma kupująca</label>
@ -262,6 +266,7 @@ $comments = $comm_stmt->fetchAll();
</div>
<div class="card-body bg-light">
<form method="POST" class="mb-3">
<?php echo csrfInput(); ?>
<div class="input-group shadow-sm">
<textarea name="comment_text" class="form-control" rows="2" placeholder="Wpisz nowy komentarz..." required></textarea>
<button type="submit" name="add_comment" class="btn btn-primary px-3"><i class="bi bi-send"></i></button>

@ -3,7 +3,17 @@ require_once 'includes/db.php';
require_once 'includes/auth.php';
checkAuth();
function sanitizeCsvCell($value) {
$value = (string)$value;
if ($value !== '' && preg_match('/^[=\+\-@]/', $value) === 1) {
return "'" . $value;
}
return $value;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['order_ids'])) {
requireCsrfToken();
$ids = $_POST['order_ids'];
// Tworzenie znaków zapytania do zapytania SQL (np. ?, ?, ?)
@ -31,16 +41,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['order_ids'])) {
foreach ($orders as $o) {
fputcsv($output, [
$o['id'],
$o['product_name'],
$o['part_number'],
sanitizeCsvCell($o['product_name']),
sanitizeCsvCell($o['part_number']),
$o['quantity'],
$o['purchase_place'],
sanitizeCsvCell($o['purchase_place']),
number_format($o['price_per_unit'], 2, ',', ''), // Cena z przecinkiem dla Excela
$o['delivery_date'],
$o['recipient'],
$o['delivery_address'],
$o['status'],
$o['notes']
sanitizeCsvCell($o['delivery_date']),
sanitizeCsvCell($o['recipient']),
sanitizeCsvCell($o['delivery_address']),
sanitizeCsvCell($o['status']),
sanitizeCsvCell($o['notes'])
], ';');
}

@ -6,11 +6,11 @@ checkAuth();
$message = '';
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES['csv_file'])) {
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_FILES['csv_file'])) {
requireCsrfToken();
$file = $_FILES['csv_file']['tmp_name'];
if (($handle = fopen($file, "r")) !== FALSE) {
// Pomijamy pierwszy wiersz (nagłówki)
if (($handle = fopen($file, "r")) !== false) {
fgetcsv($handle, 1000, ";");
try {
@ -21,32 +21,32 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES['csv_file'])) {
$stmt = $pdo->prepare($sql);
$count = 0;
while (($row = fgetcsv($handle, 1000, ";")) !== FALSE) {
// $row[0] to LP - pomijamy zgodnie z wymaganiem
// Obsługa polskich znaków (jeśli plik jest w Windows-1250)
while (($row = fgetcsv($handle, 1000, ";")) !== false) {
foreach ($row as $key => $value) {
$row[$key] = mb_convert_encoding($value, "UTF-8", "auto");
}
if (empty($row[1])) continue; // Pomiń jeśli brak nazwy produktu
if (empty($row[1])) {
continue;
}
$stmt->execute([
$row[1], // Produkt
(int)$row[2], // Ilość
$row[3], // Miejsce zakupu
(float)str_replace(',', '.', $row[4]), // Cena (zamiana przecinka na kropkę)
$row[5], // Data dostawy
$row[6], // Notatki
$row[7] ?? 'nowe' // Status
$row[1],
(int)$row[2],
$row[3],
(float)str_replace(',', '.', $row[4]),
$row[5],
$row[6],
$row[7] ?? 'nowe'
]);
$count++;
}
$pdo->commit();
$message = "<div class='alert alert-success'>Zaimportowano $count zamówień z pliku CSV!</div>";
$message = "<div class='alert alert-success'>Zaimportowano $count zamowien z pliku CSV.</div>";
} catch (Exception $e) {
$pdo->rollBack();
$message = "<div class='alert alert-danger'>Błąd: " . $e->getMessage() . "</div>";
error_log($e->getMessage());
$message = "<div class='alert alert-danger'>Import nie powiodl sie.</div>";
}
fclose($handle);
}
@ -57,7 +57,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES['csv_file'])) {
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>Import CSV - <?php echo APP_NAME; ?></title>
<title>Import CSV - <?php echo e(APP_NAME); ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
@ -70,16 +70,17 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES['csv_file'])) {
<?php echo $message; ?>
<div class="alert alert-warning small">
<strong>Ważne:</strong> W Excelu wybierz <em>Zapisz jako</em> -> <strong>CSV (rozdzielany średnikami)</strong>.
<strong>Wazne:</strong> W Excelu wybierz <em>Zapisz jako</em> -> <strong>CSV (rozdzielany srednikami)</strong>.
</div>
<form method="POST" enctype="multipart/form-data">
<?php echo csrfInput(); ?>
<div class="mb-3">
<label class="form-label">Wybierz plik .csv</label>
<input type="file" name="csv_file" class="form-control" accept=".csv" required>
</div>
<div class="d-flex justify-content-between">
<a href="index.php" class="btn btn-secondary">Powrót</a>
<a href="index.php" class="btn btn-secondary">Powrot</a>
<button type="submit" class="btn btn-success">Importuj dane</button>
</div>
</form>

@ -1,5 +1,5 @@
<?php
require 'vendor/autoload.php'; // Ładowanie biblioteki PhpSpreadsheet
require 'vendor/autoload.php';
require_once 'includes/db.php';
require_once 'includes/auth.php';
@ -9,15 +9,14 @@ checkAuth();
$message = '';
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES['excel_file'])) {
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_FILES['excel_file'])) {
requireCsrfToken();
$file = $_FILES['excel_file']['tmp_name'];
try {
$spreadsheet = IOFactory::load($file);
$worksheet = $spreadsheet->getActiveSheet();
$rows = $worksheet->toArray();
// Pomijamy nagłówek (pierwszy wiersz w Excelu)
unset($rows[0]);
$pdo->beginTransaction();
@ -28,30 +27,29 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES['excel_file'])) {
$count = 0;
foreach ($rows as $row) {
// Zgodnie z wymaganiem: pomijamy 1. kolumnę ($row[0])
// Mapowanie kolumn Excela:
// $row[1] -> Produkt, $row[2] -> Ilość, $row[3] -> Miejsce,
// $row[4] -> Cena, $row[5] -> Data, $row[6] -> Notatki, $row[7] -> Status
if (empty($row[1])) continue; // Pomiń puste wiersze
if (empty($row[1])) {
continue;
}
$stmt->execute([
$row[1], // Produkt
(int)$row[2], // Ilość
$row[3], // Miejsce zakupu
(float)$row[4], // Cena
$row[5], // Data dostawy (format YYYY-MM-DD)
$row[6], // Notatki
$row[7] ?? 'nowe' // Status
$row[1],
(int)$row[2],
$row[3],
(float)$row[4],
$row[5],
$row[6],
$row[7] ?? 'nowe'
]);
$count++;
}
$pdo->commit();
$message = "<div class='alert alert-success'>Pomyślnie zaimportowano $count zamówień!</div>";
$message = "<div class='alert alert-success'>Pomyslnie zaimportowano $count zamowien.</div>";
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
$message = "<div class='alert alert-danger'>Błąd importu: " . $e->getMessage() . "</div>";
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
error_log($e->getMessage());
$message = "<div class='alert alert-danger'>Import nie powiodl sie.</div>";
}
}
?>
@ -60,7 +58,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES['excel_file'])) {
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>Import Zamówień - <?php echo APP_NAME; ?></title>
<title>Import zamowien - <?php echo e(APP_NAME); ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
@ -74,17 +72,18 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES['excel_file'])) {
<div class="alert alert-info">
<strong>Instrukcja:</strong><br>
1. System pomija pierwszą kolumnę (LP).<br>
2. Kolejne kolumny to: Produkt, Ilość, Miejsce, Cena, Data (RRRR-MM-DD), Notatki, Status.
1. System pomija pierwsza kolumne (LP).<br>
2. Kolejne kolumny to: Produkt, Ilosc, Miejsce, Cena, Data (RRRR-MM-DD), Notatki, Status.
</div>
<form method="POST" enctype="multipart/form-data">
<?php echo csrfInput(); ?>
<div class="mb-3">
<label class="form-label">Wybierz plik Excel</label>
<input type="file" name="excel_file" class="form-control" accept=".xlsx, .xls" required>
</div>
<div class="d-flex justify-content-between">
<a href="index.php" class="btn btn-secondary">Powrót</a>
<a href="index.php" class="btn btn-secondary">Powrot</a>
<button type="submit" class="btn btn-success">Rozpocznij import</button>
</div>
</form>

@ -1,7 +1,41 @@
<?php
if (session_status() !== PHP_SESSION_ACTIVE) {
$isHttps = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
session_set_cookie_params([
'httponly' => true,
'secure' => $isHttps,
'samesite' => 'Lax',
]);
session_start();
}
function e($value) {
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}
function getCsrfToken() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function csrfInput() {
return '<input type="hidden" name="csrf_token" value="' . e(getCsrfToken()) . '">';
}
function isValidCsrfToken($token) {
return is_string($token) && hash_equals(getCsrfToken(), $token);
}
function requireCsrfToken($token = null) {
if (!isValidCsrfToken($token ?? ($_POST['csrf_token'] ?? null))) {
http_response_code(403);
exit('Blad bezpieczenstwa: nieprawidlowy token formularza.');
}
}
// Funkcja sprawdzająca czy użytkownik jest zalogowany
function checkAuth() {
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
@ -9,10 +43,10 @@ function checkAuth() {
}
}
// Funkcja sprawdzająca czy użytkownik jest adminem
function checkAdmin() {
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
die("Błąd: Brak uprawnień administratora.");
http_response_code(403);
exit('Blad: Brak uprawnien administratora.');
}
}
?>

@ -1,14 +1,27 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
ini_set('display_errors', '0');
ini_set('display_startup_errors', '0');
error_reporting(E_ALL);
// Dane do połączenia z bazą
define('DB_HOST', '');
define('DB_NAME', '');
define('DB_USER', ''); // Zmień na swojego użytkownika bazy
define('DB_PASS', ''); // Zmień na swoje hasło
define('DB_PREFIX', '');
// Ustawienia aplikacji
define('APP_NAME', ' Zeszyt 2.0');
function envOrDefault($name, $default = '') {
$value = getenv($name);
return $value !== false ? $value : $default;
}
define('DB_HOST', envOrDefault('DB_HOST', 'db'));
define('DB_NAME', envOrDefault('DB_NAME', 'zeszyt'));
define('DB_USER', envOrDefault('DB_USER', 'zeszyt'));
define('DB_PASS', envOrDefault('DB_PASS', 'zeszyt'));
define('DB_PREFIX', envOrDefault('DB_PREFIX', 'xyz_'));
define('APP_NAME', envOrDefault('APP_NAME', 'Zeszyt 2.0'));
// Przyklad redirect URI: http://localhost:8080/oauth_callback.php
define('MICROSOFT_OAUTH_ENABLED', filter_var(envOrDefault('MICROSOFT_OAUTH_ENABLED', 'false'), FILTER_VALIDATE_BOOLEAN));
define('MICROSOFT_OAUTH_TENANT', envOrDefault('MICROSOFT_OAUTH_TENANT', 'common'));
define('MICROSOFT_OAUTH_CLIENT_ID', envOrDefault('MICROSOFT_OAUTH_CLIENT_ID', ''));
define('MICROSOFT_OAUTH_CLIENT_SECRET', envOrDefault('MICROSOFT_OAUTH_CLIENT_SECRET', ''));
define('MICROSOFT_OAUTH_REDIRECT_URI', envOrDefault('MICROSOFT_OAUTH_REDIRECT_URI', ''));
define('MICROSOFT_OAUTH_ALLOWED_TENANT', envOrDefault('MICROSOFT_OAUTH_ALLOWED_TENANT', ''));
define('MICROSOFT_OAUTH_AUTO_PROVISION', filter_var(envOrDefault('MICROSOFT_OAUTH_AUTO_PROVISION', 'false'), FILTER_VALIDATE_BOOLEAN));
?>

@ -10,7 +10,63 @@ try {
];
$pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
ensureOptionalSchema($pdo);
} catch (\PDOException $e) {
throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
function ensureOptionalSchema(PDO $pdo) {
static $done = false;
if ($done || DB_PREFIX === '') {
return;
}
$done = true;
$usersTable = DB_PREFIX . "users";
try {
$columns = $pdo->query("SHOW COLUMNS FROM {$usersTable}")->fetchAll(PDO::FETCH_COLUMN, 0);
$indexes = $pdo->query("SHOW INDEX FROM {$usersTable}")->fetchAll(PDO::FETCH_ASSOC);
} catch (\PDOException $e) {
error_log($e->getMessage());
return;
}
$indexNames = array_column($indexes, 'Key_name');
$statements = [];
if (!in_array('email', $columns, true)) {
$statements[] = "ALTER TABLE {$usersTable} ADD COLUMN email varchar(255) DEFAULT NULL AFTER username";
}
if (!in_array('display_name', $columns, true)) {
$statements[] = "ALTER TABLE {$usersTable} ADD COLUMN display_name varchar(255) DEFAULT NULL AFTER email";
}
if (!in_array('oauth_provider', $columns, true)) {
$statements[] = "ALTER TABLE {$usersTable} ADD COLUMN oauth_provider varchar(50) DEFAULT NULL AFTER role";
}
if (!in_array('oauth_subject', $columns, true)) {
$statements[] = "ALTER TABLE {$usersTable} ADD COLUMN oauth_subject varchar(191) DEFAULT NULL AFTER oauth_provider";
}
if (!in_array('oauth_tenant_id', $columns, true)) {
$statements[] = "ALTER TABLE {$usersTable} ADD COLUMN oauth_tenant_id varchar(64) DEFAULT NULL AFTER oauth_subject";
}
if (!in_array('last_login_at', $columns, true)) {
$statements[] = "ALTER TABLE {$usersTable} ADD COLUMN last_login_at datetime DEFAULT NULL AFTER oauth_tenant_id";
}
if (!in_array('uniq_users_email', $indexNames, true)) {
$statements[] = "ALTER TABLE {$usersTable} ADD UNIQUE KEY uniq_users_email (email)";
}
if (!in_array('idx_users_oauth_identity', $indexNames, true)) {
$statements[] = "ALTER TABLE {$usersTable} ADD KEY idx_users_oauth_identity (oauth_provider, oauth_subject, oauth_tenant_id)";
}
foreach ($statements as $statement) {
try {
$pdo->exec($statement);
} catch (\PDOException $e) {
error_log($e->getMessage());
}
}
}
?>

@ -0,0 +1,293 @@
<?php
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/db.php';
function isMicrosoftOAuthConfigured() {
return MICROSOFT_OAUTH_ENABLED
&& MICROSOFT_OAUTH_CLIENT_ID !== ''
&& MICROSOFT_OAUTH_CLIENT_SECRET !== ''
&& MICROSOFT_OAUTH_REDIRECT_URI !== '';
}
function getMicrosoftOAuthTenant() {
return MICROSOFT_OAUTH_TENANT !== '' ? MICROSOFT_OAUTH_TENANT : 'common';
}
function getMicrosoftOAuthBaseUrl() {
return 'https://login.microsoftonline.com/' . rawurlencode(getMicrosoftOAuthTenant()) . '/oauth2/v2.0';
}
function buildMicrosoftAuthorizationUrl() {
$state = bin2hex(random_bytes(32));
$nonce = bin2hex(random_bytes(32));
$_SESSION['microsoft_oauth_state'] = $state;
$_SESSION['microsoft_oauth_nonce'] = $nonce;
$params = [
'client_id' => MICROSOFT_OAUTH_CLIENT_ID,
'response_type' => 'code',
'redirect_uri' => MICROSOFT_OAUTH_REDIRECT_URI,
'response_mode' => 'query',
'scope' => 'openid profile email offline_access',
'state' => $state,
'nonce' => $nonce,
];
return getMicrosoftOAuthBaseUrl() . '/authorize?' . http_build_query($params);
}
function exchangeMicrosoftCodeForTokens($code) {
$response = httpPostForm(
getMicrosoftOAuthBaseUrl() . '/token',
[
'client_id' => MICROSOFT_OAUTH_CLIENT_ID,
'client_secret' => MICROSOFT_OAUTH_CLIENT_SECRET,
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => MICROSOFT_OAUTH_REDIRECT_URI,
]
);
if ($response['status'] < 200 || $response['status'] >= 300) {
throw new RuntimeException('Microsoft token endpoint returned HTTP ' . $response['status']);
}
$payload = json_decode($response['body'], true);
if (!is_array($payload) || empty($payload['id_token'])) {
throw new RuntimeException('Brak id_token w odpowiedzi Microsoft');
}
return $payload;
}
function decodeJwtPayload($jwt) {
$parts = explode('.', $jwt);
if (count($parts) < 2) {
throw new RuntimeException('Nieprawidlowy format JWT');
}
$payload = json_decode(base64UrlDecode($parts[1]), true);
if (!is_array($payload)) {
throw new RuntimeException('Nie mozna odczytac payload JWT');
}
return $payload;
}
function base64UrlDecode($data) {
$remainder = strlen($data) % 4;
if ($remainder > 0) {
$data .= str_repeat('=', 4 - $remainder);
}
return base64_decode(strtr($data, '-_', '+/'));
}
function extractMicrosoftIdentity(array $tokenPayload) {
$claims = decodeJwtPayload($tokenPayload['id_token']);
$expectedNonce = $_SESSION['microsoft_oauth_nonce'] ?? null;
unset($_SESSION['microsoft_oauth_nonce']);
if (!$expectedNonce || !hash_equals($expectedNonce, (string)($claims['nonce'] ?? ''))) {
throw new RuntimeException('Nieprawidlowy nonce odpowiedzi Microsoft');
}
if ((string)($claims['aud'] ?? '') !== MICROSOFT_OAUTH_CLIENT_ID) {
throw new RuntimeException('Token Microsoft ma nieprawidlowe aud');
}
if ((int)($claims['exp'] ?? 0) < time()) {
throw new RuntimeException('Token Microsoft wygasl');
}
$tenantId = (string)($claims['tid'] ?? '');
$objectId = (string)($claims['oid'] ?? '');
if ($tenantId === '' || $objectId === '') {
throw new RuntimeException('Brak wymaganych claimow tid/oid');
}
$issuer = (string)($claims['iss'] ?? '');
if ($issuer === '' || strpos($issuer, 'https://login.microsoftonline.com/') !== 0) {
throw new RuntimeException('Token Microsoft ma nieprawidlowe iss');
}
if (MICROSOFT_OAUTH_ALLOWED_TENANT !== '' && !hash_equals(MICROSOFT_OAUTH_ALLOWED_TENANT, $tenantId)) {
throw new RuntimeException('Ten tenant Microsoft nie jest dozwolony');
}
$email = trim((string)($claims['email'] ?? ''));
$preferredUsername = trim((string)($claims['preferred_username'] ?? ''));
if ($email === '' && filter_var($preferredUsername, FILTER_VALIDATE_EMAIL)) {
$email = $preferredUsername;
}
return [
'tenant_id' => $tenantId,
'subject' => $objectId,
'display_name' => trim((string)($claims['name'] ?? '')),
'email' => $email,
'preferred_username' => $preferredUsername,
];
}
function findOrProvisionMicrosoftUser(PDO $pdo, array $identity) {
$lookup = $pdo->prepare(
"SELECT * FROM " . DB_PREFIX . "users WHERE oauth_provider = 'microsoft' AND oauth_subject = ? AND oauth_tenant_id = ? LIMIT 1"
);
$lookup->execute([$identity['subject'], $identity['tenant_id']]);
$user = $lookup->fetch();
if ($user) {
return updateMicrosoftUserProfile($pdo, $user, $identity);
}
$candidate = $identity['email'] !== '' ? $identity['email'] : $identity['preferred_username'];
if ($candidate !== '') {
$link = $pdo->prepare(
"SELECT * FROM " . DB_PREFIX . "users WHERE email = ? OR username = ? LIMIT 1"
);
$link->execute([$candidate, $candidate]);
$user = $link->fetch();
if ($user) {
$stmt = $pdo->prepare(
"UPDATE " . DB_PREFIX . "users
SET email = COALESCE(email, ?), display_name = ?, oauth_provider = 'microsoft', oauth_subject = ?, oauth_tenant_id = ?, last_login_at = NOW()
WHERE id = ?"
);
$stmt->execute([
$identity['email'] !== '' ? $identity['email'] : null,
$identity['display_name'] !== '' ? $identity['display_name'] : null,
$identity['subject'],
$identity['tenant_id'],
$user['id'],
]);
$reload = $pdo->prepare("SELECT * FROM " . DB_PREFIX . "users WHERE id = ?");
$reload->execute([$user['id']]);
return $reload->fetch();
}
}
if (!MICROSOFT_OAUTH_AUTO_PROVISION) {
throw new RuntimeException('To konto Microsoft nie jest powiazane z zadnym lokalnym uzytkownikiem');
}
$usernameBase = $candidate !== '' ? $candidate : 'm365_' . substr($identity['subject'], 0, 12);
$username = makeUniqueUsername($pdo, $usernameBase);
$passwordHash = password_hash(bin2hex(random_bytes(24)), PASSWORD_BCRYPT);
$insert = $pdo->prepare(
"INSERT INTO " . DB_PREFIX . "users
(username, email, display_name, password, role, oauth_provider, oauth_subject, oauth_tenant_id, last_login_at)
VALUES (?, ?, ?, ?, 'user', 'microsoft', ?, ?, NOW())"
);
$insert->execute([
$username,
$identity['email'] !== '' ? $identity['email'] : null,
$identity['display_name'] !== '' ? $identity['display_name'] : null,
$passwordHash,
$identity['subject'],
$identity['tenant_id'],
]);
$reload = $pdo->prepare("SELECT * FROM " . DB_PREFIX . "users WHERE id = ?");
$reload->execute([$pdo->lastInsertId()]);
return $reload->fetch();
}
function updateMicrosoftUserProfile(PDO $pdo, array $user, array $identity) {
$stmt = $pdo->prepare(
"UPDATE " . DB_PREFIX . "users
SET email = COALESCE(?, email), display_name = COALESCE(?, display_name), last_login_at = NOW()
WHERE id = ?"
);
$stmt->execute([
$identity['email'] !== '' ? $identity['email'] : null,
$identity['display_name'] !== '' ? $identity['display_name'] : null,
$user['id'],
]);
$reload = $pdo->prepare("SELECT * FROM " . DB_PREFIX . "users WHERE id = ?");
$reload->execute([$user['id']]);
return $reload->fetch();
}
function makeUniqueUsername(PDO $pdo, $base) {
$base = preg_replace('/[^a-zA-Z0-9._@-]/', '_', $base);
$base = trim($base, '_');
if ($base === '') {
$base = 'user';
}
$candidate = $base;
$i = 1;
$stmt = $pdo->prepare("SELECT COUNT(*) FROM " . DB_PREFIX . "users WHERE username = ?");
while (true) {
$stmt->execute([$candidate]);
if ((int)$stmt->fetchColumn() === 0) {
return $candidate;
}
$i++;
$candidate = $base . '_' . $i;
}
}
function loginUserIntoSession(array $user) {
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['role'] = $user['role'];
}
function httpPostForm($url, array $fields) {
$body = http_build_query($fields);
if (function_exists('curl_init')) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
CURLOPT_TIMEOUT => 15,
]);
$responseBody = curl_exec($ch);
if ($responseBody === false) {
$error = curl_error($ch);
curl_close($ch);
throw new RuntimeException('Blad cURL: ' . $error);
}
$status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
return ['status' => $status, 'body' => $responseBody];
}
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
'content' => $body,
'timeout' => 15,
'ignore_errors' => true,
],
]);
$responseBody = file_get_contents($url, false, $context);
if ($responseBody === false) {
throw new RuntimeException('Nie mozna polaczyc sie z Microsoft');
}
$status = 0;
foreach ($http_response_header ?? [] as $header) {
if (preg_match('#HTTP/\S+\s+(\d+)#', $header, $matches)) {
$status = (int)$matches[1];
break;
}
}
return ['status' => $status, 'body' => $responseBody];
}
?>

@ -59,6 +59,7 @@ $stmt->execute($params);
$orders = $stmt->fetchAll();
$logo_path = $pdo->query("SELECT setting_value FROM " . DB_PREFIX . "settings WHERE setting_key = 'logo_path'")->fetchColumn() ?: 'assets/default_logo.png';
$csrf_token = getCsrfToken();
function getStatusClass($status) {
return match($status) {
@ -102,7 +103,7 @@ function getStatusClass($status) {
<a class="navbar-brand d-flex align-items-center m-0" href="index.php">
<?php if(file_exists($logo_path)): ?>
<img src="<?php echo $logo_path; ?>" alt="Logo" class="logo-img">
<img src="<?php echo e($logo_path); ?>" alt="Logo" class="logo-img">
<?php else: ?>
<span class="fs-4">💻</span>
<?php endif; ?>
@ -114,7 +115,7 @@ function getStatusClass($status) {
<div class="d-flex ms-auto align-items-center gap-2">
<button class="btn btn-sm btn-outline-secondary" onclick="toggleDarkMode()"><i class="bi bi-moon-stars" id="themeIcon"></i></button>
<span class="text-light small d-none d-lg-inline me-2">Witaj, <strong><?php echo $_SESSION['username']; ?></strong></span>
<span class="text-light small d-none d-lg-inline me-2">Witaj, <strong><?php echo e($_SESSION['username']); ?></strong></span>
<a class="btn btn-outline-light btn-sm" href="change_password.php"><i class="bi bi-key"></i> Hasło</a>
<?php if ($_SESSION['role'] === 'admin'): ?>
<a class="btn btn-outline-warning btn-sm" href="admin.php"><i class="bi bi-gear"></i> Admin</a>
@ -193,6 +194,7 @@ function getStatusClass($status) {
</div>
<form method="POST" id="bulkForm">
<?php echo csrfInput(); ?>
<div class="table-container p-0 overflow-hidden mb-3">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
@ -421,6 +423,8 @@ function getStatusClass($status) {
</div>
<script>
const csrfToken = <?php echo json_encode($csrf_token); ?>;
async function performAction(orderId, actionType) {
let confirmMsg = actionType === 'archive'
? "Czy na pewno chcesz przenieść to zamówienie do archiwum?"
@ -432,7 +436,7 @@ function getStatusClass($status) {
let response = await fetch('ajax_action.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: orderId, action: actionType })
body: JSON.stringify({ id: orderId, action: actionType, csrf_token: csrfToken })
});
let resData = await response.json();
@ -517,7 +521,7 @@ function getStatusClass($status) {
let response = await fetch('ajax_edit.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: orderId, field: fieldName, value: newValue })
body: JSON.stringify({ id: orderId, field: fieldName, value: newValue, csrf_token: csrfToken })
});
let resData = await response.json();
if(resData.success) {
@ -582,7 +586,7 @@ function getStatusClass($status) {
let response = await fetch('ajax_add_row.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ product_name: prod, quantity: qty, purchase_place: place, price_per_unit: price })
body: JSON.stringify({ product_name: prod, quantity: qty, purchase_place: place, price_per_unit: price, csrf_token: csrfToken })
});
let resData = await response.json();

@ -1,62 +1,57 @@
<?php
// 1. Włączenie raportowania błędów (na czas testów)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
ini_set('display_errors', '0');
ini_set('display_startup_errors', '0');
error_reporting(E_ALL);
// 2. Start sesji - musi być na samym początku, przed jakimkolwiek znakiem!
session_start();
// 3. Połączenie z bazą danych
require_once 'includes/db.php';
require_once 'includes/auth.php';
require_once 'includes/oauth_microsoft.php';
$error = '';
$logo_path = '';
// Pobranie ścieżki do logo z bazy danych
if (isset($_GET['oauth_error'])) {
$error = trim((string)$_GET['oauth_error']);
}
try {
$stmt = $pdo->query("SELECT setting_value FROM " . DB_PREFIX . "settings WHERE setting_key = 'logo_path'");
if ($stmt) {
$logo_path = $stmt->fetchColumn();
}
} catch (PDOException $e) {
// Ignorujemy błąd, jeśli tabela jeszcze nie istnieje itp.
error_log($e->getMessage());
}
// 4. Obsługa logowania
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = trim($_POST['username']);
$password = $_POST['password'];
if ($_SERVER["REQUEST_METHOD"] === "POST") {
requireCsrfToken();
if (!empty($username) && !empty($password)) {
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
if ($username !== '' && $password !== '') {
try {
// Pobieranie użytkownika z bazy (uwzględniając prefix)
$stmt = $pdo->prepare("SELECT * FROM " . DB_PREFIX . "users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
if ($user) {
// Weryfikacja hasła
if (password_verify($password, $user['password'])) {
// Logowanie udane - zapis do sesji
if ($user && password_verify($password, $user['password'])) {
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['role'] = $user['role'];
// Przekierowanie do index.php
header("Location: index.php");
exit();
} else {
$error = "Błędne hasło. Upewnij się, że hasło jest poprawne).";
}
} else {
$error = "Użytkownik '$username' nie istnieje w bazie.";
}
$error = "Nieprawidlowy login lub haslo.";
} catch (PDOException $e) {
$error = "Błąd bazy danych: " . $e->getMessage();
error_log($e->getMessage());
$error = "Nie udalo sie zalogowac. Sprobuj ponownie pozniej.";
}
} else {
$error = "Proszę wypełnić oba pola.";
$error = "Prosze wypelnic oba pola.";
}
}
?>
@ -66,7 +61,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Logowanie - <?php echo defined('APP_NAME') ? APP_NAME : 'System Zamówień'; ?></title>
<title>Logowanie - <?php echo e(defined('APP_NAME') ? APP_NAME : 'System Zamowien'); ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background-color: #f4f7f6; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
@ -80,29 +75,35 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
<div class="login-card">
<div class="text-center mb-4">
<?php if ($logo_path && file_exists($logo_path)): ?>
<img src="<?php echo htmlspecialchars($logo_path); ?>" alt="Logo" class="login-logo">
<img src="<?php echo e($logo_path); ?>" alt="Logo" class="login-logo">
<?php else: ?>
<h3>💻 Logowanie</h3>
<h3>Logowanie</h3>
<?php endif; ?>
<p class="text-muted small">Wpisz dane, aby zarządzać zamówieniami</p>
<p class="text-muted small">Wpisz dane, aby zarzadzac zamowieniami</p>
</div>
<?php if ($error): ?>
<div class="alert alert-danger p-2 small"><?php echo $error; ?></div>
<div class="alert alert-danger p-2 small"><?php echo e($error); ?></div>
<?php endif; ?>
<form method="POST" action="login.php">
<?php echo csrfInput(); ?>
<div class="mb-3">
<label for="username" class="form-label small">Użytkownik</label>
<label for="username" class="form-label small">Uzytkownik</label>
<input type="text" name="username" id="username" class="form-control" required autofocus>
</div>
<div class="mb-3">
<label for="password" class="form-label small">Hasło</label>
<label for="password" class="form-label small">Haslo</label>
<input type="password" name="password" id="password" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary w-100">Zaloguj się</button>
<button type="submit" class="btn btn-primary w-100">Zaloguj sie</button>
</form>
<?php if (isMicrosoftOAuthConfigured()): ?>
<div class="text-center my-3 text-muted small">lub</div>
<a href="oauth_login.php" class="btn btn-outline-dark w-100">Zaloguj przez Microsoft 365</a>
<?php endif; ?>
<div class="mt-4 text-center border-top pt-3">
<p class="text-muted" style="font-size: 0.8rem;">
Aplikacja: <strong>goral.edu.pl/zeszyt</strong><br>

@ -1,14 +1,9 @@
<?php
// 1. Rozpoczęcie sesji, aby mieć do niej dostęp
session_start();
// 2. Usunięcie wszystkich zmiennych sesyjnych (user_id, role, username)
session_unset();
// 3. Całkowite zniszczenie sesji na serwerze
unset($_SESSION['microsoft_oauth_state'], $_SESSION['microsoft_oauth_nonce']);
session_destroy();
// 4. Przekierowanie użytkownika do strony logowania
header("Location: login.php");
exit();
?>

@ -0,0 +1,38 @@
<?php
require_once 'includes/oauth_microsoft.php';
if (!isMicrosoftOAuthConfigured()) {
http_response_code(503);
exit('Logowanie Microsoft nie jest skonfigurowane.');
}
$expectedState = $_SESSION['microsoft_oauth_state'] ?? null;
$state = $_GET['state'] ?? '';
$code = $_GET['code'] ?? '';
$error = $_GET['error'] ?? '';
unset($_SESSION['microsoft_oauth_state']);
if ($error !== '') {
header('Location: login.php?oauth_error=' . urlencode('Logowanie Microsoft zostalo anulowane lub odrzucone.'));
exit();
}
if (!$expectedState || !hash_equals($expectedState, (string)$state) || $code === '') {
header('Location: login.php?oauth_error=' . urlencode('Nieprawidlowy stan logowania Microsoft.'));
exit();
}
try {
$tokens = exchangeMicrosoftCodeForTokens($code);
$identity = extractMicrosoftIdentity($tokens);
$user = findOrProvisionMicrosoftUser($pdo, $identity);
loginUserIntoSession($user);
header('Location: index.php');
exit();
} catch (Throwable $e) {
error_log($e->getMessage());
header('Location: login.php?oauth_error=' . urlencode('Nie udalo sie zalogowac przez Microsoft 365.'));
exit();
}
?>

@ -0,0 +1,11 @@
<?php
require_once 'includes/oauth_microsoft.php';
if (!isMicrosoftOAuthConfigured()) {
http_response_code(503);
exit('Logowanie Microsoft nie jest skonfigurowane.');
}
header('Location: ' . buildMicrosoftAuthorizationUrl());
exit();
?>

@ -4,6 +4,7 @@ require_once 'includes/auth.php';
checkAuth();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['order_ids'])) {
requireCsrfToken();
$ids = $_POST['order_ids'];
$placeholders = str_repeat('?,', count($ids) - 1) . '?';
$cols = $_POST['print_cols'] ?? ['id', 'product', 'quantity', 'place', 'price', 'status'];
@ -82,7 +83,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['order_ids'])) {
<?php if($hasCol('quantity')): ?> <td style="text-align: center;"><strong><?php echo $o['quantity']; ?></strong></td> <?php endif; ?>
<?php if($hasCol('place')): ?> <td><?php echo htmlspecialchars($o['purchase_place']); ?></td> <?php endif; ?>
<?php if($hasCol('price')): ?> <td><?php echo number_format($o['price_per_unit'], 2, ',', ' '); ?></td> <?php endif; ?>
<?php if($hasCol('status')): ?> <td><?php echo $o['status']; ?></td> <?php endif; ?>
<?php if($hasCol('status')): ?> <td><?php echo e($o['status']); ?></td> <?php endif; ?>
</tr>
<?php if($hasCol('recipient') || $hasCol('address') || $hasCol('notes')): ?>

@ -4,6 +4,7 @@ require_once 'includes/auth.php';
checkAuth();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['order_ids'])) {
requireCsrfToken();
$ids = $_POST['order_ids'];
$placeholders = str_repeat('?,', count($ids) - 1) . '?';

Loading…
Cancel
Save