diff --git a/semester-4/СмП/lb-2/README.md b/semester-4/СмП/lb-2/README.md new file mode 100644 index 0000000..f28ef57 --- /dev/null +++ b/semester-4/СмП/lb-2/README.md @@ -0,0 +1,4 @@ +> [!NOTE] +> Викладач: Сокорчук І. П. +> +> Оцінка: 100 diff --git a/semester-4/СмП/lb-2/src/pzpi-23-2-sytnyk-yehor-task3.php b/semester-4/СмП/lb-2/src/pzpi-23-2-sytnyk-yehor-task3.php new file mode 100755 index 0000000..5c11240 --- /dev/null +++ b/semester-4/СмП/lb-2/src/pzpi-23-2-sytnyk-yehor-task3.php @@ -0,0 +1,469 @@ +#!/usr/bin/php + +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 settings ( + name TEXT, + age TEXT + ); + "); + + if ($this->pdo->query("SELECT COUNT(*) FROM settings;")->fetchColumn() == 0) { + $this->pdo->exec("INSERT INTO settings (name, age) VALUES ('user', 0);"); + } + } catch (PDOException $e) { + throw new DbException("Error initialising settings 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 ('Молоко пастеризоване', 12); + INSERT INTO items (name, price) VALUES ('Хліб чорний', 9); + INSERT INTO items (name, price) VALUES ('Сир білий', 21); + INSERT INTO items (name, price) VALUES ('Сметана 20%', 25); + INSERT INTO items (name, price) VALUES ('Кефір 1%', 19); + INSERT INTO items (name, price) VALUES ('Вода газована', 18); + INSERT INTO items (name, price) VALUES ('Печиво \"Весна\"', 14); + "); + } + } 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 item(id) + ); + "); + } catch (PDOException $e) { + throw new DbException("Error initialising cart table.\nCaused by: " . $e->getMessage()); + } + } + + /** + * Updates user information in the database + * + * @param string $name + * @param int $age + * @return void + * @throws DbException If there's a database error. + */ + public function update_user($name, $age): void + { + try { + $stmt = $this->pdo->prepare("UPDATE settings SET name = :name, age = :age;"); + + $stmt->bindParam(':name', $name, PDO::PARAM_STR); + $stmt->bindParam(':age', $age, PDO::PARAM_INT); + + $stmt->execute(); + } catch (PDOException $e) { + throw new DbException("Error updating user info.\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 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 name, 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 inserting init data in the items table.\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, name, price, count, price*count 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 inserting init data in the items table.\nCaused by: " . $e->getMessage()); + } + } + + /** + * Add item to the cart + * + * @param int $id + * @param int $count + * + * @return bool + * @throws DbException If there's a database error. + */ + public function add_to_cart($id, $count): bool + { + try { + $stmt = $this->pdo->prepare( + "INSERT INTO cart + (id, count) + VALUES + (:id, :count) + ON CONFLICT(id) + DO UPDATE SET + count = :count + WHERE id = :id;" + ); + return $stmt->execute(['id' => $id, 'count' => $count]); + } catch (PDOException $e) { + throw new DbException("Error adding item to 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 removimg item from the cart.\nCaused by: " . $e->getMessage()); + } + } +} + +class App +{ + private $db; + private $state; + + private $menu_ops = <<<'END' + 1 Вибрати товари + 2 Отримати підсумковий рахунок + 3 Налаштувати свій профіль + 0 Вийти з програми + END; + private $hello = <<<'END' + ################################ + # ПРОДОВОЛЬЧИЙ МАГАЗИН "ВЕСНА" # + ################################ + END; + + + /** + * @param string $db_path + */ + public function __construct($db_path) + { + try { + $this->db = new DB($db_path); + $this->state = State::Hello; + } catch (DbException $e) { + throw new AppException("Error initializing app.\nCaused by: " . $e); + } + } + + public function poll(): void + { + while ($this->state != State::Exit) { + switch ($this->state) { + case State::Hello: + $this->hello(); + break; + case State::Menu: + $this->menu(); + break; + case State::Items: + $this->items(); + break; + case State::Checkout: + $this->checkout(); + break; + case State::Settins: + $this->settings(); + break; + + default: + break; + } + } + } + + private function menu(): void + { + echo "\n"; + echo "$this->menu_ops\n"; + + $op = readline('Введіть команду: '); + switch ($op) { + case '1': + $this->state = State::Items; + break; + case '2': + $this->state = State::Checkout; + break; + case '3': + $this->state = State::Settins; + break; + case '0': + $this->state = State::Exit; + break; + + default: + echo "ПОМИЛКА! Введіть правильну команду\n"; + break; + } + + echo "\n"; + } + private function hello(): void + { + echo "$this->hello\n"; + $this->state = State::Menu; + } + private function items(): void + { + $items = $this->db->get_items(); + array_unshift($items, ['id' => "№", 'name' => "НАЗВА", 'price' => "ЦІНА"]); + array_push($items, ['id' => " ", 'name' => "-----------", 'price' => ""]); + array_push($items, ['id' => "0", 'name' => "ПОВЕРНУТИСЯ", 'price' => ""]); + $columns = $this->count_columns($items); + + while (true) { + $this->print_lits($items, $columns); + + $id = readline("Виберіть товар: "); + + if ($id == '0') { + break; + } + + echo "\n"; + + $selected = null; + foreach ($items as $item) { + if ($item['id'] === (int)$id) $selected = $item; + } + + if ($selected == null) { + echo "ПОМИЛКА! ВКАЗАНО НЕПРАВИЛЬНИЙ НОМЕР ТОВАРУ\n"; + continue; + } + + echo "Вибрано: {$selected['name']}\n"; + + $count = (int)readline("Введіть кількість, штук: "); + + if ($count > 100) { + echo "ПОМИЛКА! Не можна додати більше 100 одиниць товару в кошик\n"; + continue; + } + + if ($count < 0) { + echo "ПОМИЛКА! Кількість не може бути від'ємною\n"; + continue; + } + + if ($count == 0) { + echo "ВИДАЛЯЮ З КОШИКА\n"; + $this->db->remove_from_cart($id); + } else { + $this->db->add_to_cart($id, $count); + } + + $cart = $this->db->get_cart_no_price(); + if (count($cart) == 0) { + echo "КОШИК ПОРОЖНІЙ\n"; + } else { + echo "\nУ КОШИКУ:\n"; + array_unshift($cart, ['name' => "НАЗВА", 'count' => "КІЛЬКІСТЬ"]); + $cart_columns = $this->count_columns($cart); + $this->print_lits($cart, $cart_columns); + echo "\n"; + } + } + + $this->state = State::Menu; + } + private function checkout(): void + { + $cart = $this->db->get_cart(); + if (count($cart) == 0) { + echo "КОШИК ПОРОЖНІЙ\n"; + $this->state = State::Menu; + return; + } else { + echo "У КОШИКУ:\n"; + array_unshift($cart, ['id' => "№", 'name' => "НАЗВА", 'price' => "ЦІНА", 'count' => "КІЛЬКІСТЬ", 'total_price' => "ВАРТІСТЬ"]); + $cart_columns = $this->count_columns($cart); + $this->print_lits($cart, $cart_columns); + } + + $total_price = array_reduce($cart, function ($carry, $item) { + return $carry + (int)$item['total_price']; + }, 0); + echo "РАЗОМ ДО СПЛАТИ: {$total_price}\n"; + + $this->state = State::Menu; + } + private function settings(): void + { + while (true) { + $name = readline("Ваше ім'я: "); + if ($name !== "" && preg_match("/[a-zA-Z]+/", $name)) + break; + } + + while (true) { + $age = readline("Ваш вік: "); + + if (!filter_var($age, FILTER_VALIDATE_INT)) { + echo "ПОМИЛКА! Вік користувача потрібно вказати числом\n\n"; + continue; + } + + if ($age < 7 || $age > 150) { + echo "ПОМИЛКА! Користувач повинен мати вік від 7 та до 150 років\n\n"; + continue; + } + + break; + } + + echo "\n"; + + $this->db->update_user($name, $age); + + $this->state = State::Menu; + } + + /** + * @param array $items + * @return array + */ + private function count_columns($items): array + { + $columns = []; + foreach ($items as $item) { + foreach ($item as $field => $value) { + if (!key_exists($field, $columns)) + $columns[$field] = mb_strlen($value); + else + $columns[$field] = max(mb_strlen($value), $columns[$field]); + } + } + + return $columns; + } + /** + * @param array $element + * @param array $columns + */ + private function pad_row($element, $columns): string + { + $result = []; + foreach ($element as $field => $value) + $result[] = mb_str_pad($value, $columns[$field], ' ', STR_PAD_RIGHT); + + return implode(" ", $result); + } + /** + * @param array $items + * @param array $columns + */ + private function print_lits($items, $columns): void + { + foreach ($items as $item) + echo $this->pad_row($item, $columns) . "\n"; + } +} + +try { + $app = new App("db.sqlite"); +} catch (AppException $e) { + echo $e; + exit(1); +} + +$app->poll(); diff --git a/semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.pdf b/semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.pdf new file mode 100644 index 0000000..1ef7cc0 Binary files /dev/null and b/semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.pdf differ diff --git a/semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.txt b/semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.txt new file mode 100644 index 0000000..b625b5c --- /dev/null +++ b/semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.txt @@ -0,0 +1,681 @@ + МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ + ХАРКІВСЬКИЙ НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ РАДІОЕЛЕКТРОНІКИ + + + + Кафедра Програмної інженерії + + + + + Звіт + з лабораторної роботи №2 + з дисципліни: «Скриптові мови програмування» + з теми: «Продовольчий магазин «Весна»» + + + + +Виконав: Перевірив: +ст. гр. ПЗПІ-23-2 Старший викладач кафедри ПІ +Ситник Є. С. Сокорчук І. П. + + + + + Харків – 2025 + 2 + 2 ПРОДОВОЛЬЧИЙ МАГАЗИН «ВЕСНА» + 2.1 Історія змін + +№ Дата Версія звіту Опис змін та виправлень +1 03.03.2025 0.1 Створено звіт + + 2.2 Мета роботи + + Лабораторна робота полягає у розробці консольного застосунку +«Продовольчий магазин «Весна»» засобами мови програмування «PHP». + + 2.3 Хід роботи + 2.3.1 Архітектура програми + + Для реалізації консольного застосунку було обрано об’єктно-орієнтований +підхід з використанням наступних компонентів: + + 2.3.1.1 Класи та їх призначення: + + – DB – клас для роботи з базою даних «SQLite», що забезпечує збереження + даних про товари, кошик користувача та налаштування профілю; + – App – основний клас застосунку, що реалізує логіку роботи програми та + взаємодію з користувачем; + – DbException та AppException – класи винятків для обробки помилок. + + 2.3.1.2 Enum State: + + Для управління станами програми використовується перелічення «State» з +наступними значеннями: + – Hello – привітальний екран; + – Menu – головне меню; + – Items – вибір товарів; + – Checkout – формування рахунку; + – Settings – налаштування профілю; + – Exit – вихід з програми. + 3 + 2.3.2 Структура бази даних + + Програма використовує базу даних SQLite з трьома таблицями: + – settings – зберігає інформацію про користувача (ім’я та вік); + – items – містить каталог товарів з їх назвами та цінами; + – cart – зберігає товари, додані до кошика, з їх кількістю. + + 2.3.3 Основний функціонал + + 2.3.3.1 Головне меню: + + Програма відображає заголовок магазину та пропонує користувачу чотири +основні опції: + – вибір товарів для покупки; + – перегляд підсумкового рахунку; + – налаштування профілю користувача; + – вихід з програми. + + 2.3.3.2 Вибір товарів: + + При виборі першого пункту меню користувач потрапляє в режим покупки +товарів, де: + – відображається список доступних товарів з цінами; + – можна вибрати товар за номером та вказати кількість; + – товари додаються до кошика; + – при введенні кількості «0» товар видаляється з кошика; + – реалізована валідація введених даних. + + 2.3.3.3 Підсумковий рахунок: + + Другий пункт меню формує детальний рахунок з інформацією про: + – номер, назву та ціну кожного товару; + – кількість товару в кошику; + – загальну вартість кожного товару; + – підсумкову суму до сплати. + 4 + 2.3.3.4 Налаштування профілю: + + Третій пункт дозволяє користувачу вказати своє ім’я та вік з валідацією: + – ім’я повинно містити хоча б одну літеру; + – вік повинен бути в діапазоні від 7 до 150 років. + + 2.3.4 Технічні деталі + + Програма написана з дотриманням сучасних стандартів «PHP»: + – використання строгої типізації; + – документування методів за допомогою «PHPDoc»; + – дотримання принципів «SOLID»; + – розділення логіки на окремі методи для кращої читабельності. + + 2.4 Висновки + + Під час даної лабораторної роботи я вивчив основні принципи роботи з «PHP» +для написання консольних програм. Зокрема, було освоєно: + – створення об’єктно-орієнтованих консольних застосунків на «PHP»; + – роботу з базою даних «SQLite» через «PDO»; + – обробку користувацького вводу та валідацію даних; + – використання перелічень («enum») для управління станами програми; + – реалізацію системи обробки винятків; + – форматування виводу в консолі для створення зручного інтерфейсу. + Програмне забезпечення, реалізоване протягом даної лабораторної роботи, +повністю відповідає поставленим вимогам та забезпечує всі необхідні функції для +роботи з продовольчим магазином. Застосунок має зручний інтерфейс, надійну +систему валідації даних та ефективно працює з базою даних для збереження +інформації про товари та користувача. + 5 + ДОДАТОК А + Відеозапис + + Відеозапис презентації результатів лабораторної роботи: + https://youtu.be/CMGeL1OdyvI + + Хронологічний опис відеозапису: + 00:00 – Вступ та опис завдання + 00:19 – Архітектура програми + 00:39 – Робота з базою даних + 01:43 – Реалізація станів програми + 02:17 – Формування списку товарів + 03:13 – Додавання товарів до кошика + 04:15 – Робота з кошиком + 05:10 – Налаштування профілю користувача + 05:38 – Демонстрація роботи програми + 6 + ДОДАТОК Б + Програмний код + + Б.1 Скрипт з реалізацією програми + + GitHub репозиторій: + https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab2/smp-pzpi-23-2-sytnyk-yehor-lab2/smp-pzpi-23-2-sytnyk-yehor-lab2-code + + 1 #!/usr/bin/php + 2 + 3 pdo = new PDO("sqlite:" . $db_path); + 32 $this->pdo->setAttribute(PDO::ATTR_ERRMODE, + PDO::ERRMODE_EXCEPTION); + 33 } catch (PDOException $e) { + 34 throw new DbException("Connection to DB failed.\nCaused + by: " . $e->getMessage()); + 35 } + 36 + 37 try { + 38 $this->pdo->exec(" + 39 CREATE TABLE IF NOT EXISTS settings ( + 40 name TEXT, + 41 age TEXT + 7 + +42 ); +43 "); +44 +45 if ($this->pdo->query("SELECT COUNT(*) FROM settings;")- + >fetchColumn() == 0) { +46 $this->pdo->exec("INSERT INTO settings (name, age) + VALUES ('user', 0);"); +47 } +48 } catch (PDOException $e) { +49 throw new DbException("Error initialising settings table. + \nCaused by: " . $e->getMessage()); +50 } +51 +52 try { +53 $this->pdo->exec(" +54 CREATE TABLE IF NOT EXISTS items ( +55 id INTEGER PRIMARY KEY AUTOINCREMENT, +56 name TEXT NOT NULL, +57 price REAL NOT NULL +58 ); +59 "); +60 +61 if ($this->pdo->query("SELECT COUNT(id) FROM items;")- + >fetchColumn() == 0) { +62 $this->pdo->exec(" +63 INSERT INTO items (name, price) VALUES ('Молоко + пастеризоване', 12); +64 INSERT INTO items (name, price) VALUES ('Хліб + чорний', 9); +65 INSERT INTO items (name, price) VALUES ('Сир + білий', 21); +66 INSERT INTO items (name, price) VALUES ('Сметана + 20%', 25); +67 INSERT INTO items (name, price) VALUES ('Кефір + 1%', 19); +68 INSERT INTO items (name, price) VALUES ('Вода + газована', 18); +69 INSERT INTO items (name, price) VALUES ('Печиво + \"Весна\"', 14); +70 "); +71 } +72 } catch (PDOException $e) { +73 throw new DbException("Error initialising items table. + \nCaused by: " . $e->getMessage()); +74 } +75 +76 try { +77 $this->pdo->exec(" +78 CREATE TABLE IF NOT EXISTS cart ( +79 id INTEGER NOT NULL UNIQUE, +80 count INTEGER NOT NULL, +81 FOREIGN KEY(id) REFERENCES item(id) +82 ); +83 "); +84 } catch (PDOException $e) { + 8 + + 85 throw new DbException("Error initialising cart table. + \nCaused by: " . $e->getMessage()); + 86 } + 87 } + 88 + 89 /** + 90 * Updates user information in the database + 91 * + 92 * @param string $name + 93 * @param int $age + 94 * @return void + 95 * @throws DbException If there's a database error. + 96 */ + 97 public function update_user($name, $age): void + 98 { + 99 try { +100 $stmt = $this->pdo->prepare("UPDATE settings SET name + = :name, age = :age;"); +101 +102 $stmt->bindParam(':name', $name, PDO::PARAM_STR); +103 $stmt->bindParam(':age', $age, PDO::PARAM_INT); +104 +105 $stmt->execute(); +106 } catch (PDOException $e) { +107 throw new DbException("Error updating user info.\nCaused + by: " . $e->getMessage()); +108 } +109 } +110 +111 /** +112 * Fetches all items from the database. +113 * +114 * @return array[] +115 * @throws DbException If there's a database error. +116 */ +117 public function get_items(): array +118 { +119 try { +120 $stmt = $this->pdo->query("SELECT id, name, price FROM + items ORDER BY id;"); +121 return $stmt->fetchAll(PDO::FETCH_ASSOC); +122 } catch (PDOException $e) { +123 throw new DbException("Error retrieving data from the + items table.\nCaused by: " . $e->getMessage()); +124 } +125 } +126 +127 /** +128 * Fetches all items in the cart from the database without price + info. +129 * +130 * @return array[] +131 * @throws DbException If there's a database error. +132 */ +133 public function get_cart_no_price(): array +134 { + 9 + +135 try { +136 $stmt = $this->pdo->query( +137 "SELECT name, count FROM cart +138 INNER JOIN items ON cart.id = items.id +139 ORDER BY cart.id;" +140 ); +141 return $stmt->fetchAll(PDO::FETCH_ASSOC); +142 } catch (PDOException $e) { +143 throw new DbException("Error inserting init data in the + items table.\nCaused by: " . $e->getMessage()); +144 } +145 } +146 +147 /** +148 * Fetches all items in the cart from the database. +149 * +150 * @return array[] +151 * @throws DbException If there's a database error. +152 */ +153 public function get_cart(): array +154 { +155 try { +156 $stmt = $this->pdo->query( +157 "SELECT +158 cart.id, name, price, count, price*count as + total_price +159 FROM cart +160 INNER JOIN items ON cart.id = items.id +161 ORDER BY cart.id;" +162 ); +163 return $stmt->fetchAll(PDO::FETCH_ASSOC); +164 } catch (PDOException $e) { +165 throw new DbException("Error inserting init data in the + items table.\nCaused by: " . $e->getMessage()); +166 } +167 } +168 +169 /** +170 * Add item to the cart +171 * +172 * @param int $id +173 * @param int $count +174 * +175 * @return bool +176 * @throws DbException If there's a database error. +177 */ +178 public function add_to_cart($id, $count): bool +179 { +180 try { +181 $stmt = $this->pdo->prepare( +182 "INSERT INTO cart +183 (id, count) +184 VALUES +185 (:id, :count) +186 ON CONFLICT(id) +187 DO UPDATE SET + 10 + +188 count = :count +189 WHERE id = :id;" +190 ); +191 return $stmt->execute(['id' => $id, 'count' => $count]); +192 } catch (PDOException $e) { +193 throw new DbException("Error adding item to the cart. + \nCaused by: " . $e->getMessage()); +194 } +195 } +196 +197 /** +198 * Remove an item from the cart +199 * +200 * @param int $id +201 * +202 * @return bool +203 * @throws DbException If there's a database error. +204 */ +205 public function remove_from_cart($id): bool +206 { +207 try { +208 $stmt = $this->pdo->prepare("DELETE FROM cart WHERE id + = :id"); +209 return $stmt->execute(['id' => $id]); +210 } catch (PDOException $e) { +211 throw new DbException("Error removimg item from the cart. + \nCaused by: " . $e->getMessage()); +212 } +213 } +214 } +215 +216 class App +217 { +218 private $db; +219 private $state; +220 +221 private $menu_ops = <<<'END' +222 1 Вибрати товари +223 2 Отримати підсумковий рахунок +224 3 Налаштувати свій профіль +225 0 Вийти з програми +226 END; +227 private $hello = <<<'END' +228 ################################ +229 # ПРОДОВОЛЬЧИЙ МАГАЗИН "ВЕСНА" # +230 ################################ +231 END; +232 +233 +234 /** +235 * @param string $db_path +236 */ +237 public function __construct($db_path) +238 { +239 try { +240 $this->db = new DB($db_path); + 11 + +241 $this->state = State::Hello; +242 } catch (DbException $e) { +243 throw new AppException("Error initializing app.\nCaused + by: " . $e); +244 } +245 } +246 +247 public function poll(): void +248 { +249 while ($this->state != State::Exit) { +250 switch ($this->state) { +251 case State::Hello: +252 $this->hello(); +253 break; +254 case State::Menu: +255 $this->menu(); +256 break; +257 case State::Items: +258 $this->items(); +259 break; +260 case State::Checkout: +261 $this->checkout(); +262 break; +263 case State::Settins: +264 $this->settings(); +265 break; +266 +267 default: +268 break; +269 } +270 } +271 } +272 +273 private function menu(): void +274 { +275 echo "\n"; +276 echo "$this->menu_ops\n"; +277 +278 $op = readline('Введіть команду: '); +279 switch ($op) { +280 case '1': +281 $this->state = State::Items; +282 break; +283 case '2': +284 $this->state = State::Checkout; +285 break; +286 case '3': +287 $this->state = State::Settins; +288 break; +289 case '0': +290 $this->state = State::Exit; +291 break; +292 +293 default: +294 echo "ПОМИЛКА! Введіть правильну команду\n"; +295 break; + 12 + +296 } +297 +298 echo "\n"; +299 } +300 private function hello(): void +301 { +302 echo "$this->hello\n"; +303 $this->state = State::Menu; +304 } +305 private function items(): void +306 { +307 $items = $this->db->get_items(); +308 array_unshift($items, ['id' => "№", 'name' => "НАЗВА", 'price' + => "ЦІНА"]); +309 array_push($items, ['id' => " ", 'name' => "-----------", + 'price' => ""]); +310 array_push($items, ['id' => "0", 'name' => "ПОВЕРНУТИСЯ", + 'price' => ""]); +311 $columns = $this->count_columns($items); +312 +313 while (true) { +314 $this->print_lits($items, $columns); +315 +316 $id = readline("Виберіть товар: "); +317 +318 if ($id == '0') { +319 break; +320 } +321 +322 echo "\n"; +323 +324 $selected = null; +325 foreach ($items as $item) { +326 if ($item['id'] === (int)$id) +327 $selected = $item; +328 } +329 +330 if ($selected == null) { +331 echo "ПОМИЛКА! ВКАЗАНО НЕПРАВИЛЬНИЙ НОМЕР ТОВАРУ\n"; +332 continue; +333 } +334 +335 echo "Вибрано: {$selected['name']}\n"; +336 +337 $count = (int)readline("Введіть кількість, штук: "); +338 +339 if ($count > 100) { +340 echo "ПОМИЛКА! Не можна додати більше 100 одиниць + товару в кошик\n"; +341 continue; +342 } +343 +344 if ($count < 0) { +345 echo "ПОМИЛКА! Кількість не може бути від'ємною\n"; +346 continue; +347 } + 13 + +348 +349 if ($count == 0) { +350 echo "ВИДАЛЯЮ З КОШИКА\n"; +351 $this->db->remove_from_cart($id); +352 } else { +353 $this->db->add_to_cart($id, $count); +354 } +355 +356 $cart = $this->db->get_cart_no_price(); +357 if (count($cart) == 0) { +358 echo "КОШИК ПОРОЖНІЙ\n"; +359 } else { +360 echo "\nУ КОШИКУ:\n"; +361 array_unshift($cart, ['name' => "НАЗВА", 'count' => + "КІЛЬКІСТЬ"]); +362 $cart_columns = $this->count_columns($cart); +363 $this->print_lits($cart, $cart_columns); +364 echo "\n"; +365 } +366 } +367 +368 $this->state = State::Menu; +369 } +370 private function checkout(): void +371 { +372 $cart = $this->db->get_cart(); +373 if (count($cart) == 0) { +374 echo "КОШИК ПОРОЖНІЙ\n"; +375 $this->state = State::Menu; +376 return; +377 } else { +378 echo "У КОШИКУ:\n"; +379 array_unshift($cart, ['id' => "№", 'name' => "НАЗВА", + 'price' => "ЦІНА", 'count' => "КІЛЬКІСТЬ", 'total_price' => + "ВАРТІСТЬ"]); +380 $cart_columns = $this->count_columns($cart); +381 $this->print_lits($cart, $cart_columns); +382 } +383 +384 $total_price = array_reduce($cart, function ($carry, $item) { +385 return $carry + (int)$item['total_price']; +386 }, 0); +387 echo "РАЗОМ ДО СПЛАТИ: {$total_price}\n"; +388 +389 $this->state = State::Menu; +390 } +391 private function settings(): void +392 { +393 while (true) { +394 $name = readline("Ваше ім'я: "); +395 if ($name !== "" && preg_match("/[a-zA-Z]+/", $name)) +396 break; +397 } +398 +399 while (true) { +400 $age = readline("Ваш вік: "); + 14 + +401 +402 if (!filter_var($age, FILTER_VALIDATE_INT)) { +403 echo "ПОМИЛКА! Вік користувача потрібно вказати + числом\n\n"; +404 continue; +405 } +406 +407 if ($age < 7 || $age > 150) { +408 echo "ПОМИЛКА! Користувач повинен мати вік від 7 та до + 150 років\n\n"; +409 continue; +410 } +411 +412 break; +413 } +414 +415 echo "\n"; +416 +417 $this->db->update_user($name, $age); +418 +419 $this->state = State::Menu; +420 } +421 +422 /** +423 * @param array $items +424 * @return array +425 */ +426 private function count_columns($items): array +427 { +428 $columns = []; +429 foreach ($items as $item) { +430 foreach ($item as $field => $value) { +431 if (!key_exists($field, $columns)) +432 $columns[$field] = mb_strlen($value); +433 else +434 $columns[$field] = max(mb_strlen($value), + $columns[$field]); +435 } +436 } +437 +438 return $columns; +439 } +440 /** +441 * @param array $element +442 * @param array $columns +443 */ +444 private function pad_row($element, $columns): string +445 { +446 $result = []; +447 foreach ($element as $field => $value) +448 $result[] = mb_str_pad($value, $columns[$field], ' ', + STR_PAD_RIGHT); +449 +450 return implode(" ", $result); +451 } +452 /** + 15 + +453 * @param array $items +454 * @param array $columns +455 */ +456 private function print_lits($items, $columns): void +457 { +458 foreach ($items as $item) +459 echo $this->pad_row($item, $columns) . "\n"; +460 } +461 } +462 +463 try { +464 $app = new App("db.sqlite"); +465 } catch (AppException $e) { +466 echo $e; +467 exit(1); +468 } +469 +470 $app->poll(); + diff --git a/semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.typ b/semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.typ new file mode 100644 index 0000000..5ca61e3 --- /dev/null +++ b/semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.typ @@ -0,0 +1,644 @@ +#import "@local/nure:0.1.0": * + +#show: pz-lb.with( + title: [Продовольчий магазин "Весна"], + subject: "СМП", + doctype: "ЛБ", + worknumber: 2, + edu_program: "ПЗПІ", + university: "ХНУРЕ", + mentors: ( + ( + name: "Сокорчук І. П.", + degree: "Старший викладач кафедри ПІ", + gender: "m", + ), + ), + authors: ( + ( + name: "Ситник Є. С.", + course: 2, + edu: "ПЗПІ", + gender: "m", + group: "23-2", + ), + ), +) + +#v(-spacing) +== Історія змін +#figure( + table( + align: left, + columns: (auto, 1fr, 1fr, 3fr), + table.header([№], [Дата], [Версія звіту], [Опис змін та виправлень]), + [1], [03.03.2025], [0.1], [Створено звіт], + ), +) + +== Мета роботи +Лабораторна робота полягає у розробці консольного застосунку "Продовольчий магазин "Весна"" засобами мови програмування "PHP". + +== Хід роботи +#v(-spacing) +=== Архітектура програми + +Для реалізації консольного застосунку було обрано об'єктно-орієнтований підхід з використанням наступних компонентів: + +==== Класи та їх призначення: + +- *DB* -- клас для роботи з базою даних "SQLite", що забезпечує збереження даних про товари, кошик користувача та налаштування профілю; +- *App* -- основний клас застосунку, що реалізує логіку роботи програми та взаємодію з користувачем; +- *DbException* та *AppException* -- класи винятків для обробки помилок. + +==== Enum State: +Для управління станами програми використовується перелічення "State" з наступними значеннями: +- *Hello* -- привітальний екран; +- *Menu* -- головне меню; +- *Items* -- вибір товарів; +- *Checkout* -- формування рахунку; +- *Settings* -- налаштування профілю; +- *Exit* -- вихід з програми. + +=== Структура бази даних + +Програма використовує базу даних SQLite з трьома таблицями: + +- *settings* -- зберігає інформацію про користувача (ім'я та вік); +- *items* -- містить каталог товарів з їх назвами та цінами; +- *cart* -- зберігає товари, додані до кошика, з їх кількістю. + +=== Основний функціонал + +==== Головне меню: +Програма відображає заголовок магазину та пропонує користувачу чотири основні опції: +- вибір товарів для покупки; +- перегляд підсумкового рахунку; +- налаштування профілю користувача; +- вихід з програми. + +==== Вибір товарів: +При виборі першого пункту меню користувач потрапляє в режим покупки товарів, де: +- відображається список доступних товарів з цінами; +- можна вибрати товар за номером та вказати кількість; +- товари додаються до кошика; +- при введенні кількості "0" товар видаляється з кошика; +- реалізована валідація введених даних. + +==== Підсумковий рахунок: +Другий пункт меню формує детальний рахунок з інформацією про: +- номер, назву та ціну кожного товару; +- кількість товару в кошику; +- загальну вартість кожного товару; +- підсумкову суму до сплати. + +==== Налаштування профілю: +Третій пункт дозволяє користувачу вказати своє ім'я та вік з валідацією: +- ім'я повинно містити хоча б одну літеру; +- вік повинен бути в діапазоні від 7 до 150 років. + +=== Технічні деталі + +Програма написана з дотриманням сучасних стандартів "PHP": +- використання строгої типізації; +- документування методів за допомогою "PHPDoc"; +- дотримання принципів "SOLID"; +- розділення логіки на окремі методи для кращої читабельності. + +== Висновки + +Під час даної лабораторної роботи я вивчив основні принципи роботи з "PHP" для написання консольних програм. Зокрема, було освоєно: + +- створення об'єктно-орієнтованих консольних застосунків на "PHP"; +- роботу з базою даних "SQLite" через "PDO"; +- обробку користувацького вводу та валідацію даних; +- використання перелічень ("enum") для управління станами програми; +- реалізацію системи обробки винятків; +- форматування виводу в консолі для створення зручного інтерфейсу. + +Програмне забезпечення, реалізоване протягом даної лабораторної роботи, повністю відповідає поставленим вимогам та забезпечує всі необхідні функції для роботи з продовольчим магазином. Застосунок має зручний інтерфейс, надійну систему валідації даних та ефективно працює з базою даних для збереження інформації про товари та користувача. + +#set raw(tab-size: 8) +#show raw.where(block: true): code => { + // set text(11pt, font: "Caladea", top-edge: 1em, bottom-edge: 0em) + set text(11pt, top-edge: 1em, bottom-edge: 0em) + set par(leading: 0.17em) + + grid( + columns: (auto, auto), + column-gutter: 1em, + row-gutter: 0.17em, + align: (right, raw.align), + ..for line in code.lines { + ( + text(fill: gray)[#line.number], + { + set text(weight: "semibold") + line.body + }, + ) + }, + ) +} +#show: appendices_style += Відеозапис +Відеозапис презентації результатів лабораторної роботи: https://youtu.be/CMGeL1OdyvI + +*Хронологічний опис відеозапису:* + +00:00 -- Вступ та опис завдання + +00:19 -- Архітектура програми + +00:39 -- Робота з базою даних + +01:43 -- Реалізація станів програми + +02:17 -- Формування списку товарів + +03:13 -- Додавання товарів до кошика + +04:15 -- Робота з кошиком + +05:10 -- Налаштування профілю користувача + +05:38 -- Демонстрація роботи програми + += Програмний код +== Скрипт з реалізацією програми +GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab2/smp-pzpi-23-2-sytnyk-yehor-lab2/smp-pzpi-23-2-sytnyk-yehor-lab2-code + +#v(spacing) + +``` +#!/usr/bin/php + +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 settings ( + name TEXT, + age TEXT + ); + "); + + if ($this->pdo->query("SELECT COUNT(*) FROM settings;")->fetchColumn() == 0) { + $this->pdo->exec("INSERT INTO settings (name, age) VALUES ('user', 0);"); + } + } catch (PDOException $e) { + throw new DbException("Error initialising settings 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 ('Молоко пастеризоване', 12); + INSERT INTO items (name, price) VALUES ('Хліб чорний', 9); + INSERT INTO items (name, price) VALUES ('Сир білий', 21); + INSERT INTO items (name, price) VALUES ('Сметана 20%', 25); + INSERT INTO items (name, price) VALUES ('Кефір 1%', 19); + INSERT INTO items (name, price) VALUES ('Вода газована', 18); + INSERT INTO items (name, price) VALUES ('Печиво \"Весна\"', 14); + "); + } + } 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 item(id) + ); + "); + } catch (PDOException $e) { + throw new DbException("Error initialising cart table.\nCaused by: " . $e->getMessage()); + } + } + + /** + * Updates user information in the database + * + * @param string $name + * @param int $age + * @return void + * @throws DbException If there's a database error. + */ + public function update_user($name, $age): void + { + try { + $stmt = $this->pdo->prepare("UPDATE settings SET name = :name, age = :age;"); + + $stmt->bindParam(':name', $name, PDO::PARAM_STR); + $stmt->bindParam(':age', $age, PDO::PARAM_INT); + + $stmt->execute(); + } catch (PDOException $e) { + throw new DbException("Error updating user info.\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 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 name, 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 inserting init data in the items table.\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, name, price, count, price*count 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 inserting init data in the items table.\nCaused by: " . $e->getMessage()); + } + } + + /** + * Add item to the cart + * + * @param int $id + * @param int $count + * + * @return bool + * @throws DbException If there's a database error. + */ + public function add_to_cart($id, $count): bool + { + try { + $stmt = $this->pdo->prepare( + "INSERT INTO cart + (id, count) + VALUES + (:id, :count) + ON CONFLICT(id) + DO UPDATE SET + count = :count + WHERE id = :id;" + ); + return $stmt->execute(['id' => $id, 'count' => $count]); + } catch (PDOException $e) { + throw new DbException("Error adding item to 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 removimg item from the cart.\nCaused by: " . $e->getMessage()); + } + } +} + +class App +{ + private $db; + private $state; + + private $menu_ops = <<<'END' + 1 Вибрати товари + 2 Отримати підсумковий рахунок + 3 Налаштувати свій профіль + 0 Вийти з програми + END; + private $hello = <<<'END' + ################################ + # ПРОДОВОЛЬЧИЙ МАГАЗИН "ВЕСНА" # + ################################ + END; + + + /** + * @param string $db_path + */ + public function __construct($db_path) + { + try { + $this->db = new DB($db_path); + $this->state = State::Hello; + } catch (DbException $e) { + throw new AppException("Error initializing app.\nCaused by: " . $e); + } + } + + public function poll(): void + { + while ($this->state != State::Exit) { + switch ($this->state) { + case State::Hello: + $this->hello(); + break; + case State::Menu: + $this->menu(); + break; + case State::Items: + $this->items(); + break; + case State::Checkout: + $this->checkout(); + break; + case State::Settins: + $this->settings(); + break; + + default: + break; + } + } + } + + private function menu(): void + { + echo "\n"; + echo "$this->menu_ops\n"; + + $op = readline('Введіть команду: '); + switch ($op) { + case '1': + $this->state = State::Items; + break; + case '2': + $this->state = State::Checkout; + break; + case '3': + $this->state = State::Settins; + break; + case '0': + $this->state = State::Exit; + break; + + default: + echo "ПОМИЛКА! Введіть правильну команду\n"; + break; + } + + echo "\n"; + } + private function hello(): void + { + echo "$this->hello\n"; + $this->state = State::Menu; + } + private function items(): void + { + $items = $this->db->get_items(); + array_unshift($items, ['id' => "№", 'name' => "НАЗВА", 'price' => "ЦІНА"]); + array_push($items, ['id' => " ", 'name' => "-----------", 'price' => ""]); + array_push($items, ['id' => "0", 'name' => "ПОВЕРНУТИСЯ", 'price' => ""]); + $columns = $this->count_columns($items); + + while (true) { + $this->print_lits($items, $columns); + + $id = readline("Виберіть товар: "); + + if ($id == '0') { + break; + } + + echo "\n"; + + $selected = null; + foreach ($items as $item) { + if ($item['id'] === (int)$id) + $selected = $item; + } + + if ($selected == null) { + echo "ПОМИЛКА! ВКАЗАНО НЕПРАВИЛЬНИЙ НОМЕР ТОВАРУ\n"; + continue; + } + + echo "Вибрано: {$selected['name']}\n"; + + $count = (int)readline("Введіть кількість, штук: "); + + if ($count > 100) { + echo "ПОМИЛКА! Не можна додати більше 100 одиниць товару в кошик\n"; + continue; + } + + if ($count < 0) { + echo "ПОМИЛКА! Кількість не може бути від'ємною\n"; + continue; + } + + if ($count == 0) { + echo "ВИДАЛЯЮ З КОШИКА\n"; + $this->db->remove_from_cart($id); + } else { + $this->db->add_to_cart($id, $count); + } + + $cart = $this->db->get_cart_no_price(); + if (count($cart) == 0) { + echo "КОШИК ПОРОЖНІЙ\n"; + } else { + echo "\nУ КОШИКУ:\n"; + array_unshift($cart, ['name' => "НАЗВА", 'count' => "КІЛЬКІСТЬ"]); + $cart_columns = $this->count_columns($cart); + $this->print_lits($cart, $cart_columns); + echo "\n"; + } + } + + $this->state = State::Menu; + } + private function checkout(): void + { + $cart = $this->db->get_cart(); + if (count($cart) == 0) { + echo "КОШИК ПОРОЖНІЙ\n"; + $this->state = State::Menu; + return; + } else { + echo "У КОШИКУ:\n"; + array_unshift($cart, ['id' => "№", 'name' => "НАЗВА", 'price' => "ЦІНА", 'count' => "КІЛЬКІСТЬ", 'total_price' => "ВАРТІСТЬ"]); + $cart_columns = $this->count_columns($cart); + $this->print_lits($cart, $cart_columns); + } + + $total_price = array_reduce($cart, function ($carry, $item) { + return $carry + (int)$item['total_price']; + }, 0); + echo "РАЗОМ ДО СПЛАТИ: {$total_price}\n"; + + $this->state = State::Menu; + } + private function settings(): void + { + while (true) { + $name = readline("Ваше ім'я: "); + if ($name !== "" && preg_match("/[a-zA-Z]+/", $name)) + break; + } + + while (true) { + $age = readline("Ваш вік: "); + + if (!filter_var($age, FILTER_VALIDATE_INT)) { + echo "ПОМИЛКА! Вік користувача потрібно вказати числом\n\n"; + continue; + } + + if ($age < 7 || $age > 150) { + echo "ПОМИЛКА! Користувач повинен мати вік від 7 та до 150 років\n\n"; + continue; + } + + break; + } + + echo "\n"; + + $this->db->update_user($name, $age); + + $this->state = State::Menu; + } + + /** + * @param array $items + * @return array + */ + private function count_columns($items): array + { + $columns = []; + foreach ($items as $item) { + foreach ($item as $field => $value) { + if (!key_exists($field, $columns)) + $columns[$field] = mb_strlen($value); + else + $columns[$field] = max(mb_strlen($value), $columns[$field]); + } + } + + return $columns; + } + /** + * @param array $element + * @param array $columns + */ + private function pad_row($element, $columns): string + { + $result = []; + foreach ($element as $field => $value) + $result[] = mb_str_pad($value, $columns[$field], ' ', STR_PAD_RIGHT); + + return implode(" ", $result); + } + /** + * @param array $items + * @param array $columns + */ + private function print_lits($items, $columns): void + { + foreach ($items as $item) + echo $this->pad_row($item, $columns) . "\n"; + } +} + +try { + $app = new App("db.sqlite"); +} catch (AppException $e) { + echo $e; + exit(1); +} + +$app->poll(); +``` diff --git a/semester-4/СмП/lb-3/README.md b/semester-4/СмП/lb-3/README.md new file mode 100644 index 0000000..a182819 --- /dev/null +++ b/semester-4/СмП/lb-3/README.md @@ -0,0 +1,4 @@ +> [!NOTE] +> Викладач: Сокорчук І. П. +> +> Оцінка: 80 diff --git a/semester-4/СмП/lb-3/src/pzpi-23-2-sytnyk-yehor-task3.php b/semester-4/СмП/lb-3/src/pzpi-23-2-sytnyk-yehor-task3.php new file mode 100755 index 0000000..5c11240 --- /dev/null +++ b/semester-4/СмП/lb-3/src/pzpi-23-2-sytnyk-yehor-task3.php @@ -0,0 +1,469 @@ +#!/usr/bin/php + +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 settings ( + name TEXT, + age TEXT + ); + "); + + if ($this->pdo->query("SELECT COUNT(*) FROM settings;")->fetchColumn() == 0) { + $this->pdo->exec("INSERT INTO settings (name, age) VALUES ('user', 0);"); + } + } catch (PDOException $e) { + throw new DbException("Error initialising settings 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 ('Молоко пастеризоване', 12); + INSERT INTO items (name, price) VALUES ('Хліб чорний', 9); + INSERT INTO items (name, price) VALUES ('Сир білий', 21); + INSERT INTO items (name, price) VALUES ('Сметана 20%', 25); + INSERT INTO items (name, price) VALUES ('Кефір 1%', 19); + INSERT INTO items (name, price) VALUES ('Вода газована', 18); + INSERT INTO items (name, price) VALUES ('Печиво \"Весна\"', 14); + "); + } + } 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 item(id) + ); + "); + } catch (PDOException $e) { + throw new DbException("Error initialising cart table.\nCaused by: " . $e->getMessage()); + } + } + + /** + * Updates user information in the database + * + * @param string $name + * @param int $age + * @return void + * @throws DbException If there's a database error. + */ + public function update_user($name, $age): void + { + try { + $stmt = $this->pdo->prepare("UPDATE settings SET name = :name, age = :age;"); + + $stmt->bindParam(':name', $name, PDO::PARAM_STR); + $stmt->bindParam(':age', $age, PDO::PARAM_INT); + + $stmt->execute(); + } catch (PDOException $e) { + throw new DbException("Error updating user info.\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 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 name, 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 inserting init data in the items table.\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, name, price, count, price*count 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 inserting init data in the items table.\nCaused by: " . $e->getMessage()); + } + } + + /** + * Add item to the cart + * + * @param int $id + * @param int $count + * + * @return bool + * @throws DbException If there's a database error. + */ + public function add_to_cart($id, $count): bool + { + try { + $stmt = $this->pdo->prepare( + "INSERT INTO cart + (id, count) + VALUES + (:id, :count) + ON CONFLICT(id) + DO UPDATE SET + count = :count + WHERE id = :id;" + ); + return $stmt->execute(['id' => $id, 'count' => $count]); + } catch (PDOException $e) { + throw new DbException("Error adding item to 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 removimg item from the cart.\nCaused by: " . $e->getMessage()); + } + } +} + +class App +{ + private $db; + private $state; + + private $menu_ops = <<<'END' + 1 Вибрати товари + 2 Отримати підсумковий рахунок + 3 Налаштувати свій профіль + 0 Вийти з програми + END; + private $hello = <<<'END' + ################################ + # ПРОДОВОЛЬЧИЙ МАГАЗИН "ВЕСНА" # + ################################ + END; + + + /** + * @param string $db_path + */ + public function __construct($db_path) + { + try { + $this->db = new DB($db_path); + $this->state = State::Hello; + } catch (DbException $e) { + throw new AppException("Error initializing app.\nCaused by: " . $e); + } + } + + public function poll(): void + { + while ($this->state != State::Exit) { + switch ($this->state) { + case State::Hello: + $this->hello(); + break; + case State::Menu: + $this->menu(); + break; + case State::Items: + $this->items(); + break; + case State::Checkout: + $this->checkout(); + break; + case State::Settins: + $this->settings(); + break; + + default: + break; + } + } + } + + private function menu(): void + { + echo "\n"; + echo "$this->menu_ops\n"; + + $op = readline('Введіть команду: '); + switch ($op) { + case '1': + $this->state = State::Items; + break; + case '2': + $this->state = State::Checkout; + break; + case '3': + $this->state = State::Settins; + break; + case '0': + $this->state = State::Exit; + break; + + default: + echo "ПОМИЛКА! Введіть правильну команду\n"; + break; + } + + echo "\n"; + } + private function hello(): void + { + echo "$this->hello\n"; + $this->state = State::Menu; + } + private function items(): void + { + $items = $this->db->get_items(); + array_unshift($items, ['id' => "№", 'name' => "НАЗВА", 'price' => "ЦІНА"]); + array_push($items, ['id' => " ", 'name' => "-----------", 'price' => ""]); + array_push($items, ['id' => "0", 'name' => "ПОВЕРНУТИСЯ", 'price' => ""]); + $columns = $this->count_columns($items); + + while (true) { + $this->print_lits($items, $columns); + + $id = readline("Виберіть товар: "); + + if ($id == '0') { + break; + } + + echo "\n"; + + $selected = null; + foreach ($items as $item) { + if ($item['id'] === (int)$id) $selected = $item; + } + + if ($selected == null) { + echo "ПОМИЛКА! ВКАЗАНО НЕПРАВИЛЬНИЙ НОМЕР ТОВАРУ\n"; + continue; + } + + echo "Вибрано: {$selected['name']}\n"; + + $count = (int)readline("Введіть кількість, штук: "); + + if ($count > 100) { + echo "ПОМИЛКА! Не можна додати більше 100 одиниць товару в кошик\n"; + continue; + } + + if ($count < 0) { + echo "ПОМИЛКА! Кількість не може бути від'ємною\n"; + continue; + } + + if ($count == 0) { + echo "ВИДАЛЯЮ З КОШИКА\n"; + $this->db->remove_from_cart($id); + } else { + $this->db->add_to_cart($id, $count); + } + + $cart = $this->db->get_cart_no_price(); + if (count($cart) == 0) { + echo "КОШИК ПОРОЖНІЙ\n"; + } else { + echo "\nУ КОШИКУ:\n"; + array_unshift($cart, ['name' => "НАЗВА", 'count' => "КІЛЬКІСТЬ"]); + $cart_columns = $this->count_columns($cart); + $this->print_lits($cart, $cart_columns); + echo "\n"; + } + } + + $this->state = State::Menu; + } + private function checkout(): void + { + $cart = $this->db->get_cart(); + if (count($cart) == 0) { + echo "КОШИК ПОРОЖНІЙ\n"; + $this->state = State::Menu; + return; + } else { + echo "У КОШИКУ:\n"; + array_unshift($cart, ['id' => "№", 'name' => "НАЗВА", 'price' => "ЦІНА", 'count' => "КІЛЬКІСТЬ", 'total_price' => "ВАРТІСТЬ"]); + $cart_columns = $this->count_columns($cart); + $this->print_lits($cart, $cart_columns); + } + + $total_price = array_reduce($cart, function ($carry, $item) { + return $carry + (int)$item['total_price']; + }, 0); + echo "РАЗОМ ДО СПЛАТИ: {$total_price}\n"; + + $this->state = State::Menu; + } + private function settings(): void + { + while (true) { + $name = readline("Ваше ім'я: "); + if ($name !== "" && preg_match("/[a-zA-Z]+/", $name)) + break; + } + + while (true) { + $age = readline("Ваш вік: "); + + if (!filter_var($age, FILTER_VALIDATE_INT)) { + echo "ПОМИЛКА! Вік користувача потрібно вказати числом\n\n"; + continue; + } + + if ($age < 7 || $age > 150) { + echo "ПОМИЛКА! Користувач повинен мати вік від 7 та до 150 років\n\n"; + continue; + } + + break; + } + + echo "\n"; + + $this->db->update_user($name, $age); + + $this->state = State::Menu; + } + + /** + * @param array $items + * @return array + */ + private function count_columns($items): array + { + $columns = []; + foreach ($items as $item) { + foreach ($item as $field => $value) { + if (!key_exists($field, $columns)) + $columns[$field] = mb_strlen($value); + else + $columns[$field] = max(mb_strlen($value), $columns[$field]); + } + } + + return $columns; + } + /** + * @param array $element + * @param array $columns + */ + private function pad_row($element, $columns): string + { + $result = []; + foreach ($element as $field => $value) + $result[] = mb_str_pad($value, $columns[$field], ' ', STR_PAD_RIGHT); + + return implode(" ", $result); + } + /** + * @param array $items + * @param array $columns + */ + private function print_lits($items, $columns): void + { + foreach ($items as $item) + echo $this->pad_row($item, $columns) . "\n"; + } +} + +try { + $app = new App("db.sqlite"); +} catch (AppException $e) { + echo $e; + exit(1); +} + +$app->poll(); diff --git a/semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.pdf b/semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.pdf new file mode 100644 index 0000000..9372cf4 Binary files /dev/null and b/semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.pdf differ diff --git a/semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.txt b/semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.txt new file mode 100644 index 0000000..798859b --- /dev/null +++ b/semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.txt @@ -0,0 +1,785 @@ + МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ + ХАРКІВСЬКИЙ НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ РАДІОЕЛЕКТРОНІКИ + + + + Кафедра Програмної інженерії + + + + + Звіт + з лабораторної роботи №3 + з дисципліни: «Скриптові мови програмування» + з теми: « Створення WEB-застосунків за допомогою PHP « + + + + +Виконав: Перевірив: +ст. гр. ПЗПІ-23-2 Старший викладач кафедри ПІ +Ситник Є. С. Сокорчук І. П. + + + + + Харків – 2025 + 2 + 3 СТВОРЕННЯ WEB-ЗАСТОСУНКІВ ЗА ДОПОМОГОЮ PHP + 3.1 Історія змін + +№ Дата Версія звіту Опис змін та виправлень +1 03.06.2025 0.1 Створено звіт + + 3.2 Мета роботи + + Лабораторна робота полягає у розробці веб-застосунку «Продовольчий +магазин Весна» засобами мови програмування PHP з використанням HTML, CSS та +JavaScript для створення інтерактивного інтерфейсу користувача. + + 3.3 Хід роботи + 3.3.1 Архітектура веб-застосунку + + Для реалізації веб-застосунку було обрано модульний підхід з розділенням +логіки на окремі файли: + + 3.3.1.1 Структура файлів: + + – index.php – головна сторінка застосунку з логотипом магазину; + – items.php – сторінка каталогу товарів з можливістю додавання до кошика; + – cart.php – сторінка кошика з переглядом обраних товарів та управлінням; + – handle_cart.php – обробник AJAX-запитів для роботи з кошиком; + – DB.php – клас для роботи з базою даних SQLite; + – style.css – стилі для оформлення веб-сторінок. + + 3.3.1.2 Основні компоненти: + + – Клас DB – забезпечує взаємодію з базою даних SQLite, включаючи + методи для роботи з товарами, кошиком та налаштуваннями користувача; + – Клас DbException – спеціалізований виняток для обробки помилок бази + даних; + – Сесійна система – для збереження стану користувача між сторінками. + 3 + 3.3.2 Структура бази даних + + Веб-застосунок використовує базу даних SQLite з трьома основними +таблицями: + + 3.3.2.1 Таблиця settings: + + – name (TEXT) – ім’я користувача; + – age (TEXT) – вік користувача. + + 3.3.2.2 Таблиця items: + + – id (INTEGER PRIMARY KEY) – унікальний ідентифікатор товару; + – name (TEXT NOT NULL) – назва товару; + – price (REAL NOT NULL) – ціна товару. + + 3.3.2.3 Таблиця cart: + + – id (INTEGER NOT NULL UNIQUE) – ідентифікатор товару; + – count (INTEGER NOT NULL) – кількість товару в кошику; + – FOREIGN KEY – зв’язок з таблицею items. + + 3.3.3 Функціональні можливості + + 3.3.3.1 Головна сторінка (index.php): + + – Відображення логотипу магазину; + – Навігаційне меню з кількістю товарів у кошику; + – Привітання користувача за ім’ям; + – Підвал з інформацією про магазин. + + 3.3.3.2 Каталог товарів (items.php): + + – Відображення списку доступних товарів з цінами; + – Форма для вибору кількості товару; + – Можливість додавання товарів до кошика; + – Валідація кількості товару (від 0 до 100 одиниць); + 4 +– Автоматичне оновлення лічильника кошика. + +3.3.3.3 Кошик покупок (cart.php): + +– Перегляд обраних товарів з детальною інформацією; +– Відображення кількості, ціни за одиницю та загальної вартості; +– Можливість видалення окремих товарів з кошика; +– Функція повного очищення кошика; +– Підрахунок загальної суми покупки; +– Кнопка «Сплатити» для завершення покупки. + +3.3.3.4 Обробка запитів (handle_cart.php): + +– Обробка POST-запитів для додавання товарів до кошика; +– Обробка DELETE-запитів для видалення товарів; +– Підтримка AJAX для асинхронного оновлення кошика; +– Валідація вхідних даних та обробка помилок. + +3.3.4 Технічні особливості + +3.3.4.1 Безпека: + +– Використання підготовлених запитів (prepared statements) для запобігання + SQL-ін’єкціям; +– Функція «htmlspecialchars()» для запобігання XSS-атакам; +– Валідація та фільтрація користувацьких даних через «filter_input()»; +– Обробка винятків для коректної роботи з помилками. + +3.3.4.2 Користувацький інтерфейс: + +– Адаптивний дизайн з використанням CSS; +– Інтуїтивна навігація між сторінками; +– Зручні форми для взаємодії з користувачем. + 5 + 3.3.4.3 База даних: + + – Автоматичне створення таблиць при першому запуску; + – Заповнення початковими даними; + + 3.3.5 Методи класу DB + + Клас DB містить наступні основні методи: + – retrieve_user() – отримання інформації про користувача; + – update_user() – оновлення профілю користувача; + – get_items() – отримання списку всіх товарів; + – get_item_by_id() – отримання конкретного товару за ID; + – get_cart() – отримання товарів у кошику з детальною інформацією; + – get_cart_count() – підрахунок загальної кількості товарів у кошику; + – get_cart_total() – підрахунок загальної вартості кошика; + – add_to_cart() – додавання товару до кошика або оновлення кількості; + – remove_from_cart() – видалення товару з кошика; + – empty_cart() – повне очищення кошика. + + 3.4 Висновки + + Під час виконання даної лабораторної роботи було успішно розроблено веб- +застосунок інтернет-магазину з використанням сучасних веб-технологій. Зокрема, +було освоєно: + – створення багатосторінкових веб-застосунків на PHP; + – роботу з базами даних SQLite через PDO з дотриманням принципів + безпеки; + – реалізацію сесійної системи для збереження стану користувача; + – створення інтерактивного користувацького інтерфейсу з використанням + HTML та CSS; + – валідацію та фільтрацію користувацьких даних. + Застосунок демонструє практичне застосування технологій веб-розробки для +створення реальних рішень. + 6 + ДОДАТОК А + Відеозапис + + Відеозапис презентації результатів лабораторної роботи: https://youtu.be/Gils7poMkgk + Хронологічний опис відеозапису: + 00:00 – Вступ та загальний опис роботи + 00:30 – Структура веб-застосунку + 01:14 – Робота з базою даних та сесіями + 01:43 – Обробка запитів користувача + 03:56 – Демонстрація роботи веб-застосунку + 7 + ДОДАТОК Б + Програмний код + + Б.1 Головна сторінка (index.php) + + GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/index.php + 1 retrieve_user(); + 9 } +10 +11 $cart_count = $db->get_cart_count(); +12 ?> +13 +14 +15 +16 +17 +18 Головна сторінка +19 +20 +21 +22
+23

