Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
eedfa31d21
@ -229,7 +229,13 @@ include 'header.php';
|
||||
if (!empty($_SESSION['user_roles']) && in_array('ADMIN', $_SESSION['user_roles'], true)):
|
||||
?>
|
||||
<a href="productAdder.php" class="auth__submit account__action-link">
|
||||
Produkt hinzufügen
|
||||
Produkt verwalten
|
||||
</a>
|
||||
<a href="offerAdder.php" class="auth__submit account__action-link" style="margin-top: 10px; background-color: #f59e0b;">
|
||||
Angebot verwalten
|
||||
</a>
|
||||
<a href="shopAdder.php" class="auth__submit account__action-link" style="margin-top: 10px; background-color: #10b981;">
|
||||
Shop verwalten
|
||||
</a>
|
||||
<a href="admin_users.php" class="auth__submit account__action-link" style="margin-top: 10px; background-color: #3b82f6;">
|
||||
Benutzerverwaltung
|
||||
|
||||
Binary file not shown.
350
info/MySQL_Dump_Workbench.sql
Normal file
350
info/MySQL_Dump_Workbench.sql
Normal file
@ -0,0 +1,350 @@
|
||||
-- MySQL Script generated by MySQL Workbench
|
||||
-- Mon Apr 6 01:50:45 2026
|
||||
-- Model: New Model Version: 1.0
|
||||
-- MySQL Workbench Forward Engineering
|
||||
|
||||
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
|
||||
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
|
||||
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Schema mydb
|
||||
-- -----------------------------------------------------
|
||||
-- -----------------------------------------------------
|
||||
-- Schema FSST
|
||||
-- -----------------------------------------------------
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Schema FSST
|
||||
-- -----------------------------------------------------
|
||||
CREATE SCHEMA IF NOT EXISTS `FSST` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci ;
|
||||
USE `FSST` ;
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`attributes`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`attributes` (
|
||||
`attributeID` INT NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`unit` VARCHAR(50) NULL DEFAULT NULL,
|
||||
`dataType` VARCHAR(20) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`attributeID`))
|
||||
ENGINE = InnoDB
|
||||
AUTO_INCREMENT = 70
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`brands`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`brands` (
|
||||
`brandID` INT NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`brandID`))
|
||||
ENGINE = InnoDB
|
||||
AUTO_INCREMENT = 8
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`categories`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`categories` (
|
||||
`categoryID` INT NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`parentCategoryID` INT NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`categoryID`),
|
||||
INDEX `categories_ibfk_1` (`parentCategoryID` ASC) VISIBLE,
|
||||
CONSTRAINT `categories_ibfk_1`
|
||||
FOREIGN KEY (`parentCategoryID`)
|
||||
REFERENCES `FSST`.`categories` (`categoryID`)
|
||||
ON DELETE CASCADE)
|
||||
ENGINE = InnoDB
|
||||
AUTO_INCREMENT = 26
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`categoryAttributes`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`categoryAttributes` (
|
||||
`categoryID` INT NOT NULL,
|
||||
`attributeID` INT NOT NULL,
|
||||
PRIMARY KEY (`categoryID`, `attributeID`),
|
||||
INDEX `attributeID` (`attributeID` ASC) VISIBLE,
|
||||
CONSTRAINT `categoryAttributes_ibfk_1`
|
||||
FOREIGN KEY (`categoryID`)
|
||||
REFERENCES `FSST`.`categories` (`categoryID`),
|
||||
CONSTRAINT `categoryAttributes_ibfk_2`
|
||||
FOREIGN KEY (`attributeID`)
|
||||
REFERENCES `FSST`.`attributes` (`attributeID`))
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`users`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`users` (
|
||||
`userID` INT NOT NULL AUTO_INCREMENT,
|
||||
`email` VARCHAR(255) NOT NULL,
|
||||
`passwordHash` VARCHAR(255) NOT NULL,
|
||||
`displayName` VARCHAR(255) NULL DEFAULT NULL,
|
||||
`isActive` TINYINT(1) NOT NULL DEFAULT '1',
|
||||
`createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`profilePicture` VARCHAR(255) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`userID`),
|
||||
UNIQUE INDEX `email` (`email` ASC) VISIBLE)
|
||||
ENGINE = InnoDB
|
||||
AUTO_INCREMENT = 45
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`notifications`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`notifications` (
|
||||
`notificationID` INT NOT NULL AUTO_INCREMENT,
|
||||
`userID` INT NOT NULL,
|
||||
`title` VARCHAR(255) NOT NULL,
|
||||
`message` TEXT NOT NULL,
|
||||
`isRead` TINYINT(1) NOT NULL DEFAULT '0',
|
||||
`createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`notificationID`),
|
||||
INDEX `fk_notifications_user` (`userID` ASC) VISIBLE,
|
||||
CONSTRAINT `fk_notifications_user`
|
||||
FOREIGN KEY (`userID`)
|
||||
REFERENCES `FSST`.`users` (`userID`)
|
||||
ON DELETE CASCADE)
|
||||
ENGINE = InnoDB
|
||||
AUTO_INCREMENT = 4
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`products`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`products` (
|
||||
`productID` INT NOT NULL AUTO_INCREMENT,
|
||||
`categoryID` INT NOT NULL,
|
||||
`brandID` INT NOT NULL,
|
||||
`model` VARCHAR(255) NOT NULL,
|
||||
`description` TEXT NULL DEFAULT NULL,
|
||||
`imagePath` VARCHAR(255) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`productID`),
|
||||
INDEX `categoryID` (`categoryID` ASC) VISIBLE,
|
||||
INDEX `brandID` (`brandID` ASC) VISIBLE,
|
||||
CONSTRAINT `products_ibfk_1`
|
||||
FOREIGN KEY (`categoryID`)
|
||||
REFERENCES `FSST`.`categories` (`categoryID`),
|
||||
CONSTRAINT `products_ibfk_2`
|
||||
FOREIGN KEY (`brandID`)
|
||||
REFERENCES `FSST`.`brands` (`brandID`))
|
||||
ENGINE = InnoDB
|
||||
AUTO_INCREMENT = 1347
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`shops`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`shops` (
|
||||
`shopID` INT NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`website` VARCHAR(255) NULL DEFAULT NULL,
|
||||
`logoPath` VARCHAR(255) NULL DEFAULT NULL,
|
||||
`shippingTime` VARCHAR(255) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`shopID`))
|
||||
ENGINE = InnoDB
|
||||
AUTO_INCREMENT = 7
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`offers`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`offers` (
|
||||
`offerID` INT NOT NULL AUTO_INCREMENT,
|
||||
`productID` INT NOT NULL,
|
||||
`shopID` INT NOT NULL,
|
||||
`price` DECIMAL(10,2) NOT NULL,
|
||||
`shippingCost` DECIMAL(10,2) NULL DEFAULT NULL,
|
||||
`inStock` TINYINT(1) NULL DEFAULT NULL,
|
||||
`offerURL` VARCHAR(255) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`offerID`),
|
||||
INDEX `productID` (`productID` ASC) VISIBLE,
|
||||
INDEX `shopID` (`shopID` ASC) VISIBLE,
|
||||
CONSTRAINT `offers_ibfk_1`
|
||||
FOREIGN KEY (`productID`)
|
||||
REFERENCES `FSST`.`products` (`productID`),
|
||||
CONSTRAINT `offers_ibfk_2`
|
||||
FOREIGN KEY (`shopID`)
|
||||
REFERENCES `FSST`.`shops` (`shopID`))
|
||||
ENGINE = InnoDB
|
||||
AUTO_INCREMENT = 10
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`priceAlerts`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`priceAlerts` (
|
||||
`alertID` INT NOT NULL AUTO_INCREMENT,
|
||||
`userID` INT NOT NULL,
|
||||
`productID` INT NOT NULL,
|
||||
`targetPrice` DECIMAL(10,2) NOT NULL,
|
||||
`isActive` TINYINT(1) NOT NULL DEFAULT '1',
|
||||
`createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`alertID`),
|
||||
INDEX `fk_pricealerts_user` (`userID` ASC) VISIBLE,
|
||||
INDEX `fk_pricealerts_product` (`productID` ASC) VISIBLE,
|
||||
CONSTRAINT `fk_pricealerts_product`
|
||||
FOREIGN KEY (`productID`)
|
||||
REFERENCES `FSST`.`products` (`productID`)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_pricealerts_user`
|
||||
FOREIGN KEY (`userID`)
|
||||
REFERENCES `FSST`.`users` (`userID`)
|
||||
ON DELETE CASCADE)
|
||||
ENGINE = InnoDB
|
||||
AUTO_INCREMENT = 4
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`productAttributes`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`productAttributes` (
|
||||
`productID` INT NOT NULL,
|
||||
`attributeID` INT NOT NULL,
|
||||
`valueString` VARCHAR(255) NULL DEFAULT NULL,
|
||||
`valueNumber` DECIMAL(10,2) NULL DEFAULT NULL,
|
||||
`valueBool` TINYINT(1) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`productID`, `attributeID`),
|
||||
INDEX `attributeID` (`attributeID` ASC) VISIBLE,
|
||||
CONSTRAINT `productAttributes_ibfk_1`
|
||||
FOREIGN KEY (`productID`)
|
||||
REFERENCES `FSST`.`products` (`productID`),
|
||||
CONSTRAINT `productAttributes_ibfk_2`
|
||||
FOREIGN KEY (`attributeID`)
|
||||
REFERENCES `FSST`.`attributes` (`attributeID`))
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`reviews`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`reviews` (
|
||||
`reviewID` INT NOT NULL AUTO_INCREMENT,
|
||||
`userID` INT NOT NULL,
|
||||
`productID` INT NOT NULL,
|
||||
`rating` INT NOT NULL,
|
||||
`comment` TEXT NULL DEFAULT NULL,
|
||||
`createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`reviewID`),
|
||||
INDEX `fk_reviews_user` (`userID` ASC) VISIBLE,
|
||||
INDEX `fk_reviews_product` (`productID` ASC) VISIBLE,
|
||||
CONSTRAINT `fk_reviews_product`
|
||||
FOREIGN KEY (`productID`)
|
||||
REFERENCES `FSST`.`products` (`productID`)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_reviews_user`
|
||||
FOREIGN KEY (`userID`)
|
||||
REFERENCES `FSST`.`users` (`userID`)
|
||||
ON DELETE CASCADE)
|
||||
ENGINE = InnoDB
|
||||
AUTO_INCREMENT = 42
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`roles`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`roles` (
|
||||
`roleID` INT NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(50) NOT NULL,
|
||||
PRIMARY KEY (`roleID`),
|
||||
UNIQUE INDEX `name` (`name` ASC) VISIBLE)
|
||||
ENGINE = InnoDB
|
||||
AUTO_INCREMENT = 4
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`userFavorites`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`userFavorites` (
|
||||
`userID` INT NOT NULL,
|
||||
`productID` INT NOT NULL,
|
||||
`createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`userID`, `productID`),
|
||||
INDEX `fk_favorites_product` (`productID` ASC) VISIBLE,
|
||||
CONSTRAINT `fk_favorites_product`
|
||||
FOREIGN KEY (`productID`)
|
||||
REFERENCES `FSST`.`products` (`productID`)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_favorites_user`
|
||||
FOREIGN KEY (`userID`)
|
||||
REFERENCES `FSST`.`users` (`userID`)
|
||||
ON DELETE CASCADE)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`userRoles`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`userRoles` (
|
||||
`userID` INT NOT NULL,
|
||||
`roleID` INT NOT NULL,
|
||||
PRIMARY KEY (`userID`, `roleID`),
|
||||
INDEX `fk_userroles_role` (`roleID` ASC) VISIBLE,
|
||||
CONSTRAINT `fk_userroles_role`
|
||||
FOREIGN KEY (`roleID`)
|
||||
REFERENCES `FSST`.`roles` (`roleID`)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_userroles_user`
|
||||
FOREIGN KEY (`userID`)
|
||||
REFERENCES `FSST`.`users` (`userID`)
|
||||
ON DELETE CASCADE)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `FSST`.`userSessions`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `FSST`.`userSessions` (
|
||||
`sessionID` VARCHAR(128) NOT NULL,
|
||||
`userID` INT NOT NULL,
|
||||
`expiresAt` TIMESTAMP NOT NULL,
|
||||
PRIMARY KEY (`sessionID`),
|
||||
INDEX `fk_sessions_user` (`userID` ASC) VISIBLE,
|
||||
CONSTRAINT `fk_sessions_user`
|
||||
FOREIGN KEY (`userID`)
|
||||
REFERENCES `FSST`.`users` (`userID`)
|
||||
ON DELETE CASCADE)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
|
||||
SET SQL_MODE=@OLD_SQL_MODE;
|
||||
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
|
||||
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
|
||||
332
offerAdder.php
Normal file
332
offerAdder.php
Normal file
@ -0,0 +1,332 @@
|
||||
<?php
|
||||
/**
|
||||
* @file offerAdder.php
|
||||
* @brief Angebot hinzufügen und verwalten.
|
||||
*
|
||||
* Dieses Skript stellt eine Benutzeroberfläche und die zugrundeliegende Logik bereit,
|
||||
* um Angebote für Produkte in verschiedenen Shops hinzuzufügen, anzuzeigen und zu löschen.
|
||||
* Der Zugriff ist streng auf Administratoren beschränkt.
|
||||
*
|
||||
* @author Geizkragen-Team
|
||||
* @date 2026-04-06
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/lib/bootstrap.php';
|
||||
|
||||
/**
|
||||
* @brief Zugriffskontrolle für Administratoren.
|
||||
*
|
||||
* Überprüft, ob der aktuelle Benutzer angemeldet ist, Rollen zugewiesen hat
|
||||
* und ob die Rolle 'ADMIN' in seinen Benutzerrollen enthalten ist.
|
||||
* Falls nicht, wird ein HTTP 403 (Forbidden) Fehler gesendet und eine
|
||||
* Fehlermeldung auf der Seite angezeigt, bevor die Ausführung beendet wird.
|
||||
*/
|
||||
// Only ADMIN
|
||||
if (empty($_SESSION['user_id']) || empty($_SESSION['user_roles']) || !in_array('ADMIN', $_SESSION['user_roles'], true)) {
|
||||
http_response_code(403);
|
||||
include 'header.php';
|
||||
echo '<main class="auth"><section class="auth__grid"><div class="auth__card">';
|
||||
echo '<h2 class="auth__title">Zugriff verweigert</h2>';
|
||||
echo '<p>Du hast keine Berechtigung, Angebote hinzuzufügen.</p>';
|
||||
echo '</div></section></main>';
|
||||
include 'footer.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Datenbankverbindung herstellen und Initialisierung von Statusvariablen.
|
||||
*
|
||||
* @var mysqli $conn Die aktive Datenbankverbindung.
|
||||
* @var string $message Eine Nachricht an den Benutzer (Erfolg oder Fehler).
|
||||
* @var string $messageType Der Typ der Nachricht ('success' oder 'error').
|
||||
*/
|
||||
$conn = db_connect();
|
||||
$message = '';
|
||||
$messageType = '';
|
||||
|
||||
/**
|
||||
* @brief Verarbeitung von POST-Anfragen zur Angebotsverwaltung.
|
||||
*
|
||||
* Überprüft, ob das Formular abgesendet wurde und eine spezifische 'action' gesetzt ist.
|
||||
* Unterstützte Aktionen sind 'add_offer' (Angebot hinzufügen) und 'delete_offer' (Angebot löschen).
|
||||
*/
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
/**
|
||||
* @brief Aktion: Neues Angebot hinzufügen.
|
||||
*
|
||||
* Liest die übermittelten Formulardaten aus, validiert diese (Produkt-ID, Shop-ID und
|
||||
* gültiger Preis) und fügt sie durch ein Prepared Statement in die Tabelle `offers` ein.
|
||||
*/
|
||||
if ($_POST['action'] === 'add_offer') {
|
||||
$productID = (int)$_POST['product_id'];
|
||||
$shopID = (int)$_POST['shop_id'];
|
||||
$price = (float)$_POST['price'];
|
||||
$shippingCost = isset($_POST['shipping_cost']) && $_POST['shipping_cost'] !== '' ? (float)$_POST['shipping_cost'] : 0.00;
|
||||
$inStock = isset($_POST['in_stock']) ? 1 : 0;
|
||||
$offerURL = trim($_POST['offer_url']);
|
||||
|
||||
if ($productID > 0 && $shopID > 0 && $price >= 0) {
|
||||
/**
|
||||
* @brief Einfügen in die Datenbank.
|
||||
* Bereitet das SQL-Statement vor und bindet die Parameter sicher, um SQL-Injection zu verhindern.
|
||||
*/
|
||||
$stmt = $conn->prepare("INSERT INTO offers (productID, shopID, price, shippingCost, inStock, offerURL) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->bind_param("iiddis", $productID, $shopID, $price, $shippingCost, $inStock, $offerURL);
|
||||
if ($stmt->execute()) {
|
||||
$message = 'Angebot erfolgreich hinzugefügt!';
|
||||
$messageType = 'success';
|
||||
} else {
|
||||
$message = 'Fehler beim Hinzufügen des Angebots.';
|
||||
$messageType = 'error';
|
||||
}
|
||||
$stmt->close();
|
||||
} else {
|
||||
/**
|
||||
* @brief Fehlerbehandlung bei ungültigen Eingaben.
|
||||
*/
|
||||
$message = 'Bitte alle Pflichtfelder korrekt ausfüllen.';
|
||||
$messageType = 'error';
|
||||
}
|
||||
} elseif ($_POST['action'] === 'delete_offer') {
|
||||
/**
|
||||
* @brief Aktion: Existierendes Angebot löschen.
|
||||
*
|
||||
* Überprüft die übermittelte Angebot-ID und löscht den entsprechenden Eintrag
|
||||
* sicher per Prepared Statement aus der Datenbank.
|
||||
*/
|
||||
$offerID = (int)$_POST['offer_id'];
|
||||
if ($offerID > 0) {
|
||||
$stmt = $conn->prepare("DELETE FROM offers WHERE offerID = ?");
|
||||
$stmt->bind_param("i", $offerID);
|
||||
if ($stmt->execute()) {
|
||||
$message = 'Angebot erfolgreich gelöscht!';
|
||||
$messageType = 'success';
|
||||
} else {
|
||||
$message = 'Fehler beim Löschen des Angebots.';
|
||||
$messageType = 'error';
|
||||
}
|
||||
$stmt->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Abrufen aller Produkte für das Dropdown-Feld.
|
||||
*
|
||||
* Führt eine Query aus, um alle verfügbaren Produkte (IDs und Modelle)
|
||||
* alphabetisch geordnet aus der Datenbank zu laden.
|
||||
*
|
||||
* @var array $products Enthält die geladenen Produkte.
|
||||
*/
|
||||
// Get all products for dropdown
|
||||
$productsResult = $conn->query("SELECT productID, model FROM products ORDER BY model ASC");
|
||||
$products = [];
|
||||
if ($productsResult) {
|
||||
while ($row = $productsResult->fetch_assoc()) {
|
||||
$products[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Abrufen aller Shops für das Dropdown-Feld.
|
||||
*
|
||||
* Führt eine Query aus, um alle verfügbaren Shops (IDs und Namen)
|
||||
* alphabetisch geordnet aus der Datenbank zu laden.
|
||||
*
|
||||
* @var array $shops Enthält die geladenen Shops.
|
||||
*/
|
||||
// Get all shops for dropdown
|
||||
$shopsResult = $conn->query("SELECT shopID, name FROM shops ORDER BY name ASC");
|
||||
$shops = [];
|
||||
if ($shopsResult) {
|
||||
while ($row = $shopsResult->fetch_assoc()) {
|
||||
$shops[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Filterlogik für die Anzeige existierender Angebote.
|
||||
*
|
||||
* Überprüft, ob über GET ein Filter für eine bestimmte Produkt-ID gesetzt wurde.
|
||||
* Ist dies der Fall, werden nur Angebote für dieses Produkt angezeigt.
|
||||
*
|
||||
* @var int $filterProductID Die ID des zu filternden Produkts (0, wenn kein Filter gesetzt ist).
|
||||
* @var string $offersQuery Die dynamisch generierte SQL-Abfrage für die Angebote.
|
||||
*/
|
||||
$filterProductID = isset($_GET['filter_product_id']) ? (int)$_GET['filter_product_id'] : 0;
|
||||
|
||||
$offersQuery = "
|
||||
SELECT o.offerID, p.model AS productName, s.name AS shopName, o.price
|
||||
FROM offers o
|
||||
JOIN products p ON o.productID = p.productID
|
||||
JOIN shops s ON o.shopID = s.shopID
|
||||
";
|
||||
|
||||
if ($filterProductID > 0) {
|
||||
/**
|
||||
* @brief Anwenden des Filters auf die SQL-Query, falls notwendig.
|
||||
*/
|
||||
$offersQuery .= " WHERE o.productID = " . $filterProductID;
|
||||
}
|
||||
|
||||
$offersQuery .= " ORDER BY o.offerID DESC";
|
||||
|
||||
/**
|
||||
* @brief Ausführen der Angebot-Abfrage und Speichern in ein Array.
|
||||
*
|
||||
* @var array $existingOffers Enthält die geladenen Angebote inkl. Shop- und Produkt-Namen,
|
||||
* die zur Anzeige in der Tabelle verwendet werden.
|
||||
*/
|
||||
// Get existing offers to manage
|
||||
$offersResult = $conn->query($offersQuery);
|
||||
$existingOffers = [];
|
||||
if ($offersResult) {
|
||||
while ($row = $offersResult->fetch_assoc()) {
|
||||
$existingOffers[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Einbinden des HTML-Headers, der Navigation und Stile.
|
||||
*/
|
||||
include 'header.php';
|
||||
?>
|
||||
<main class="auth">
|
||||
<section class="auth__grid" style="grid-template-columns: 1fr;">
|
||||
<div class="auth__card">
|
||||
<header class="auth__header">
|
||||
<h2 class="auth__title">Angebot verwalten</h2>
|
||||
</header>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<p class="<?php echo $messageType === 'success' ? 'auth__alert__sucess' : 'auth__alert__error'; ?>" style="margin-bottom: 1rem;">
|
||||
<?php echo htmlspecialchars($message); ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST" action="offerAdder.php" class="auth__form">
|
||||
<input type="hidden" name="action" value="add_offer">
|
||||
|
||||
<div class="auth__select__wrap">
|
||||
<label class="auth__select__label" for="product_id">Produkt auswählen *</label>
|
||||
<select id="product_id" name="product_id" class="auth__select" required>
|
||||
<option value="">-- Bitte wählen --</option>
|
||||
<?php foreach ($products as $product): ?>
|
||||
<option value="<?php echo htmlspecialchars($product['productID']); ?>" <?php echo (isset($_GET['productID']) && $_GET['productID'] == $product['productID']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($product['model']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="auth__select__wrap" style="margin-top: 1rem;">
|
||||
<label class="auth__select__label" for="shop_id">Shop auswählen *</label>
|
||||
<select id="shop_id" name="shop_id" class="auth__select" required>
|
||||
<option value="">-- Bitte wählen --</option>
|
||||
<?php foreach ($shops as $shop): ?>
|
||||
<option value="<?php echo htmlspecialchars($shop['shopID']); ?>">
|
||||
<?php echo htmlspecialchars($shop['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 1rem; margin-top: 1rem;">
|
||||
<div style="flex: 1;">
|
||||
<label for="price" style="display: block; margin-bottom: 0.5rem; font-weight: 500; color: var(--text-muted);">Preis (€) *</label>
|
||||
<input type="number" id="price" name="price" class="auth__input" step="0.01" min="0" required>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<label for="shipping_cost" style="display: block; margin-bottom: 0.5rem; font-weight: 500; color: var(--text-muted);">Versandkosten (€)</label>
|
||||
<input type="number" id="shipping_cost" name="shipping_cost" class="auth__input" step="0.01" min="0" value="0.00">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1rem; display: flex; align-items: center; gap: 0.5rem;">
|
||||
<input type="checkbox" id="in_stock" name="in_stock" value="1" checked style="width: 18px; height: 18px; accent-color: var(--primary-color);">
|
||||
<label for="in_stock" style="font-weight: 500; color: var(--text-muted); cursor: pointer;">Auf Lager</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1rem;">
|
||||
<label for="offer_url" style="display: block; margin-bottom: 0.5rem; font-weight: 500; color: var(--text-muted);">Angebots-URL</label>
|
||||
<input type="url" id="offer_url" name="offer_url" class="auth__input" placeholder="https://">
|
||||
</div>
|
||||
|
||||
<div class="auth__actions" style="margin-top: 1.5rem;">
|
||||
<button type="submit" class="auth__submit">Angebot hinzufügen</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="auth__card" style="margin-top: 2rem;">
|
||||
<header class="auth__header">
|
||||
<h2 class="auth__title">Bestehende Angebote verwalten</h2>
|
||||
</header>
|
||||
|
||||
<form method="GET" action="offerAdder.php" style="margin-bottom: 1.5rem; display: flex; gap: 1rem; align-items: flex-end;">
|
||||
<div class="auth__select__wrap" style="flex: 1;">
|
||||
<label class="auth__select__label" for="filter_product_id">Nach Produkt filtern</label>
|
||||
<select id="filter_product_id" name="filter_product_id" class="auth__select" onchange="this.form.submit()">
|
||||
<option value="">-- Alle Produkte anzeigen --</option>
|
||||
<?php foreach ($products as $product): ?>
|
||||
<option value="<?php echo htmlspecialchars($product['productID']); ?>" <?php echo ($filterProductID == $product['productID']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($product['model']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php if ($filterProductID > 0): ?>
|
||||
<a href="offerAdder.php" style="color: var(--primary-color); text-decoration: none; font-weight: 500; font-size: 0.9rem; padding-bottom: 0.75rem;">Filter zurücksetzen</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
||||
<?php if (count($existingOffers) > 0): ?>
|
||||
<div style="overflow-x: auto; margin-top: 1rem;">
|
||||
<table style="width: 100%; border-collapse: collapse; min-width: 500px;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); text-align: left;">
|
||||
<th style="padding: 0.75rem 0.5rem; color: var(--text-muted); font-weight: 600;">Produkt</th>
|
||||
<th style="padding: 0.75rem 0.5rem; color: var(--text-muted); font-weight: 600;">Shop</th>
|
||||
<th style="padding: 0.75rem 0.5rem; color: var(--text-muted); font-weight: 600;">Preis</th>
|
||||
<th style="padding: 0.75rem 0.5rem; text-align: right; color: var(--text-muted); font-weight: 600;">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($existingOffers as $offer): ?>
|
||||
<tr style="border-bottom: 1px solid var(--border-light);">
|
||||
<td style="padding: 1rem 0.5rem; vertical-align: middle;"><?php echo htmlspecialchars($offer['productName']); ?></td>
|
||||
<td style="padding: 1rem 0.5rem; vertical-align: middle;"><?php echo htmlspecialchars($offer['shopName']); ?></td>
|
||||
<td style="padding: 1rem 0.5rem; vertical-align: middle; font-weight: 600;">€<?php echo number_format($offer['price'], 2, ',', '.'); ?></td>
|
||||
<td style="padding: 1rem 0.5rem; text-align: right; vertical-align: middle;">
|
||||
<form method="POST" action="offerAdder.php<?php echo $filterProductID > 0 ? '?filter_product_id=' . $filterProductID : ''; ?>" onsubmit="return confirm('Möchtest du dieses Angebot wirklich löschen?');" style="display: inline-block; margin: 0;">
|
||||
<input type="hidden" name="action" value="delete_offer">
|
||||
<input type="hidden" name="offer_id" value="<?php echo $offer['offerID']; ?>">
|
||||
<!--
|
||||
@brief Button für das Löschen eines Angebotes.
|
||||
Enthält ein SVG-Icon als Mülleimer. Ein Klick löst einen Bestätigungsdialog (JavaScript) aus.
|
||||
-->
|
||||
<button type="submit" style="background: none; border: none; color: #ef4444; cursor: pointer; display: flex; align-items: center; justify-content: flex-end; padding: 0.5rem; border-radius: 4px;" title="Angebot löschen" onmouseover="this.style.backgroundColor='#fee2e2';" onmouseout="this.style.backgroundColor='transparent';">
|
||||
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path d="M19 6V20a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p style="margin-top: 1rem; color: var(--text-muted); text-align: center; padding: 2rem 0;">Keine Angebote vorhanden.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<?php
|
||||
/**
|
||||
* @brief Einbinden des HTML-Footers am Ende der Seite.
|
||||
*/
|
||||
include 'footer.php';
|
||||
?>
|
||||
@ -68,6 +68,28 @@ if (isset($_GET['categoryID']) && ctype_digit($_GET['categoryID'])) {
|
||||
======================= */
|
||||
$conn = db_connect();
|
||||
|
||||
/* =======================
|
||||
2b) Produkt löschen
|
||||
======================= */
|
||||
$deleteMessage = '';
|
||||
$deleteMessageType = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete_product') {
|
||||
$delProductID = (int)$_POST['product_id'];
|
||||
$catIDFromForm = (int)$_POST['categoryID']; // to redirect back
|
||||
if ($delProductID > 0) {
|
||||
$delStmt = $conn->prepare("DELETE FROM products WHERE productID = ?");
|
||||
$delStmt->bind_param("i", $delProductID);
|
||||
if ($delStmt->execute()) {
|
||||
$deleteMessage = 'Produkt erfolgreich gelöscht!';
|
||||
$deleteMessageType = 'success';
|
||||
} else {
|
||||
$deleteMessage = 'Fehler beim Löschen des Produkts (möglicherweise sind noch Angebote oder Attribute verknüpft?).';
|
||||
$deleteMessageType = 'error';
|
||||
}
|
||||
$delStmt->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @section Kategorien-Laden
|
||||
* @brief Abrufen aller verfügbaren Kategorien.
|
||||
@ -419,7 +441,7 @@ include 'header.php';
|
||||
<?php if ($categoryID > 0): ?>
|
||||
<div class="auth__card">
|
||||
<header class="auth__header">
|
||||
<h2 class="auth__title">Produkt hinzufügen</h2>
|
||||
<h2 class="auth__title">Produkt verwalten</h2>
|
||||
</header>
|
||||
|
||||
<form method="post" class="auth__form" enctype="multipart/form-data">
|
||||
@ -484,6 +506,52 @@ include 'header.php';
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Produkt löschen -->
|
||||
<div class="auth__card">
|
||||
<header class="auth__header">
|
||||
<h2 class="auth__title">Produkt löschen</h2>
|
||||
</header>
|
||||
|
||||
<?php if ($deleteMessage): ?>
|
||||
<div class="auth__message auth__message--<?= $deleteMessageType ?>">
|
||||
<?= htmlspecialchars($deleteMessage) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" class="auth__form">
|
||||
<input type="hidden" name="categoryID" value="<?= $categoryID ?>">
|
||||
<input type="hidden" name="action" value="delete_product">
|
||||
<div class="auth__select__wrap">
|
||||
<label class="auth__select__label" for="product_id">Produkt</label>
|
||||
<select id="product_id" name="product_id" class="auth__select" required>
|
||||
<option value="">Produkt wählen</option>
|
||||
<?php
|
||||
// Produkte laden (gefiltert nach Kategorie falls ausgewählt, sonst alle)
|
||||
$products = [];
|
||||
$query = "SELECT productID, model FROM products ORDER BY model";
|
||||
if ($categoryID > 0) {
|
||||
$query = "SELECT productID, model FROM products WHERE categoryID = " . (int)$categoryID . " ORDER BY model";
|
||||
}
|
||||
$result = $conn->query($query);
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$products[] = $row;
|
||||
}
|
||||
foreach ($products as $product): ?>
|
||||
<option value="<?= $product['productID'] ?>">
|
||||
<?= htmlspecialchars($product['model']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="auth__actions">
|
||||
<button type="submit" class="auth__submit" style="background-color: #ef4444; border-color: #ef4444;" onclick="return confirm('Möchtest du das Produkt wirklich löschen?');">
|
||||
Produkt löschen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</main>
|
||||
|
||||
|
||||
355
shopAdder.php
Normal file
355
shopAdder.php
Normal file
@ -0,0 +1,355 @@
|
||||
<?php
|
||||
/**
|
||||
* @file shopAdder.php
|
||||
* @brief Shop-Verwaltungssystem (Hinzufügen und Löschen von Shops)
|
||||
*
|
||||
* Dieses Skript ermöglicht Administratoren das Hinzufügen neuer Shops sowie das Löschen
|
||||
* bestehender Shops, sofern diese keine verknüpften Angebote (Offers) haben.
|
||||
* Es enthält Funktionen für den Upload oder die Verlinkung von Shop-Logos.
|
||||
*
|
||||
* @author GitHub Copilot
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/lib/bootstrap.php';
|
||||
|
||||
/**
|
||||
* @brief Überprüfung der Zugriffsberechtigungen
|
||||
*
|
||||
* Es wird geprüft, ob eine aktive Benutzersession existiert und ob der Benutzer
|
||||
* die Rolle 'ADMIN' besitzt. Wenn nicht, wird der Zugriff verweigert (Status 403)
|
||||
* und eine entsprechende Fehlermeldung ausgegeben.
|
||||
*/
|
||||
// Only ADMIN
|
||||
if (empty($_SESSION['user_id']) || empty($_SESSION['user_roles']) || !in_array('ADMIN', $_SESSION['user_roles'], true)) {
|
||||
http_response_code(403);
|
||||
include 'header.php';
|
||||
echo '<main class="auth"><section class="auth__grid"><div class="auth__card">';
|
||||
echo '<h2 class="auth__title">Zugriff verweigert</h2>';
|
||||
echo '<p>Du hast keine Berechtigung, Shops hinzuzufügen.</p>';
|
||||
echo '</div></section></main>';
|
||||
include 'footer.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Datenbankverbindung herstellen
|
||||
* @var mysqli $conn Die aktive Datenbankverbindung
|
||||
*/
|
||||
$conn = db_connect();
|
||||
|
||||
/**
|
||||
* @var string $message Speichert Erfolgs- oder Fehlermeldungen für den Benutzer
|
||||
* @var string $messageType Definiert den Typ der Nachricht ('success' oder 'error')
|
||||
*/
|
||||
$message = '';
|
||||
$messageType = '';
|
||||
|
||||
/**
|
||||
* @brief Verarbeitung von POST-Anfragen (Formular-Übermittlungen)
|
||||
*
|
||||
* Prüft, ob ein Formular abgesendet wurde und welche Aktion ('add_shop' oder 'delete_shop')
|
||||
* ausgeführt werden soll.
|
||||
*/
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
if ($_POST['action'] === 'add_shop') {
|
||||
/**
|
||||
* @brief Extrahieren der Formulardaten für einen neuen Shop
|
||||
*/
|
||||
$name = trim($_POST['name']);
|
||||
$website = trim($_POST['website']);
|
||||
$shippingTime = trim($_POST['shipping_time']);
|
||||
$logoPath = null;
|
||||
|
||||
/**
|
||||
* @brief Verarbeitung des Logo-Uploads
|
||||
*
|
||||
* Prüft zunächst, ob eine Datei hochgeladen wurde und kein Fehler aufgetreten ist.
|
||||
*/
|
||||
// Handle logo upload
|
||||
if (isset($_FILES['logo']) && $_FILES['logo']['error'] === UPLOAD_ERR_OK) {
|
||||
/**
|
||||
* @brief Validierung des MIME-Types der hochgeladenen Datei
|
||||
*/
|
||||
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
$fileInfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mimeType = finfo_file($fileInfo, $_FILES['logo']['tmp_name']);
|
||||
finfo_close($fileInfo);
|
||||
|
||||
if (in_array($mimeType, $allowedTypes)) {
|
||||
/**
|
||||
* @brief Verzeichnisstruktur für Logos sicherstellen
|
||||
*/
|
||||
$uploadDir = __DIR__ . '/assets/images/shopLogo/';
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0777, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generierung eines eindeutigen Dateinamens
|
||||
*/
|
||||
$extension = pathinfo($_FILES['logo']['name'], PATHINFO_EXTENSION);
|
||||
$fileName = preg_replace('/[^a-zA-Z0-9_-]/', '_', $name) . '_' . time() . '.' . $extension;
|
||||
$targetFile = $uploadDir . $fileName;
|
||||
|
||||
/**
|
||||
* @brief Speichern der hochgeladenen Datei
|
||||
*/
|
||||
if (move_uploaded_file($_FILES['logo']['tmp_name'], $targetFile)) {
|
||||
$logoPath = 'assets/images/shopLogo/' . $fileName;
|
||||
} else {
|
||||
$message = 'Fehler beim Hochladen des Logos.';
|
||||
$messageType = 'error';
|
||||
}
|
||||
} else {
|
||||
$message = 'Ungültiges Dateiformat für das Logo. Erlaubt sind JPG, PNG, GIF und WEBP.';
|
||||
$messageType = 'error';
|
||||
}
|
||||
} elseif (isset($_POST['logo_url']) && trim($_POST['logo_url']) !== '') {
|
||||
/**
|
||||
* @brief Fallback auf eine angegebene Bild-URL, falls keine Datei hochgeladen wurde
|
||||
*/
|
||||
$logoPath = trim($_POST['logo_url']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Speichern des neuen Shops in der Datenbank
|
||||
*
|
||||
* Wenn keine Fehler aufgetreten sind und ein Name angegeben wurde,
|
||||
* wird der Shop über ein Prepared Statement in die Datenbank eingefügt.
|
||||
*/
|
||||
if (empty($message) && $name !== '') {
|
||||
$stmt = $conn->prepare("INSERT INTO shops (name, website, logoPath, shippingTime) VALUES (?, ?, ?, ?)");
|
||||
$stmt->bind_param("ssss", $name, $website, $logoPath, $shippingTime);
|
||||
if ($stmt->execute()) {
|
||||
$message = 'Shop erfolgreich hinzugefügt!';
|
||||
$messageType = 'success';
|
||||
} else {
|
||||
$message = 'Fehler beim Hinzufügen des Shops.';
|
||||
$messageType = 'error';
|
||||
}
|
||||
$stmt->close();
|
||||
} elseif (empty($message)) {
|
||||
$message = 'Bitte mindestens den Shop-Namen angeben.';
|
||||
$messageType = 'error';
|
||||
}
|
||||
} elseif ($_POST['action'] === 'delete_shop') {
|
||||
/**
|
||||
* @brief Löschen eines bestehenden Shops
|
||||
*/
|
||||
$shopID = (int)$_POST['shop_id'];
|
||||
if ($shopID > 0) {
|
||||
/**
|
||||
* @brief Überprüfung auf verknüpfte Angebote
|
||||
*
|
||||
* Bevor ein Shop gelöscht wird, muss sichergestellt werden, dass
|
||||
* keine Angebote (Offers) mehr mit diesem verknüpft sind, um die
|
||||
* referenzielle Integrität zu wahren.
|
||||
*/
|
||||
// First check if the shop has associated offers
|
||||
$checkStmt = $conn->prepare("SELECT COUNT(*) AS offerCount FROM offers WHERE shopID = ?");
|
||||
$checkStmt->bind_param("i", $shopID);
|
||||
$checkStmt->execute();
|
||||
$result = $checkStmt->get_result();
|
||||
$row = $result->fetch_assoc();
|
||||
$offerCount = (int)$row['offerCount'];
|
||||
$checkStmt->close();
|
||||
|
||||
if ($offerCount > 0) {
|
||||
/**
|
||||
* @brief Löschen verhindern, wenn Angebote existieren
|
||||
*/
|
||||
$message = "Der Shop kann nicht gelöscht werden, da er noch $offerCount verknüpfte Angebote hat.";
|
||||
$messageType = 'error';
|
||||
} else {
|
||||
/**
|
||||
* @brief Führt das Löschen des Shops aus der Datenbank durch
|
||||
*/
|
||||
$stmt = $conn->prepare("DELETE FROM shops WHERE shopID = ?");
|
||||
$stmt->bind_param("i", $shopID);
|
||||
if ($stmt->execute()) {
|
||||
$message = 'Shop erfolgreich gelöscht!';
|
||||
$messageType = 'success';
|
||||
} else {
|
||||
$message = 'Fehler beim Löschen des Shops.';
|
||||
$messageType = 'error';
|
||||
}
|
||||
$stmt->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Abrufen aller bestehenden Shops zur Verwaltung
|
||||
*
|
||||
* Es wird eine Liste aller Shops inklusive der Anzahl ihrer zugeordneten Angebote
|
||||
* (über einen LEFT JOIN mit der Tabelle offers) abgerufen, um diese in der Tabelle
|
||||
* im Frontend anzuzeigen.
|
||||
*/
|
||||
// Get existing shops to manage
|
||||
$shopsResult = $conn->query("
|
||||
SELECT s.shopID, s.name, s.website, s.logoPath, s.shippingTime, COUNT(o.offerID) AS offerCount
|
||||
FROM shops s
|
||||
LEFT JOIN offers o ON s.shopID = o.shopID
|
||||
GROUP BY s.shopID, s.name, s.website, s.logoPath, s.shippingTime
|
||||
ORDER BY s.name ASC
|
||||
");
|
||||
/**
|
||||
* @var array $existingShops Speichert die aus der Datenbank abgerufenen Shops
|
||||
*/
|
||||
$existingShops = [];
|
||||
if ($shopsResult) {
|
||||
while ($row = $shopsResult->fetch_assoc()) {
|
||||
$existingShops[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Einbinden des globalen Headers
|
||||
*/
|
||||
include 'header.php';
|
||||
?>
|
||||
<!--
|
||||
@brief Hauptbereich für die Shop-Verwaltung
|
||||
Hier wird die GUI zum Anlegen und Löschen von Shops aufgebaut.
|
||||
-->
|
||||
<main class="auth">
|
||||
<section class="auth__grid" style="grid-template-columns: 1fr;">
|
||||
<div class="auth__card">
|
||||
<header class="auth__header">
|
||||
<!-- @brief Überschrift für das Hinzufügen-Formular -->
|
||||
<h2 class="auth__title">Shop verwalten</h2>
|
||||
</header>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<!-- @brief Ausgabe von Systemnachrichten (Erfolg / Fehler) -->
|
||||
<p class="<?php echo $messageType === 'success' ? 'auth__alert__sucess' : 'auth__alert__error'; ?>" style="margin-bottom: 1rem;">
|
||||
<?php echo htmlspecialchars($message); ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- @brief Formular zum Hinzufügen eines neuen Shops -->
|
||||
<form method="POST" action="shopAdder.php" class="auth__form" enctype="multipart/form-data">
|
||||
<input type="hidden" name="action" value="add_shop">
|
||||
|
||||
<div>
|
||||
<label for="name" style="display: block; margin-bottom: 0.5rem; font-weight: 500; color: var(--text-muted);">Shop Name *</label>
|
||||
<input type="text" id="name" name="name" class="auth__input" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1rem;">
|
||||
<label for="website" style="display: block; margin-bottom: 0.5rem; font-weight: 500; color: var(--text-muted);">Website URL</label>
|
||||
<input type="url" id="website" name="website" class="auth__input" placeholder="https://">
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1rem;">
|
||||
<label for="shipping_time" style="display: block; margin-bottom: 0.5rem; font-weight: 500; color: var(--text-muted);">Versanddauer (z.B. "1-3 Werktage")</label>
|
||||
<input type="text" id="shipping_time" name="shipping_time" class="auth__input">
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1rem;">
|
||||
<!-- @brief Upload-Bereich für das Shop-Logo oder URL-Eingabe -->
|
||||
<label style="display: block; margin-bottom: 0.5rem; font-weight: 500; color: var(--text-muted);">Shop Logo</label>
|
||||
<div style="display: flex; flex-direction: column; gap: 0.8rem; background: var(--bg-alt); padding: 1rem; border-radius: 6px; border: 1px solid var(--border-light);">
|
||||
<div>
|
||||
<label for="logo" style="display: block; margin-bottom: 0.3rem; font-size: 0.9em;">Bild hochladen:</label>
|
||||
<input type="file" id="logo" name="logo" accept="image/*" class="auth__input" style="padding: 0.5rem;">
|
||||
</div>
|
||||
<div style="text-align: center; color: var(--text-muted); font-size: 0.9em; margin: 0.2rem 0;">- ODER -</div>
|
||||
<div>
|
||||
<label for="logo_url" style="display: block; margin-bottom: 0.3rem; font-size: 0.9em;">Bild-URL:</label>
|
||||
<input type="url" id="logo_url" name="logo_url" class="auth__input" placeholder="https://...">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="auth__actions" style="margin-top: 1.5rem;">
|
||||
<!-- @brief Absende-Button -->
|
||||
<button type="submit" class="auth__submit">Shop hinzufügen</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="auth__card" style="margin-top: 2rem;">
|
||||
<header class="auth__header">
|
||||
<!-- @brief Überschrift für die Liste der bestehenden Shops -->
|
||||
<h2 class="auth__title">Bestehende Shops verwalten</h2>
|
||||
</header>
|
||||
|
||||
<?php if (count($existingShops) > 0): ?>
|
||||
<!-- @brief Tabelle mit den vorhandenen Shops -->
|
||||
<div style="overflow-x: auto; margin-top: 1rem;">
|
||||
<table style="width: 100%; border-collapse: collapse; min-width: 600px;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); text-align: left;">
|
||||
<th style="padding: 0.75rem 0.5rem; color: var(--text-muted); font-weight: 600;">Logo</th>
|
||||
<th style="padding: 0.75rem 0.5rem; color: var(--text-muted); font-weight: 600;">Name</th>
|
||||
<th style="padding: 0.75rem 0.5rem; color: var(--text-muted); font-weight: 600;">Website</th>
|
||||
<th style="padding: 0.75rem 0.5rem; color: var(--text-muted); font-weight: 600;">Angebote</th>
|
||||
<th style="padding: 0.75rem 0.5rem; text-align: right; color: var(--text-muted); font-weight: 600;">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($existingShops as $shop): ?>
|
||||
<!-- @brief Tabellenzeile für jeden einzelnen Shop -->
|
||||
<tr style="border-bottom: 1px solid var(--border-light);">
|
||||
<td style="padding: 1rem 0.5rem; vertical-align: middle;">
|
||||
<?php if ($shop['logoPath']): ?>
|
||||
<img src="<?php echo htmlspecialchars($shop['logoPath']); ?>" alt="Logo" style="max-height: 40px; max-width: 100px; object-fit: contain;">
|
||||
<?php else: ?>
|
||||
<span style="color: var(--text-muted); font-size: 0.8em; font-style: italic;">Kein Logo</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td style="padding: 1rem 0.5rem; vertical-align: middle; font-weight: 500;"><?php echo htmlspecialchars($shop['name']); ?></td>
|
||||
<td style="padding: 1rem 0.5rem; vertical-align: middle;">
|
||||
<?php if ($shop['website']): ?>
|
||||
<a href="<?php echo htmlspecialchars($shop['website']); ?>" target="_blank" rel="noopener noreferrer" style="color: var(--primary-color); text-decoration: none; font-size: 0.9em;">Link ↗</a>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td style="padding: 1rem 0.5rem; vertical-align: middle;">
|
||||
<span style="background: var(--bg-alt); padding: 0.2rem 0.6rem; border-radius: 12px; font-size: 0.85em; font-weight: 600;">
|
||||
<?php echo $shop['offerCount']; ?>
|
||||
</span>
|
||||
</td>
|
||||
<td style="padding: 1rem 0.5rem; text-align: right; vertical-align: middle;">
|
||||
<?php if ($shop['offerCount'] > 0): ?>
|
||||
<!-- @brief Deaktivierter Löschen-Button, falls Angebote existieren -->
|
||||
<button disabled style="background: none; border: none; color: #ccc; cursor: not-allowed; display: inline-flex; align-items: center; justify-content: flex-end; padding: 0.5rem;" title="Shop kann nicht gelöscht werden, da er noch Angebote hat">
|
||||
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path d="M19 6V20a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<!-- @brief Aktives Formular mit Lösch-Button, wenn keine Angebote verknüpft sind -->
|
||||
<form method="POST" action="shopAdder.php" onsubmit="return confirm('Möchtest du diesen Shop wirklich löschen?');" style="display: inline-block; margin: 0;">
|
||||
<input type="hidden" name="action" value="delete_shop">
|
||||
<input type="hidden" name="shop_id" value="<?php echo $shop['shopID']; ?>">
|
||||
<button type="submit" style="background: none; border: none; color: #ef4444; cursor: pointer; display: inline-flex; align-items: center; justify-content: flex-end; padding: 0.5rem; border-radius: 4px;" title="Shop löschen" onmouseover="this.style.backgroundColor='#fee2e2';" onmouseout="this.style.backgroundColor='transparent';">
|
||||
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path d="M19 6V20a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<!-- @brief Hinweis, wenn noch keine Shops in der Datenbank vorhanden sind -->
|
||||
<p style="margin-top: 1rem; color: var(--text-muted); text-align: center; padding: 2rem 0;">Keine Shops vorhanden.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<?php
|
||||
/**
|
||||
* @brief Einbinden des globalen Footers
|
||||
*/
|
||||
include 'footer.php';
|
||||
?>
|
||||
Loading…
Reference in New Issue
Block a user