diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f94c71d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.git +.gitignore +node_modules +vendor +docker-compose.override.yml +source/uploads +mysql_data diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0a773c3 --- /dev/null +++ b/.env.example @@ -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 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2b612e2 --- /dev/null +++ b/Dockerfile @@ -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 diff --git a/db/zeszyt.sql b/db/zeszyt.sql index fb392a2..8b759ff 100644 --- a/db/zeszyt.sql +++ b/db/zeszyt.sql @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6fce2f1 --- /dev/null +++ b/docker-compose.yml @@ -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: diff --git a/source/add_order.php b/source/add_order.php index 2cab010..513f17e 100644 --- a/source/add_order.php +++ b/source/add_order.php @@ -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(); @@ -33,11 +35,11 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") { $hist_stmt->execute([$order_id, $_SESSION['user_id'], 'Utworzono nowe zamówienie.']); $pdo->commit(); - - $message = "
Zamówienie dodane pomyślnie!
"; + $message = "
Zamówienie dodane pomyślnie.
"; } catch (PDOException $e) { $pdo->rollBack(); - $message = "
Błąd: " . $e->getMessage() . "
"; + error_log($e->getMessage()); + $message = "
Nie udało się zapisać zamówienia.
"; } } else { $message = "
Wypełnij nazwę produktu i ilość.
"; @@ -49,7 +51,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") { - Dodaj zamówienie - <?php echo APP_NAME; ?> + Dodaj zamówienie - <?php echo e(APP_NAME); ?> @@ -62,6 +64,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
+
@@ -90,13 +93,13 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
- +
- +
diff --git a/source/admin.php b/source/admin.php index d64f3f0..8cd3f96 100644 --- a/source/admin.php +++ b/source/admin.php @@ -3,13 +3,10 @@ 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', @@ -18,119 +15,108 @@ $default_settings = [ ]; 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 ($_FILES['logo']['error'] === 0) { - $upload_dir = 'uploads/'; - if (!is_dir($upload_dir)) mkdir($upload_dir, 0777, true); - - $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; - $dest_path = $upload_dir . $new_name; - - if (move_uploaded_file($_FILES['logo']['tmp_name'], $dest_path)) { - $stmt = $pdo->prepare("REPLACE INTO " . DB_PREFIX . "settings (setting_key, setting_value) VALUES ('logo_path', ?)"); - $stmt->execute([$dest_path]); - $message = "
Pomyślnie zaktualizowano 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, 0755, true); + } + + $tmpPath = $_FILES['logo']['tmp_name']; + $mime = mime_content_type($tmpPath); + $allowedMimes = [ + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif', + ]; + + 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($tmpPath, $dest_path)) { + $stmt = $pdo->prepare("REPLACE INTO " . DB_PREFIX . "settings (setting_key, setting_value) VALUES ('logo_path', ?)"); + $stmt->execute([$dest_path]); + $message = "
Pomyślnie zaktualizowano logo.
"; + } else { + $message = "
Nie udało się zapisać pliku.
"; + } } else { - $message = "
Błąd: Nie udało się zapisać pliku. Sprawdź uprawnienia folderu 'uploads'.
"; + $message = "
Dozwolone są tylko prawidłowe pliki JPG, PNG i GIF.
"; } - } else { - $message = "
Błąd: Dozwolone są tylko pliki graficzne (JPG, PNG, GIF, SVG).
"; } - } -} + } elseif (isset($_POST['action']) && $_POST['action'] === 'update_company_settings') { + $settings_to_update = ['company_1_name', 'company_1_details', 'company_2_name', 'company_2_details']; -// --------------------------------------------------------- -// 1B. ZAPISYWANIE DANYCH FIRM -// --------------------------------------------------------- -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_company_settings') { - $settings_to_update = ['company_1_name', 'company_1_details', 'company_2_name', 'company_2_details']; - - try { - $pdo->beginTransaction(); - $update_stmt = $pdo->prepare("UPDATE " . DB_PREFIX . "settings SET setting_value = ? WHERE setting_key = ?"); + try { + $pdo->beginTransaction(); + $update_stmt = $pdo->prepare("UPDATE " . DB_PREFIX . "settings SET setting_value = ? WHERE setting_key = ?"); - foreach ($settings_to_update as $key) { - if (isset($_POST[$key])) { - $update_stmt->execute([trim($_POST[$key]), $key]); + foreach ($settings_to_update as $key) { + if (isset($_POST[$key])) { + $update_stmt->execute([trim($_POST[$key]), $key]); + } } + $pdo->commit(); + $message = "
Ustawienia firmowe zostały zapisane.
"; + } catch (PDOException $e) { + $pdo->rollBack(); + error_log($e->getMessage()); + $message = "
Nie udało się zapisać ustawień.
"; } - $pdo->commit(); - $message = "
Ustawienia firmowe zostały zapisane!
"; - } catch (PDOException $e) { - $pdo->rollBack(); - $message = "
Błąd zapisu: " . $e->getMessage() . "
"; - } -} - -// --------------------------------------------------------- -// 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 ($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, email, password, role) VALUES (?, ?, ?, ?)"); + $stmt->execute([$new_user, $new_email !== '' ? $new_email : null, $hashed_pass, $new_role === 'admin' ? 'admin' : 'user']); + $message = "
Dodano nowego użytkownika: " . e($new_user) . "
"; + } catch (PDOException $e) { + error_log($e->getMessage()); + $message = "
Nie udało się dodać użytkownika.
"; + } + } + } elseif (isset($_POST['reset_password'])) { + $user_id = (int)($_POST['user_id'] ?? 0); + $new_pass = $_POST['new_password'] ?? ''; - if (!empty($new_user) && !empty($new_pass)) { - try { + if ($user_id > 0 && $new_pass !== '') { $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 = "
Dodano nowego użytkownika: $new_user
"; - } catch (PDOException $e) { - $message = "
Błąd: Użytkownik o takiej nazwie prawdopodobnie już istnieje.
"; + $stmt = $pdo->prepare("UPDATE " . DB_PREFIX . "users SET password = ? WHERE id = ?"); + $stmt->execute([$hashed_pass, $user_id]); + $message = "
Zmieniono hasło dla wybranego użytkownika.
"; + } + } 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 = "
Użytkownik został pomyślnie usunięty.
"; + } else { + $message = "
Nie możesz usunąć własnego konta administratora.
"; } } } -// --------------------------------------------------------- -// 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)) { - $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 = "
Zmieniono hasło dla wybranego użytkownika.
"; - } -} - -// --------------------------------------------------------- -// 4. USUWANIE UŻYTKOWNIKA -// --------------------------------------------------------- -if (isset($_GET['delete'])) { - $id_to_delete = (int)$_GET['delete']; - if ($id_to_delete != $_SESSION['user_id']) { - $stmt = $pdo->prepare("DELETE FROM " . DB_PREFIX . "users WHERE id = ?"); - $stmt->execute([$id_to_delete]); - $message = "
Użytkownik został pomyślnie usunięty.
"; - } else { - $message = "
Odmowa: Nie możesz usunąć własnego konta administratora!
"; - } -} - -// --------------------------------------------------------- -// 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 +} catch (PDOException $e) { + error_log($e->getMessage()); } $current_logo = $settings['logo_path'] ?? ''; @@ -141,7 +127,7 @@ $current_logo = $settings['logo_path'] ?? ''; - Panel Administratora - <?php echo defined('APP_NAME') ? APP_NAME : 'System'; ?> + Panel administratora - <?php echo e(defined('APP_NAME') ? APP_NAME : 'System'); ?>