SMP all works
This commit is contained in:
4
semester-4/СмП/lb-4/README.md
Normal file
4
semester-4/СмП/lb-4/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
> [!NOTE]
|
||||
> Викладач: Сокорчук І. П.
|
||||
>
|
||||
> Оцінка: In Progress
|
92
semester-4/СмП/lb-4/src/index.php
Normal file
92
semester-4/СмП/lb-4/src/index.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
session_start();
|
||||
|
||||
require_once 'src/Database/DB.php';
|
||||
require_once 'src/Database/CartRepository.php';
|
||||
require_once 'src/Controllers/HomeController.php';
|
||||
require_once 'src/Controllers/ItemsController.php';
|
||||
require_once 'src/Controllers/CartController.php';
|
||||
require_once 'src/Controllers/AuthController.php';
|
||||
require_once 'src/Controllers/ProfileController.php';
|
||||
|
||||
$db = new DB("shop.db");
|
||||
|
||||
$request = $_GET['page'] ?? 'home';
|
||||
$action = $_GET['action'] ?? 'index';
|
||||
|
||||
$protected_pages = ['home', 'items', 'cart', 'profile'];
|
||||
$public_pages = ['login', 'register'];
|
||||
|
||||
if (in_array($request, $protected_pages) && !isset($_SESSION['user'])) {
|
||||
header('Location: ?page=404');
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
switch ($request) {
|
||||
case 'home':
|
||||
$controller = new HomeController($db);
|
||||
$controller->index();
|
||||
break;
|
||||
|
||||
case 'items':
|
||||
$controller = new ItemsController($db);
|
||||
$controller->index();
|
||||
break;
|
||||
|
||||
case 'cart':
|
||||
$controller = new CartController($db);
|
||||
if ($action === 'add' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$controller->add();
|
||||
} elseif ($action === 'remove' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$controller->remove();
|
||||
} elseif ($action === 'clear' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$controller->clear();
|
||||
} else {
|
||||
$controller->index();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'login':
|
||||
$controller = new AuthController($db);
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$controller->login();
|
||||
} else {
|
||||
$controller->showLogin();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'register':
|
||||
$controller = new AuthController($db);
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$controller->register();
|
||||
} else {
|
||||
$controller->showRegister();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'logout':
|
||||
$controller = new AuthController($db);
|
||||
$controller->logout();
|
||||
break;
|
||||
|
||||
case 'profile':
|
||||
$controller = new ProfileController($db);
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$controller->updateProfile();
|
||||
} else {
|
||||
$controller->showProfile();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(404);
|
||||
include 'templates/pages/404.php';
|
||||
break;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Application error: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
include 'templates/pages/error.php';
|
||||
}
|
BIN
semester-4/СмП/lb-4/src/public/assets/logo.png
Normal file
BIN
semester-4/СмП/lb-4/src/public/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 MiB |
43
semester-4/СмП/lb-4/src/public/css/style.css
Normal file
43
semester-4/СмП/lb-4/src/public/css/style.css
Normal file
@ -0,0 +1,43 @@
|
||||
body {
|
||||
font-family: monospace;
|
||||
background-color: #f0f0f0;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
header,
|
||||
footer {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
nav a:not(:hover) {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.product-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.product-list>* {
|
||||
border: 1px solid #eee;
|
||||
padding: 10px;
|
||||
}
|
109
semester-4/СмП/lb-4/src/src/Controllers/AuthController.php
Normal file
109
semester-4/СмП/lb-4/src/src/Controllers/AuthController.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
class AuthController
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct(DB $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function showLogin(): void
|
||||
{
|
||||
if (isset($_SESSION['user'])) {
|
||||
header('Location: ?page=home');
|
||||
exit();
|
||||
}
|
||||
|
||||
$data = ['title' => 'Вхід в систему'];
|
||||
$this->render('login', $data);
|
||||
}
|
||||
|
||||
public function login(): void
|
||||
{
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
$error = '';
|
||||
|
||||
if (empty($username) || empty($password)) {
|
||||
$error = 'Заповніть всі поля';
|
||||
} else {
|
||||
try {
|
||||
$user = $this->db->authenticate_user($username, $password);
|
||||
if ($user) {
|
||||
$_SESSION['user'] = $user;
|
||||
header('Location: ?page=home');
|
||||
exit();
|
||||
} else {
|
||||
$error = 'Невірні дані для входу';
|
||||
}
|
||||
} catch (DbException $e) {
|
||||
$error = 'Помилка системи';
|
||||
error_log("Login error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$data = ['title' => 'Вхід в систему', 'error' => $error];
|
||||
$this->render('login', $data);
|
||||
}
|
||||
|
||||
public function showRegister(): void
|
||||
{
|
||||
if (isset($_SESSION['user'])) {
|
||||
header('Location: ?page=home');
|
||||
exit();
|
||||
}
|
||||
|
||||
$data = ['title' => 'Реєстрація'];
|
||||
$this->render('register', $data);
|
||||
}
|
||||
|
||||
public function register(): void
|
||||
{
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$surname = trim($_POST['surname'] ?? '');
|
||||
$age = (int)($_POST['age'] ?? 0);
|
||||
$error = '';
|
||||
|
||||
if (empty($username) || empty($password) || empty($name)) {
|
||||
$error = 'Заповніть всі обов\'язкові поля';
|
||||
} elseif (strlen($password) < 6) {
|
||||
$error = 'Пароль повинен містити мінімум 6 символів';
|
||||
} else {
|
||||
try {
|
||||
if ($this->db->register_user($username, $password, $name, $surname, $age)) {
|
||||
$user = $this->db->authenticate_user($username, $password);
|
||||
$_SESSION['user'] = $user;
|
||||
header('Location: ?page=home');
|
||||
exit();
|
||||
} else {
|
||||
$error = 'Користувач з таким іменем вже існує';
|
||||
}
|
||||
} catch (DbException $e) {
|
||||
$error = 'Помилка реєстрації';
|
||||
error_log("Registration error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$data = ['title' => 'Реєстрація', 'error' => $error];
|
||||
$this->render('register', $data);
|
||||
}
|
||||
|
||||
public function logout(): void
|
||||
{
|
||||
session_destroy();
|
||||
header('Location: ?page=login');
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,mixed> $data
|
||||
*/
|
||||
private function render(string $template, array $data = []): void
|
||||
{
|
||||
extract($data);
|
||||
include 'templates/pages/' . $template . '.php';
|
||||
}
|
||||
}
|
77
semester-4/СмП/lb-4/src/src/Controllers/CartController.php
Normal file
77
semester-4/СмП/lb-4/src/src/Controllers/CartController.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
class CartController
|
||||
{
|
||||
private $cartRepo;
|
||||
|
||||
public function __construct(DB $db)
|
||||
{
|
||||
$this->cartRepo = new CartRepository($db);
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$data = [
|
||||
'title' => 'Кошик',
|
||||
'cart_items' => $this->cartRepo->getItems(),
|
||||
'cart_total' => $this->cartRepo->getTotal(),
|
||||
'cart_count' => $this->cartRepo->getCount(),
|
||||
'user' => $_SESSION['user']
|
||||
];
|
||||
|
||||
$this->render('cart', $data);
|
||||
}
|
||||
|
||||
public function add(): void
|
||||
{
|
||||
$product_id = filter_input(INPUT_POST, 'product_id', FILTER_VALIDATE_INT);
|
||||
$quantity = filter_input(INPUT_POST, 'quantity', FILTER_VALIDATE_INT);
|
||||
|
||||
if ($product_id !== false && $product_id !== null && $quantity !== false && $quantity !== null) {
|
||||
try {
|
||||
$this->cartRepo->addItem($product_id, $quantity);
|
||||
} catch (DbException $e) {
|
||||
error_log("Cart handling error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: ?page=items');
|
||||
exit();
|
||||
}
|
||||
|
||||
public function remove(): void
|
||||
{
|
||||
$product_id = filter_input(INPUT_POST, 'product_id', FILTER_VALIDATE_INT);
|
||||
|
||||
if ($product_id !== false && $product_id !== null) {
|
||||
try {
|
||||
$this->cartRepo->removeItem($product_id);
|
||||
} catch (DbException $e) {
|
||||
error_log("Cart handling error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: ?page=cart');
|
||||
exit();
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
try {
|
||||
$this->cartRepo->clear();
|
||||
} catch (DbException $e) {
|
||||
error_log("Cart handling error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
header('Location: ?page=cart');
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,mixed> $data
|
||||
*/
|
||||
private function render(string $template, array $data = []): void
|
||||
{
|
||||
extract($data);
|
||||
include 'templates/pages/' . $template . '.php';
|
||||
}
|
||||
}
|
30
semester-4/СмП/lb-4/src/src/Controllers/HomeController.php
Normal file
30
semester-4/СмП/lb-4/src/src/Controllers/HomeController.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
class HomeController
|
||||
{
|
||||
private $cartRepo;
|
||||
|
||||
public function __construct(DB $db)
|
||||
{
|
||||
$this->cartRepo = new CartRepository($db);
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$data = [
|
||||
'title' => 'Головна сторінка',
|
||||
'cart_count' => $this->cartRepo->getCount(),
|
||||
'user' => $_SESSION['user']
|
||||
];
|
||||
|
||||
$this->render('home', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,mixed> $data
|
||||
*/
|
||||
private function render(string $template, array $data = []): void
|
||||
{
|
||||
extract($data);
|
||||
include 'templates/pages/' . $template . '.php';
|
||||
}
|
||||
}
|
33
semester-4/СмП/lb-4/src/src/Controllers/ItemsController.php
Normal file
33
semester-4/СмП/lb-4/src/src/Controllers/ItemsController.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
class ItemsController
|
||||
{
|
||||
private $db;
|
||||
private $cartRepo;
|
||||
|
||||
public function __construct(DB $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
$this->cartRepo = new CartRepository($db);
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$data = [
|
||||
'title' => 'Сторінка товарів',
|
||||
'items' => $this->db->get_items(),
|
||||
'cart_count' => $this->cartRepo->getCount(),
|
||||
'user' => $_SESSION['user']
|
||||
];
|
||||
|
||||
$this->render('items', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,mixed> $data
|
||||
*/
|
||||
private function render(string $template, array $data = []): void
|
||||
{
|
||||
extract($data);
|
||||
include 'templates/pages/' . $template . '.php';
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
class ProfileController
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct(DB $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function showProfile(): void
|
||||
{
|
||||
$data = ['title' => 'Профіль'];
|
||||
$this->render('profile', $data);
|
||||
}
|
||||
|
||||
public function updateProfile(): void
|
||||
{
|
||||
$user = $_SESSION['user'];
|
||||
$id = $user['id'];
|
||||
$name = trim($_POST['name'] ?? $user['name']);
|
||||
$surname = trim($_POST['surname'] ?? $user['surname']);
|
||||
$age = (int)($_POST['age'] ?? $user['age']);
|
||||
$description = trim($_POST['description'] ?? $user['description']);
|
||||
$photo_path = $user['photo_path'];
|
||||
$error = '';
|
||||
|
||||
if (mb_strlen($name) < 2 && mb_strlen($surname) < 2) {
|
||||
$error = "Ім'я та прізвище повинні мати довжину більше 1 символа";
|
||||
$data = ['title' => 'Профіль', 'error' => $error];
|
||||
$this->render('profile', $data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mb_strlen($description) < 50) {
|
||||
$error = 'Біоаграфія не може бути менше 50 символів';
|
||||
$data = ['title' => 'Профіль', 'error' => $error];
|
||||
$this->render('profile', $data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_FILES['photo']) && $_FILES['photo']['error'] === UPLOAD_ERR_OK) {
|
||||
if (!in_array($_FILES['photo']['type'], ['image/jpeg', 'image/png'])) {
|
||||
$error = 'Неправильний формат файлу';
|
||||
$data = ['title' => 'Профіль', 'error' => $error];
|
||||
$this->render('profile', $data);
|
||||
return;
|
||||
}
|
||||
|
||||
$uploads = 'uploads/';
|
||||
if (!is_dir($uploads)) {
|
||||
mkdir($uploads, 0755, true);
|
||||
}
|
||||
|
||||
$new_path = $uploads . $id . '-' . time() . '.' . pathinfo($_FILES['photo']['name'], PATHINFO_EXTENSION);
|
||||
|
||||
if (!move_uploaded_file($_FILES['photo']['tmp_name'], $new_path)) {
|
||||
$error = 'Помилка під час переміщення файлу';
|
||||
$data = ['title' => 'Профіль', 'error' => $error];
|
||||
$this->render('profile', $data);
|
||||
return;
|
||||
}
|
||||
|
||||
$photo_path = $new_path;
|
||||
}
|
||||
|
||||
try {
|
||||
$success = $this->db->update_user($id, $name, $surname, $description, $photo_path, $age);
|
||||
|
||||
if ($success) {
|
||||
$user = $this->db->get_user_by_id($id);
|
||||
$_SESSION['user'] = $user;
|
||||
} else {
|
||||
$error = 'Під час оновлення даних сталася помилка';
|
||||
}
|
||||
|
||||
header('Location: ?page=profile');
|
||||
exit();
|
||||
} catch (DbException $e) {
|
||||
$error = 'Помилка оновлення профілю';
|
||||
error_log("Profile update error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$data = ['title' => 'Профіль', 'error' => $error];
|
||||
$this->render('profile', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,mixed> $data
|
||||
*/
|
||||
private function render(string $template, array $data = []): void
|
||||
{
|
||||
extract($data);
|
||||
include 'templates/pages/' . $template . '.php';
|
||||
}
|
||||
}
|
45
semester-4/СмП/lb-4/src/src/Database/CartRepository.php
Normal file
45
semester-4/СмП/lb-4/src/src/Database/CartRepository.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
class CartRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct(DB $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function getItems(): array
|
||||
{
|
||||
return $this->db->get_cart();
|
||||
}
|
||||
|
||||
public function getTotal(): float
|
||||
{
|
||||
return $this->db->get_cart_total();
|
||||
}
|
||||
|
||||
public function getCount(): int
|
||||
{
|
||||
return $this->db->get_cart_count();
|
||||
}
|
||||
|
||||
public function addItem(int $id, int $quantity): bool
|
||||
{
|
||||
return $this->db->add_to_cart($id, $quantity);
|
||||
}
|
||||
|
||||
public function removeItem(int $id): bool
|
||||
{
|
||||
return $this->db->remove_from_cart($id);
|
||||
}
|
||||
|
||||
public function clear(): bool
|
||||
{
|
||||
return $this->db->empty_cart();
|
||||
}
|
||||
|
||||
public function getItemQuantity(int $id): int
|
||||
{
|
||||
return $this->db->get_cart_item_quantity($id);
|
||||
}
|
||||
}
|
394
semester-4/СмП/lb-4/src/src/Database/DB.php
Normal file
394
semester-4/СмП/lb-4/src/src/Database/DB.php
Normal file
@ -0,0 +1,394 @@
|
||||
<?php
|
||||
|
||||
class DbException extends Exception {}
|
||||
|
||||
class DB
|
||||
{
|
||||
private $pdo;
|
||||
|
||||
/**
|
||||
* Initializes database
|
||||
*
|
||||
* @param string $db_path
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function __construct($db_path)
|
||||
{
|
||||
try {
|
||||
$this->pdo = new PDO("sqlite:" . $db_path);
|
||||
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Connection to DB failed.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
surname TEXT NOT NULL,
|
||||
description TEXT NULL,
|
||||
photo_path TEXT NULL,
|
||||
age INTEGER DEFAULT 0
|
||||
);
|
||||
");
|
||||
|
||||
if ($this->pdo->query("SELECT COUNT(*) FROM users;")->fetchColumn() == 0) {
|
||||
$default_password = password_hash('admin123', PASSWORD_DEFAULT);
|
||||
$this->pdo->exec("
|
||||
INSERT INTO users (username, password, name, surname, description, age)
|
||||
VALUES ('admin', '$default_password', 'Адміністратор', 'Адміністратор', 'Адміністратор', 25);
|
||||
");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising users table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
price REAL NOT NULL
|
||||
);
|
||||
");
|
||||
if ($this->pdo->query("SELECT COUNT(id) FROM items;")->fetchColumn() == 0) {
|
||||
$this->pdo->exec("
|
||||
INSERT INTO items (name, price) VALUES ('Молоко пастеризоване', 32.50);
|
||||
INSERT INTO items (name, price) VALUES ('Хліб чорний', 18.00);
|
||||
INSERT INTO items (name, price) VALUES ('Сир білий', 85.00);
|
||||
INSERT INTO items (name, price) VALUES ('Сметана 20%', 45.80);
|
||||
INSERT INTO items (name, price) VALUES ('Кефір 1%', 28.50);
|
||||
INSERT INTO items (name, price) VALUES ('Вода газована', 25.00);
|
||||
INSERT INTO items (name, price) VALUES ('Печиво \"Весна\"', 42.30);
|
||||
INSERT INTO items (name, price) VALUES ('Масло вершкове', 125.00);
|
||||
INSERT INTO items (name, price) VALUES ('Йогурт натуральний', 38.90);
|
||||
INSERT INTO items (name, price) VALUES ('Сік апельсиновий', 55.00);
|
||||
");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS cart (
|
||||
id INTEGER NOT NULL UNIQUE,
|
||||
count INTEGER NOT NULL,
|
||||
FOREIGN KEY(id) REFERENCES items(id) ON DELETE CASCADE
|
||||
);
|
||||
");
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising cart table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates user by id
|
||||
* @param int $id
|
||||
* @param string $name
|
||||
* @param string $surname
|
||||
* @param string $description
|
||||
* @param int $age
|
||||
* @param string $photo_path
|
||||
*/
|
||||
public function update_user($id, $name, $surname, $description, $photo_path, $age): bool
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("
|
||||
UPDATE users
|
||||
SET name = :name, surname = :surname, description = :description, age = :age, photo_path = :photo_path
|
||||
WHERE id = :id
|
||||
");
|
||||
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':surname', $surname, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':description', $description, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':photo_path', $photo_path, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':age', $age, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error updating user.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate user by username and password
|
||||
* @param mixed $username
|
||||
* @param mixed $password
|
||||
*/
|
||||
public function authenticate_user($username, $password): ?array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("SELECT id, username, name, surname, description, photo_path, age FROM users WHERE username = :username");
|
||||
$stmt->bindParam(':username', $username, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($user) {
|
||||
$stmt = $this->pdo->prepare("SELECT password FROM users WHERE username = :username");
|
||||
$stmt->bindParam(':username', $username, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
$stored_password = $stmt->fetchColumn();
|
||||
|
||||
if (password_verify($password, $stored_password)) {
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error authenticating user.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register new user
|
||||
* @param mixed $username
|
||||
* @param mixed $password
|
||||
* @param mixed $name
|
||||
* @param mixed $surname
|
||||
* @param mixed $age
|
||||
*/
|
||||
public function register_user($username, $password, $name, $surname, $age): bool
|
||||
{
|
||||
try {
|
||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||
$stmt = $this->pdo->prepare("INSERT INTO users (username, password, name, surname, age) VALUES (:username, :password, :name, :surname, :age)");
|
||||
$stmt->bindParam(':username', $username, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':password', $hashed_password, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':surname', $surname, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':age', $age, PDO::PARAM_INT);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error registering user.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user by ID
|
||||
* @param mixed $id
|
||||
*/
|
||||
public function get_user_by_id($id): ?array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("SELECT id, username, name, surname, description, age, photo_path FROM users WHERE id = :id");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return $result ?: null;
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving user by ID.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items from the database.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_items(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query("SELECT id, name, price FROM items ORDER BY id;");
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving data from the items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a specific item by ID
|
||||
*
|
||||
* @param int $id
|
||||
* @return array|null
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_item_by_id($id): ?array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("SELECT id, name, price FROM items WHERE id = :id;");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return $result ?: null;
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving item by ID.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items in the cart from the database without price info.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart_no_price(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT items.name, cart.count
|
||||
FROM cart
|
||||
INNER JOIN items ON cart.id = items.id
|
||||
ORDER BY cart.id;"
|
||||
);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving cart items without price.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items in the cart from the database.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT
|
||||
cart.id,
|
||||
items.name,
|
||||
items.price,
|
||||
cart.count,
|
||||
ROUND(items.price * cart.count, 2) as total_price
|
||||
FROM cart
|
||||
INNER JOIN items ON cart.id = items.id
|
||||
ORDER BY cart.id;"
|
||||
);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving cart items.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total items count in cart
|
||||
*
|
||||
* @return int
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart_count(): int
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query("SELECT COALESCE(SUM(count), 0) FROM cart;");
|
||||
return (int)$stmt->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error getting cart count.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total price of all items in cart
|
||||
*
|
||||
* @return float
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart_total(): float
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT COALESCE(SUM(items.price * cart.count), 0.0)
|
||||
FROM cart
|
||||
INNER JOIN items ON cart.id = items.id;"
|
||||
);
|
||||
return (float)$stmt->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error calculating cart total.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add item to the cart or update its quantity.
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $count
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error or item doesn't exist.
|
||||
*/
|
||||
public function add_to_cart($id, $count): bool
|
||||
{
|
||||
try {
|
||||
// Check if the item exists
|
||||
$item = $this->get_item_by_id($id);
|
||||
if (!$item) {
|
||||
throw new DbException("Item with ID $id does not exist.");
|
||||
}
|
||||
|
||||
// If count is 0 or less, remove the item from the cart
|
||||
if ($count <= 0) {
|
||||
return $this->remove_from_cart($id);
|
||||
}
|
||||
|
||||
// Insert or update the cart item
|
||||
$stmt = $this->pdo->prepare(
|
||||
"INSERT INTO cart (id, count)
|
||||
VALUES (:id, :count)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
count = excluded.count;"
|
||||
);
|
||||
return $stmt->execute(['id' => $id, 'count' => $count]);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error adding/updating item in the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty the cart
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function empty_cart(): bool
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("DELETE FROM cart");
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error removing item from the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the cart
|
||||
*
|
||||
* @param int $id
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function remove_from_cart($id): bool
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("DELETE FROM cart WHERE id = :id");
|
||||
return $stmt->execute(['id' => $id]);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error removing item from the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item quantity in cart
|
||||
*
|
||||
* @param int $id
|
||||
* @return int The quantity of the item in the cart, or 0 if not found.
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart_item_quantity(int $id): int
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("SELECT count FROM cart WHERE id = :id");
|
||||
$stmt->execute(['id' => $id]);
|
||||
$result = $stmt->fetchColumn();
|
||||
return $result !== false ? (int)$result : 0;
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error getting cart item quantity.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
5
semester-4/СмП/lb-4/src/templates/components/error.php
Normal file
5
semester-4/СмП/lb-4/src/templates/components/error.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php if (isset($error) && !empty($error)): ?>
|
||||
<div style="color: red; margin-bottom: 20px; padding: 10px; border: 1px solid red; background: #ffebee;">
|
||||
<?php echo htmlspecialchars($error); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
5
semester-4/СмП/lb-4/src/templates/components/footer.php
Normal file
5
semester-4/СмП/lb-4/src/templates/components/footer.php
Normal file
@ -0,0 +1,5 @@
|
||||
<footer>
|
||||
<?php $cart_count = $cart_count ?? 0;
|
||||
include 'navigation.php'; ?>
|
||||
<p>© <?php echo date("Y"); ?> ТОВ "Весна". Усі права захищені.</p>
|
||||
</footer>
|
7
semester-4/СмП/lb-4/src/templates/components/header.php
Normal file
7
semester-4/СмП/lb-4/src/templates/components/header.php
Normal file
@ -0,0 +1,7 @@
|
||||
<header>
|
||||
<h1>Продовольчий магазин "Весна"</h1>
|
||||
<?php if (isset($_SESSION['user'])): ?>
|
||||
<h3>Добрий день <?php echo htmlspecialchars($_SESSION['user']['name']); ?></h3>
|
||||
<?php endif; ?>
|
||||
<?php include 'navigation.php'; ?>
|
||||
</header>
|
17
semester-4/СмП/lb-4/src/templates/components/navigation.php
Normal file
17
semester-4/СмП/lb-4/src/templates/components/navigation.php
Normal file
@ -0,0 +1,17 @@
|
||||
<nav>
|
||||
<?php if (isset($_SESSION['user'])): ?>
|
||||
<a href="?page=home">Головна</a>
|
||||
|
|
||||
<a href="?page=items">Товари</a>
|
||||
|
|
||||
<a href="?page=cart">Кошик (<?php echo $cart_count ?? 0; ?>)</a>
|
||||
|
|
||||
<a href="?page=profile">Профіль</a>
|
||||
|
|
||||
<a href="?page=logout">Вихід</a>
|
||||
<?php else: ?>
|
||||
<a href="?page=login">Вхід</a>
|
||||
|
|
||||
<a href="?page=register">Реєстрація</a>
|
||||
<?php endif; ?>
|
||||
</nav>
|
20
semester-4/СмП/lb-4/src/templates/layout.php
Normal file
20
semester-4/СмП/lb-4/src/templates/layout.php
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="uk">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?php echo htmlspecialchars($title ?? 'Продовольчий магазин "Весна"'); ?></title>
|
||||
<link rel="stylesheet" href="public/css/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<?php include 'components/header.php'; ?>
|
||||
|
||||
<div class="container">
|
||||
<?php echo $content; ?>
|
||||
</div>
|
||||
|
||||
<?php include 'components/footer.php'; ?>
|
||||
</body>
|
||||
|
||||
</html>
|
9
semester-4/СмП/lb-4/src/templates/pages/404.php
Normal file
9
semester-4/СмП/lb-4/src/templates/pages/404.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php ob_start(); ?>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: space-between;">
|
||||
<h1 style="text-align: center;">Будь-ласка увійдіть в акаунт для доступу до цієї сторінки</h1>
|
||||
<img src="public/assets/logo.png" alt="logo" style="width: 90%;">
|
||||
</div>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include 'templates/layout.php';
|
||||
?>
|
46
semester-4/СмП/lb-4/src/templates/pages/cart.php
Normal file
46
semester-4/СмП/lb-4/src/templates/pages/cart.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php ob_start(); ?>
|
||||
<?php if (empty($cart_items)): ?>
|
||||
<div style="display: flex; align-items: center; justify-content: space-evenly;">
|
||||
<h3>Ваш кошик порожній <a href="?page=items">Перейти до покупок</a></h3>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div style="display: flex; align-items: center; justify-content: space-evenly;">
|
||||
<h3>Ваш кошик</h3>
|
||||
<h3 class="cart-summary">
|
||||
Загальна сума: <?php echo number_format($cart_total, 2); ?> грн
|
||||
</h3>
|
||||
|
||||
<button type="submit">Сплатити</button>
|
||||
|
||||
<form action="?page=cart&action=clear" method="POST" style="display: inline;">
|
||||
<button type="submit">Очистити</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="product-list">
|
||||
<?php foreach ($cart_items as $item): ?>
|
||||
<div>
|
||||
<h2>
|
||||
<?php echo htmlspecialchars($item['name']); ?>
|
||||
<br>
|
||||
<?php echo htmlspecialchars($item['count']); ?> шт.
|
||||
</h2>
|
||||
|
||||
<span>Ціна за одиницю: <?php echo number_format($item['price'], 2); ?> грн</span>
|
||||
<br>
|
||||
<span>Загальна ціна: <?php echo number_format($item['total_price'], 2); ?> грн</span>
|
||||
|
||||
<br><br>
|
||||
|
||||
<form action="?page=cart&action=remove" method="POST">
|
||||
<input type="hidden" name="product_id" value="<?php echo htmlspecialchars($item['id']); ?>">
|
||||
<button type="submit" style="width: 100%;">Видалити</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include 'templates/layout.php';
|
||||
?>
|
8
semester-4/СмП/lb-4/src/templates/pages/home.php
Normal file
8
semester-4/СмП/lb-4/src/templates/pages/home.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php ob_start(); ?>
|
||||
<div style="display: flex; flex-direction: column; align-items: center;">
|
||||
<img src="public/assets/logo.png" alt="logo" style="width: 90%;">
|
||||
</div>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include 'templates/layout.php';
|
||||
?>
|
21
semester-4/СмП/lb-4/src/templates/pages/items.php
Normal file
21
semester-4/СмП/lb-4/src/templates/pages/items.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php ob_start(); ?>
|
||||
<h2>Доступні товари</h2>
|
||||
<div class="product-list">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<div>
|
||||
<h2><?php echo htmlspecialchars($item['name']); ?></h2>
|
||||
<h3>Ціна: <?php echo number_format($item['price'], 2); ?> грн</h3>
|
||||
|
||||
<form action="?page=cart&action=add" method="POST">
|
||||
<input type="hidden" name="product_id" value="<?php echo htmlspecialchars($item['id']); ?>">
|
||||
<label for="quantity_<?php echo htmlspecialchars($item['id']); ?>">Кількість:</label>
|
||||
<input type="number" id="quantity_<?php echo htmlspecialchars($item['id']); ?>" name="quantity" value="0" min="0" max="100">
|
||||
<button type="submit">Купити</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include 'templates/layout.php';
|
||||
?>
|
28
semester-4/СмП/lb-4/src/templates/pages/login.php
Normal file
28
semester-4/СмП/lb-4/src/templates/pages/login.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php ob_start(); ?>
|
||||
<div style="max-width: 400px; margin: 0 auto;">
|
||||
<h2>Вхід в систему</h2>
|
||||
|
||||
<?php require 'templates/components/error.php' ?>
|
||||
|
||||
<form method="POST" action="?page=login">
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="username">Ім'я користувача:</label><br>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="password">Пароль:</label><br>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" style="width: 100%;">Увійти</button>
|
||||
</form>
|
||||
|
||||
<p style="text-align: center; margin-top: 20px;">
|
||||
<a href="?page=register">Немає акаунта? Зареєструватися</a>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include 'templates/layout.php';
|
||||
?>
|
32
semester-4/СмП/lb-4/src/templates/pages/profile.php
Normal file
32
semester-4/СмП/lb-4/src/templates/pages/profile.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php ob_start(); ?>
|
||||
|
||||
<?php require 'templates/components/error.php' ?>
|
||||
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: space-between;">
|
||||
<div style="width: 150px; height: 150px; border: 1px solid black; display: flex; justify-content: center; align-items: center; margin-bottom: 20px;">
|
||||
<img src="<?= htmlspecialchars($_SESSION['user']['photo_path']) ?>" alt="Profile Image" style="max-width: 100%; max-height: 100%; object-fit: contain;">
|
||||
</div>
|
||||
|
||||
<form style="display: grid; gap: 15px;" method="post" enctype="multipart/form-data">
|
||||
<input type="file" name="photo" accept="image/*">
|
||||
|
||||
<label style="font-weight: bold;">Ім'я</label>
|
||||
<input type="text" value="<?php echo $_SESSION['user']['name'] ?>" name="name">
|
||||
|
||||
<label style="font-weight: bold;">Фамілія</label>
|
||||
<input type="text" value="<?php echo $_SESSION['user']['surname'] ?>" name="surname">
|
||||
|
||||
<label style="font-weight: bold;">Вік</label>
|
||||
<input type="number" min="16" max="150" value="<?php echo $_SESSION['user']['age'] ?>" name="age">
|
||||
|
||||
<label style="font-weight: bold;">Про себе</label>
|
||||
<textarea style="height: 50px; resize: vertical;" name="description"><?php echo $_SESSION['user']['description'] ?></textarea>
|
||||
|
||||
<button>Сохранить</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include 'templates/layout.php';
|
||||
?>
|
43
semester-4/СмП/lb-4/src/templates/pages/register.php
Normal file
43
semester-4/СмП/lb-4/src/templates/pages/register.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php ob_start(); ?>
|
||||
<div style="max-width: 400px; margin: 0 auto;">
|
||||
<h2>Реєстрація</h2>
|
||||
|
||||
<?php require 'templates/components/error.php' ?>
|
||||
|
||||
<form method="POST" action="?page=register">
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="username">Логін *:</label><br>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="password">Пароль * (мінімум 6 символів):</label><br>
|
||||
<input type="password" name="password" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="name">Ім'я *:</label><br>
|
||||
<input type="text" name="name" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="surname">Прізвище *:</label><br>
|
||||
<input type="text" name="surname" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="age">Вік *:</label><br>
|
||||
<input type="number" name="age" min="16" max="150" style="width: 97%;" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" style="width: 100%;">Зареєструватися</button>
|
||||
</form>
|
||||
|
||||
<p style="text-align: center; margin-top: 20px;">
|
||||
<a href="?page=login">Вже є акаунт? Увійти</a>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include 'templates/layout.php';
|
||||
?>
|
Reference in New Issue
Block a user