Продовольчий магазин "Весна"

+24

Добрий день

+25 +32
+33 +34
+35 logo +36
+37 +38 +48 +49 + + + Б.2 Каталог товарів (items.php) + + GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/items.php + 1 retrieve_user(); + 9 } +10 +11 $items = $db->get_items(); +12 $cart_count = $db->get_cart_count(); +13 ?> +14 +15 +16 +17 +18 +19 Сторінка товарів +20 +21 +22 +23
+24

Продовольчий магазин "Весна"

+25

Добрий день

+26 +33
+34 +35
+36

Доступні товари

+37
+38 +39
+ 9 + +40

+41

Ціна: грн

+42 +43
+44 +45 +46 +47 +48
+49
+50 +51
+52
+53 +54 +64 +65 + + + Б.3 Кошик покупок (cart.php) + + GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/cart.php + 1 retrieve_user(); + 9 } +10 +11 $cart_items = $db->get_cart(); +12 $cart_total = $db->get_cart_total(); +13 $cart_count = $db->get_cart_count(); +14 ?> + 10 + +15 +16 +17 +18 +19 +20 Кошик +21 +22 +23 +24
+25

Продовольчий магазин "Весна"

+26

Добрий день

+27 +34
+35 +36
+37 +38
+39

Ваш кошик порожній Перейти до + покупок

+40
+41 +42
+43

Ваш кошик

+44

+45 Загальна сума: грн +46

+47 +48 +49 +50 +51
+52 +53
+54 +55
+56

+57 +58
+59 шт. +60

+61 + 11 + +62 Ціна за одиницю: грн +63
+64 Загальна ціна: грн +65 +66

+67 +68 +77
+78 +79
+80 +81
+82 +83 +93 +94 + + + Б.4 Обробник кошика (handle_cart.php) + + GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/handle_cart.php + 1 add_to_cart($product_id, $quantity); +16 } catch (DbException $e) { +17 error_log("Cart handling error: " . $e->getMessage()); +18 } +19 } +20 header('Location: items.php'); +21 break; +22 +23 case 'DELETE': +24 $product_id = filter_input(INPUT_GET, 'product_id', + FILTER_VALIDATE_INT); +25 +26 if ($product_id === null) { +27 try { +28 $db->empty_cart(); +29 } catch (DbException $e) { +30 error_log("Cart handling error: " . $e->getMessage()); +31 } +32 } else if ($product_id !== false) { +33 try { +34 $db->remove_from_cart($product_id); +35 } catch (DbException $e) { +36 error_log("Cart handling error: " . $e->getMessage()); +37 } +38 } +39 +40 header('Location: cart.php'); +41 break; +42 +43 default: +44 break; +45 } +46 +47 exit(); + + + Б.5 Клас для роботи з базою даних (DB.php) + + GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/DB.php + 1 pdo = new PDO("sqlite:" . $db_path); +19 $this->pdo->setAttribute(PDO::ATTR_ERRMODE, + PDO::ERRMODE_EXCEPTION); +20 } catch (PDOException $e) { +21 throw new DbException("Connection to DB failed.\nCaused + by: " . $e->getMessage()); +22 } +23 +24 // Ініціалізація таблиць та початкових даних +25 try { +26 $this->pdo->exec(" +27 CREATE TABLE IF NOT EXISTS settings ( +28 name TEXT, +29 age TEXT +30 ); +31 "); +32 if ($this->pdo->query("SELECT COUNT(*) FROM settings;")- + >fetchColumn() == 0) { +33 $this->pdo->exec("INSERT INTO settings (name, age) + VALUES ('user', 0);"); +34 } +35 } catch (PDOException $e) { +36 throw new DbException("Error initialising settings table. + \nCaused by: " . $e->getMessage()); +37 } +38 +39 try { +40 $this->pdo->exec(" +41 CREATE TABLE IF NOT EXISTS items ( +42 id INTEGER PRIMARY KEY AUTOINCREMENT, +43 name TEXT NOT NULL, +44 price REAL NOT NULL +45 ); +46 "); +47 if ($this->pdo->query("SELECT COUNT(id) FROM items;")- + >fetchColumn() == 0) { +48 $this->pdo->exec(" +49 INSERT INTO items (name, price) VALUES ('Молоко + пастеризоване', 32.50); +50 INSERT INTO items (name, price) VALUES ('Хліб + чорний', 18.00); +51 INSERT INTO items (name, price) VALUES ('Сир + білий', 85.00); +52 INSERT INTO items (name, price) VALUES ('Сметана + 20%', 45.80); + 14 + +53 INSERT INTO items (name, price) VALUES ('Кефір + 1%', 28.50); +54 INSERT INTO items (name, price) VALUES ('Вода + газована', 25.00); +55 INSERT INTO items (name, price) VALUES ('Печиво + \"Весна\"', 42.30); +56 INSERT INTO items (name, price) VALUES ('Масло + вершкове', 125.00); +57 INSERT INTO items (name, price) VALUES ('Йогурт + натуральний', 38.90); +58 INSERT INTO items (name, price) VALUES ('Сік + апельсиновий', 55.00); +59 "); +60 } +61 } catch (PDOException $e) { +62 throw new DbException("Error initialising items table. + \nCaused by: " . $e->getMessage()); +63 } +64 +65 try { +66 $this->pdo->exec(" +67 CREATE TABLE IF NOT EXISTS cart ( +68 id INTEGER NOT NULL UNIQUE, +69 count INTEGER NOT NULL, +70 FOREIGN KEY(id) REFERENCES items(id) ON DELETE + CASCADE +71 ); +72 "); +73 } catch (PDOException $e) { +74 throw new DbException("Error initialising cart table. + \nCaused by: " . $e->getMessage()); +75 } +76 } +77 +78 /** +79 * Retrieve user information from the database +80 * +81 * @return array +82 * @throws DbException If there's a database error. +83 */ +84 public function retrieve_user(): array +85 { +86 try { +87 $stmt = $this->pdo->query("SELECT name, age FROM + settings;"); +88 return $stmt->fetch(PDO::FETCH_ASSOC); +89 } catch (PDOException $e) { +90 throw new DbException("Error retrieving user info.\nCaused + by: " . $e->getMessage()); +91 } +92 } +93 +94 /** +95 * Fetches all items from the database. +96 * +97 * @return array[] + 15 + + 98 * @throws DbException If there's a database error. + 99 */ +100 public function get_items(): array +101 { +102 try { +103 $stmt = $this->pdo->query("SELECT id, name, price FROM + items ORDER BY id;"); +104 return $stmt->fetchAll(PDO::FETCH_ASSOC); +105 } catch (PDOException $e) { +106 throw new DbException("Error retrieving data from the + items table.\nCaused by: " . $e->getMessage()); +107 } +108 } +109 +110 /** +111 * Fetches all items in the cart from the database. +112 * +113 * @return array[] +114 * @throws DbException If there's a database error. +115 */ +116 public function get_cart(): array +117 { +118 try { +119 $stmt = $this->pdo->query( +120 "SELECT +121 cart.id, +122 items.name, +123 items.price, +124 cart.count, +125 ROUND(items.price * cart.count, 2) as total_price +126 FROM cart +127 INNER JOIN items ON cart.id = items.id +128 ORDER BY cart.id;" +129 ); +130 return $stmt->fetchAll(PDO::FETCH_ASSOC); +131 } catch (PDOException $e) { +132 throw new DbException("Error retrieving cart items. + \nCaused by: " . $e->getMessage()); +133 } +134 } +135 +136 /** +137 * Get total items count in cart +138 * +139 * @return int +140 * @throws DbException If there's a database error. +141 */ +142 public function get_cart_count(): int +143 { +144 try { +145 $stmt = $this->pdo->query("SELECT COALESCE(SUM(count), 0) + FROM cart;"); +146 return (int)$stmt->fetchColumn(); +147 } catch (PDOException $e) { +148 throw new DbException("Error getting cart count.\nCaused + by: " . $e->getMessage()); + 16 + +149 } +150 } +151 +152 /** +153 * Get total price of all items in cart +154 * +155 * @return float +156 * @throws DbException If there's a database error. +157 */ +158 public function get_cart_total(): float +159 { +160 try { +161 $stmt = $this->pdo->query( +162 "SELECT COALESCE(SUM(items.price * cart.count), 0.0) +163 FROM cart +164 INNER JOIN items ON cart.id = items.id;" +165 ); +166 return (float)$stmt->fetchColumn(); +167 } catch (PDOException $e) { +168 throw new DbException("Error calculating cart total. + \nCaused by: " . $e->getMessage()); +169 } +170 } +171 +172 /** +173 * Add item to the cart or update its quantity. +174 * +175 * @param int $id +176 * @param int $count +177 * +178 * @return bool +179 * @throws DbException If there's a database error or item doesn't + exist. +180 */ +181 public function add_to_cart($id, $count): bool +182 { +183 try { +184 // Check if the item exists +185 $item = $this->get_item_by_id($id); +186 if (!$item) { +187 throw new DbException("Item with ID $id does not + exist."); +188 } +189 +190 // If count is 0 or less, remove the item from the cart +191 if ($count <= 0) { +192 return $this->remove_from_cart($id); +193 } +194 +195 // Insert or update the cart item +196 $stmt = $this->pdo->prepare( +197 "INSERT INTO cart (id, count) +198 VALUES (:id, :count) +199 ON CONFLICT(id) DO UPDATE SET +200 count = excluded.count;" +201 ); + 17 + +202 return $stmt->execute(['id' => $id, 'count' => $count]); +203 } catch (PDOException $e) { +204 throw new DbException("Error adding/updating item in the + cart.\nCaused by: " . $e->getMessage()); +205 } +206 } +207 +208 /** +209 * Empty the cart +210 * +211 * @return bool +212 * @throws DbException If there's a database error. +213 */ +214 public function empty_cart(): bool +215 { +216 try { +217 $stmt = $this->pdo->prepare("DELETE FROM cart"); +218 return $stmt->execute(); +219 } catch (PDOException $e) { +220 throw new DbException("Error removing item from the cart. + \nCaused by: " . $e->getMessage()); +221 } +222 } +223 +224 /** +225 * Remove an item from the cart +226 * +227 * @param int $id +228 * +229 * @return bool +230 * @throws DbException If there's a database error. +231 */ +232 public function remove_from_cart($id): bool +233 { +234 try { +235 $stmt = $this->pdo->prepare("DELETE FROM cart WHERE id + = :id"); +236 return $stmt->execute(['id' => $id]); +237 } catch (PDOException $e) { +238 throw new DbException("Error removing item from the cart. + \nCaused by: " . $e->getMessage()); +239 } +240 } +241 } + diff --git a/semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.typ b/semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.typ new file mode 100644 index 0000000..f21b853 --- /dev/null +++ b/semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.typ @@ -0,0 +1,712 @@ +#import "@local/nure:0.1.0": * + +#show: pz-lb.with( + title: [Створення WEB-застосунків за допомогою PHP], + subject: "СМП", + doctype: "ЛБ", + worknumber: 3, + edu_program: "ПЗПІ", + university: "ХНУРЕ", + mentors: ( + ( + name: "Сокорчук І. П.", + degree: "Старший викладач кафедри ПІ", + gender: "m", + ), + ), + authors: ( + ( + name: "Ситник Є. С.", + course: 2, + edu: "ПЗПІ", + gender: "m", + group: "23-2", + ), + ), +) + +#v(-spacing) +== Історія змін +#figure( + table( + align: left, + columns: (auto, 1fr, 1fr, 3fr), + table.header([№], [Дата], [Версія звіту], [Опис змін та виправлень]), + [1], [03.06.2025], [0.1], [Створено звіт], + ), +) + +== Мета роботи +Лабораторна робота полягає у розробці веб-застосунку "Продовольчий магазин Весна" засобами мови програмування PHP з використанням HTML, CSS та JavaScript для створення інтерактивного інтерфейсу користувача. + +== Хід роботи +#v(-spacing) +=== Архітектура веб-застосунку + +Для реалізації веб-застосунку було обрано модульний підхід з розділенням логіки на окремі файли: + +==== Структура файлів: + +- *index.php* -- головна сторінка застосунку з логотипом магазину; +- *items.php* -- сторінка каталогу товарів з можливістю додавання до кошика; +- *cart.php* -- сторінка кошика з переглядом обраних товарів та управлінням; +- *handle_cart.php* -- обробник AJAX-запитів для роботи з кошиком; +- *DB.php* -- клас для роботи з базою даних SQLite; +- *style.css* -- стилі для оформлення веб-сторінок. + +==== Основні компоненти: + +- *Клас DB* -- забезпечує взаємодію з базою даних SQLite, включаючи методи для роботи з товарами, кошиком та налаштуваннями користувача; +- *Клас DbException* -- спеціалізований виняток для обробки помилок бази даних; +- *Сесійна система* -- для збереження стану користувача між сторінками. + +=== Структура бази даних + +Веб-застосунок використовує базу даних SQLite з трьома основними таблицями: + +==== Таблиця settings: +- *name* (TEXT) -- ім'я користувача; +- *age* (TEXT) -- вік користувача. + +==== Таблиця items: +- *id* (INTEGER PRIMARY KEY) -- унікальний ідентифікатор товару; +- *name* (TEXT NOT NULL) -- назва товару; +- *price* (REAL NOT NULL) -- ціна товару. + +==== Таблиця cart: +- *id* (INTEGER NOT NULL UNIQUE) -- ідентифікатор товару; +- *count* (INTEGER NOT NULL) -- кількість товару в кошику; +- *FOREIGN KEY* -- зв'язок з таблицею items. + +=== Функціональні можливості + +==== Головна сторінка (index.php): +- Відображення логотипу магазину; +- Навігаційне меню з кількістю товарів у кошику; +- Привітання користувача за ім'ям; +- Підвал з інформацією про магазин. + +==== Каталог товарів (items.php): +- Відображення списку доступних товарів з цінами; +- Форма для вибору кількості товару; +- Можливість додавання товарів до кошика; +- Валідація кількості товару (від 0 до 100 одиниць); +- Автоматичне оновлення лічильника кошика. + +==== Кошик покупок (cart.php): +- Перегляд обраних товарів з детальною інформацією; +- Відображення кількості, ціни за одиницю та загальної вартості; +- Можливість видалення окремих товарів з кошика; +- Функція повного очищення кошика; +- Підрахунок загальної суми покупки; +- Кнопка "Сплатити" для завершення покупки. + +==== Обробка запитів (handle_cart.php): +- Обробка POST-запитів для додавання товарів до кошика; +- Обробка DELETE-запитів для видалення товарів; +- Підтримка AJAX для асинхронного оновлення кошика; +- Валідація вхідних даних та обробка помилок. + +=== Технічні особливості + +==== Безпека: +- Використання підготовлених запитів (prepared statements) для запобігання SQL-ін'єкціям; +- Функція "htmlspecialchars()" для запобігання XSS-атакам; +- Валідація та фільтрація користувацьких даних через "filter_input()"; +- Обробка винятків для коректної роботи з помилками. + +==== Користувацький інтерфейс: +- Адаптивний дизайн з використанням CSS; +- Інтуїтивна навігація між сторінками; +- Зручні форми для взаємодії з користувачем. + +==== База даних: +- Автоматичне створення таблиць при першому запуску; +- Заповнення початковими даними; + +=== Методи класу DB + +Клас DB містить наступні основні методи: + +- *retrieve_user()* -- отримання інформації про користувача; +- *update_user()* -- оновлення профілю користувача; +- *get_items()* -- отримання списку всіх товарів; +- *get_item_by_id()* -- отримання конкретного товару за ID; +- *get_cart()* -- отримання товарів у кошику з детальною інформацією; +- *get_cart_count()* -- підрахунок загальної кількості товарів у кошику; +- *get_cart_total()* -- підрахунок загальної вартості кошика; +- *add_to_cart()* -- додавання товару до кошика або оновлення кількості; +- *remove_from_cart()* -- видалення товару з кошика; +- *empty_cart()* -- повне очищення кошика. + +== Висновки + +Під час виконання даної лабораторної роботи було успішно розроблено веб-застосунок інтернет-магазину з використанням сучасних веб-технологій. Зокрема, було освоєно: + +- створення багатосторінкових веб-застосунків на PHP; +- роботу з базами даних SQLite через PDO з дотриманням принципів безпеки; +- реалізацію сесійної системи для збереження стану користувача; +- створення інтерактивного користувацького інтерфейсу з використанням HTML та CSS; +- валідацію та фільтрацію користувацьких даних. + +Застосунок демонструє практичне застосування технологій веб-розробки для створення реальних рішень. + +#set raw(tab-size: 8) +#show raw.where(block: true): code => { + set text(11pt, top-edge: 1em, bottom-edge: 0em) + set par(leading: 0.17em) + + grid( + columns: (auto, auto), + column-gutter: 1em, + row-gutter: 0.17em, + align: (right, raw.align), + ..for line in code.lines { + ( + text(fill: gray)[#line.number], + { + set text(weight: "semibold") + line.body + }, + ) + }, + ) +} +#show: appendices_style += Відеозапис +Відеозапис презентації результатів лабораторної роботи: https://youtu.be/Gils7poMkgk + +*Хронологічний опис відеозапису:* + +00:00 -- Вступ та загальний опис роботи + +00:30 -- Структура веб-застосунку + +01:14 -- Робота з базою даних та сесіями + +01:43 -- Обробка запитів користувача + +03:56 -- Демонстрація роботи веб-застосунку + += Програмний код + +== Головна сторінка (index.php) +GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/index.php +``` +retrieve_user(); +} + +$cart_count = $db->get_cart_count(); +?> + + + + + + Головна сторінка + + + +
+

Продовольчий магазин "Весна"

+

Добрий день

+ +
+ +
+ logo +
+ + + + +``` + +== Каталог товарів (items.php) +GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/items.php +``` +retrieve_user(); +} + +$items = $db->get_items(); +$cart_count = $db->get_cart_count(); +?> + + + + + + Сторінка товарів + + + +
+

Продовольчий магазин "Весна"

+

Добрий день

+ +
+ +
+

Доступні товари

+
+ +
+

+

Ціна: грн

+ +
+ + + + +
+
+ +
+
+ + + + +``` + +== Кошик покупок (cart.php) +GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/cart.php +``` +retrieve_user(); +} + +$cart_items = $db->get_cart(); +$cart_total = $db->get_cart_total(); +$cart_count = $db->get_cart_count(); +?> + + + + + + Кошик + + + +
+

Продовольчий магазин "Весна"

+

Добрий день

+ +
+ +
+ +
+

Ваш кошик порожній Перейти до покупок

+
+ +
+

Ваш кошик

+

+ Загальна сума: грн +

+ + + + +
+ +
+ +
+

+ +
+ шт. +

+ + Ціна за одиницю: грн +
+ Загальна ціна: грн + +

+ + +
+ +
+ +
+ + + + +``` + +== Обробник кошика (handle_cart.php) +GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/handle_cart.php +``` +add_to_cart($product_id, $quantity); + } catch (DbException $e) { + error_log("Cart handling error: " . $e->getMessage()); + } + } + header('Location: items.php'); + break; + + case 'DELETE': + $product_id = filter_input(INPUT_GET, 'product_id', FILTER_VALIDATE_INT); + + if ($product_id === null) { + try { + $db->empty_cart(); + } catch (DbException $e) { + error_log("Cart handling error: " . $e->getMessage()); + } + } else if ($product_id !== false) { + try { + $db->remove_from_cart($product_id); + } catch (DbException $e) { + error_log("Cart handling error: " . $e->getMessage()); + } + } + + header('Location: cart.php'); + break; + + default: + break; +} + +exit(); +``` + +== Клас для роботи з базою даних (DB.php) +GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/DB.php +``` +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 settings ( + name TEXT, + age TEXT + ); + "); + if ($this->pdo->query("SELECT COUNT(*) FROM settings;")->fetchColumn() == 0) { + $this->pdo->exec("INSERT INTO settings (name, age) VALUES ('user', 0);"); + } + } catch (PDOException $e) { + throw new DbException("Error initialising settings 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()); + } + } + + /** + * Retrieve user information from the database + * + * @return array + * @throws DbException If there's a database error. + */ + public function retrieve_user(): array + { + try { + $stmt = $this->pdo->query("SELECT name, age FROM settings;"); + return $stmt->fetch(PDO::FETCH_ASSOC); + } catch (PDOException $e) { + throw new DbException("Error retrieving user info.\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 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()); + } + } +} +``` diff --git a/semester-4/СмП/lb-4/README.md b/semester-4/СмП/lb-4/README.md new file mode 100644 index 0000000..354c7a9 --- /dev/null +++ b/semester-4/СмП/lb-4/README.md @@ -0,0 +1,4 @@ +> [!NOTE] +> Викладач: Сокорчук І. П. +> +> Оцінка: In Progress diff --git a/semester-4/СмП/lb-4/src/index.php b/semester-4/СмП/lb-4/src/index.php new file mode 100644 index 0000000..ab1f026 --- /dev/null +++ b/semester-4/СмП/lb-4/src/index.php @@ -0,0 +1,92 @@ +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'; +} diff --git a/semester-4/СмП/lb-4/src/public/assets/logo.png b/semester-4/СмП/lb-4/src/public/assets/logo.png new file mode 100644 index 0000000..8de60c2 Binary files /dev/null and b/semester-4/СмП/lb-4/src/public/assets/logo.png differ diff --git a/semester-4/СмП/lb-4/src/public/css/style.css b/semester-4/СмП/lb-4/src/public/css/style.css new file mode 100644 index 0000000..3cb6e55 --- /dev/null +++ b/semester-4/СмП/lb-4/src/public/css/style.css @@ -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; +} diff --git a/semester-4/СмП/lb-4/src/src/Controllers/AuthController.php b/semester-4/СмП/lb-4/src/src/Controllers/AuthController.php new file mode 100644 index 0000000..c0e0bf7 --- /dev/null +++ b/semester-4/СмП/lb-4/src/src/Controllers/AuthController.php @@ -0,0 +1,109 @@ +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 $data + */ + private function render(string $template, array $data = []): void + { + extract($data); + include 'templates/pages/' . $template . '.php'; + } +} diff --git a/semester-4/СмП/lb-4/src/src/Controllers/CartController.php b/semester-4/СмП/lb-4/src/src/Controllers/CartController.php new file mode 100644 index 0000000..04202d8 --- /dev/null +++ b/semester-4/СмП/lb-4/src/src/Controllers/CartController.php @@ -0,0 +1,77 @@ +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 $data + */ + private function render(string $template, array $data = []): void + { + extract($data); + include 'templates/pages/' . $template . '.php'; + } +} diff --git a/semester-4/СмП/lb-4/src/src/Controllers/HomeController.php b/semester-4/СмП/lb-4/src/src/Controllers/HomeController.php new file mode 100644 index 0000000..e9a66d6 --- /dev/null +++ b/semester-4/СмП/lb-4/src/src/Controllers/HomeController.php @@ -0,0 +1,30 @@ +cartRepo = new CartRepository($db); + } + + public function index(): void + { + $data = [ + 'title' => 'Головна сторінка', + 'cart_count' => $this->cartRepo->getCount(), + 'user' => $_SESSION['user'] + ]; + + $this->render('home', $data); + } + + /** + * @param array $data + */ + private function render(string $template, array $data = []): void + { + extract($data); + include 'templates/pages/' . $template . '.php'; + } +} diff --git a/semester-4/СмП/lb-4/src/src/Controllers/ItemsController.php b/semester-4/СмП/lb-4/src/src/Controllers/ItemsController.php new file mode 100644 index 0000000..d56aad2 --- /dev/null +++ b/semester-4/СмП/lb-4/src/src/Controllers/ItemsController.php @@ -0,0 +1,33 @@ +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 $data + */ + private function render(string $template, array $data = []): void + { + extract($data); + include 'templates/pages/' . $template . '.php'; + } +} diff --git a/semester-4/СмП/lb-4/src/src/Controllers/ProfileController.php b/semester-4/СмП/lb-4/src/src/Controllers/ProfileController.php new file mode 100644 index 0000000..5ed07fe --- /dev/null +++ b/semester-4/СмП/lb-4/src/src/Controllers/ProfileController.php @@ -0,0 +1,96 @@ +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 $data + */ + private function render(string $template, array $data = []): void + { + extract($data); + include 'templates/pages/' . $template . '.php'; + } +} diff --git a/semester-4/СмП/lb-4/src/src/Database/CartRepository.php b/semester-4/СмП/lb-4/src/src/Database/CartRepository.php new file mode 100644 index 0000000..e0c8db5 --- /dev/null +++ b/semester-4/СмП/lb-4/src/src/Database/CartRepository.php @@ -0,0 +1,45 @@ +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); + } +} diff --git a/semester-4/СмП/lb-4/src/src/Database/DB.php b/semester-4/СмП/lb-4/src/src/Database/DB.php new file mode 100644 index 0000000..3be7368 --- /dev/null +++ b/semester-4/СмП/lb-4/src/src/Database/DB.php @@ -0,0 +1,394 @@ +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()); + } + } +} diff --git a/semester-4/СмП/lb-4/src/templates/components/error.php b/semester-4/СмП/lb-4/src/templates/components/error.php new file mode 100644 index 0000000..7d209eb --- /dev/null +++ b/semester-4/СмП/lb-4/src/templates/components/error.php @@ -0,0 +1,5 @@ + +
+ +
+ diff --git a/semester-4/СмП/lb-4/src/templates/components/footer.php b/semester-4/СмП/lb-4/src/templates/components/footer.php new file mode 100644 index 0000000..ac2c5f5 --- /dev/null +++ b/semester-4/СмП/lb-4/src/templates/components/footer.php @@ -0,0 +1,5 @@ +
+ +

© ТОВ "Весна". Усі права захищені.

+
diff --git a/semester-4/СмП/lb-4/src/templates/components/header.php b/semester-4/СмП/lb-4/src/templates/components/header.php new file mode 100644 index 0000000..a80ef15 --- /dev/null +++ b/semester-4/СмП/lb-4/src/templates/components/header.php @@ -0,0 +1,7 @@ +
+

Продовольчий магазин "Весна"

+ +

Добрий день

+ + +
diff --git a/semester-4/СмП/lb-4/src/templates/components/navigation.php b/semester-4/СмП/lb-4/src/templates/components/navigation.php new file mode 100644 index 0000000..e3ebf90 --- /dev/null +++ b/semester-4/СмП/lb-4/src/templates/components/navigation.php @@ -0,0 +1,17 @@ + diff --git a/semester-4/СмП/lb-4/src/templates/layout.php b/semester-4/СмП/lb-4/src/templates/layout.php new file mode 100644 index 0000000..8a600ef --- /dev/null +++ b/semester-4/СмП/lb-4/src/templates/layout.php @@ -0,0 +1,20 @@ + + + + + + <?php echo htmlspecialchars($title ?? 'Продовольчий магазин "Весна"'); ?> + + + + + + +
+ +
+ + + + + diff --git a/semester-4/СмП/lb-4/src/templates/pages/404.php b/semester-4/СмП/lb-4/src/templates/pages/404.php new file mode 100644 index 0000000..8887914 --- /dev/null +++ b/semester-4/СмП/lb-4/src/templates/pages/404.php @@ -0,0 +1,9 @@ + +
+

Будь-ласка увійдіть в акаунт для доступу до цієї сторінки

+ logo +
+ diff --git a/semester-4/СмП/lb-4/src/templates/pages/cart.php b/semester-4/СмП/lb-4/src/templates/pages/cart.php new file mode 100644 index 0000000..7bb0428 --- /dev/null +++ b/semester-4/СмП/lb-4/src/templates/pages/cart.php @@ -0,0 +1,46 @@ + + +
+

Ваш кошик порожній Перейти до покупок

+
+ +
+

Ваш кошик

+

+ Загальна сума: грн +

+ + + +
+ +
+
+ +
+ +
+

+ +
+ шт. +

+ + Ціна за одиницю: грн +
+ Загальна ціна: грн + +

+ +
+ + +
+
+ +
+ + diff --git a/semester-4/СмП/lb-4/src/templates/pages/home.php b/semester-4/СмП/lb-4/src/templates/pages/home.php new file mode 100644 index 0000000..9bd355c --- /dev/null +++ b/semester-4/СмП/lb-4/src/templates/pages/home.php @@ -0,0 +1,8 @@ + +
+ logo +
+ diff --git a/semester-4/СмП/lb-4/src/templates/pages/items.php b/semester-4/СмП/lb-4/src/templates/pages/items.php new file mode 100644 index 0000000..6de4f23 --- /dev/null +++ b/semester-4/СмП/lb-4/src/templates/pages/items.php @@ -0,0 +1,21 @@ + +

Доступні товари

+
+ +
+

+

Ціна: грн

+ +
+ + + + +
+
+ +
+ diff --git a/semester-4/СмП/lb-4/src/templates/pages/login.php b/semester-4/СмП/lb-4/src/templates/pages/login.php new file mode 100644 index 0000000..7280c8a --- /dev/null +++ b/semester-4/СмП/lb-4/src/templates/pages/login.php @@ -0,0 +1,28 @@ + +
+

Вхід в систему

+ + + +
+
+
+ +
+ +
+
+ +
+ + +
+ +

+ Немає акаунта? Зареєструватися +

+
+ diff --git a/semester-4/СмП/lb-4/src/templates/pages/profile.php b/semester-4/СмП/lb-4/src/templates/pages/profile.php new file mode 100644 index 0000000..09c6c26 --- /dev/null +++ b/semester-4/СмП/lb-4/src/templates/pages/profile.php @@ -0,0 +1,32 @@ + + + + +
+
+ Profile Image +
+ +
+ + + + + + + + + + + + + + + +
+
+ + diff --git a/semester-4/СмП/lb-4/src/templates/pages/register.php b/semester-4/СмП/lb-4/src/templates/pages/register.php new file mode 100644 index 0000000..f5b0678 --- /dev/null +++ b/semester-4/СмП/lb-4/src/templates/pages/register.php @@ -0,0 +1,43 @@ + +
+

Реєстрація

+ + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+ +

+ Вже є акаунт? Увійти +

+
+ diff --git a/semester-4/СмП/pz-1/Пр_1_Ситник_ПЗПІ_23_2.pdf b/semester-4/СмП/pz-1/Пр_1_Ситник_ПЗПІ_23_2.pdf index 707695e..7af9712 100644 Binary files a/semester-4/СмП/pz-1/Пр_1_Ситник_ПЗПІ_23_2.pdf and b/semester-4/СмП/pz-1/Пр_1_Ситник_ПЗПІ_23_2.pdf differ