485 lines
14 KiB
PHP
485 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* Copyright seit 2024 Webshop System
|
|
*
|
|
* Shop Controller für das Admin-Bereich
|
|
*
|
|
* @author Webshop System
|
|
* @license GPL v3
|
|
*/
|
|
|
|
namespace App\Controllers\Admin;
|
|
|
|
use App\Core\MultiShop;
|
|
use App\Core\SessionHandler;
|
|
use App\Core\Configuration;
|
|
use Doctrine\DBAL\DriverManager;
|
|
use Doctrine\DBAL\Exception;
|
|
|
|
class ShopController extends AdminController
|
|
{
|
|
private $multiShop;
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->multiShop = new MultiShop();
|
|
}
|
|
|
|
/**
|
|
* Shop-Übersicht
|
|
*/
|
|
public function index()
|
|
{
|
|
if (!$this->checkAdminSession()) {
|
|
$this->redirect('/admin/login');
|
|
}
|
|
|
|
$shops = $this->multiShop->getAllShops();
|
|
$currentShop = $this->multiShop->getCurrentShop();
|
|
|
|
// Statistiken für jeden Shop
|
|
$shopStats = [];
|
|
foreach ($shops as $shop) {
|
|
$shopStats[$shop['id']] = $this->multiShop->getShopStatistics($shop['id']);
|
|
}
|
|
|
|
$this->render('admin/shop/index.html.twig', [
|
|
'title' => 'Shop-Verwaltung',
|
|
'shops' => $shops,
|
|
'current_shop' => $currentShop,
|
|
'shop_stats' => $shopStats
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Shop erstellen
|
|
*/
|
|
public function create()
|
|
{
|
|
if (!$this->checkAdminSession()) {
|
|
$this->redirect('/admin/login');
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$this->handleShopCreate();
|
|
}
|
|
|
|
$this->render('admin/shop/create.html.twig', [
|
|
'title' => 'Neuen Shop erstellen'
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Shop bearbeiten
|
|
*/
|
|
public function edit($id)
|
|
{
|
|
if (!$this->checkAdminSession()) {
|
|
$this->redirect('/admin/login');
|
|
}
|
|
|
|
$shop = $this->multiShop->getShop($id);
|
|
|
|
if (!$shop) {
|
|
$this->addFlashMessage('Shop nicht gefunden', 'error');
|
|
$this->redirect('/admin/shop');
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$this->handleShopUpdate($id);
|
|
}
|
|
|
|
$shopConfig = $this->getShopConfiguration($id);
|
|
|
|
$this->render('admin/shop/edit.html.twig', [
|
|
'title' => 'Shop bearbeiten: ' . $shop['name'],
|
|
'shop' => $shop,
|
|
'shop_config' => $shopConfig
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Shop löschen
|
|
*/
|
|
public function delete($id)
|
|
{
|
|
if (!$this->checkAdminSession()) {
|
|
$this->redirect('/admin/login');
|
|
}
|
|
|
|
$shop = $this->multiShop->getShop($id);
|
|
|
|
if (!$shop) {
|
|
$this->addFlashMessage('Shop nicht gefunden', 'error');
|
|
$this->redirect('/admin/shop');
|
|
}
|
|
|
|
if ($shop['is_default']) {
|
|
$this->addFlashMessage('Standard-Shop kann nicht gelöscht werden', 'error');
|
|
$this->redirect('/admin/shop');
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
if ($this->multiShop->deleteShop($id)) {
|
|
$this->addFlashMessage('Shop erfolgreich gelöscht', 'success');
|
|
} else {
|
|
$this->addFlashMessage('Fehler beim Löschen des Shops', 'error');
|
|
}
|
|
$this->redirect('/admin/shop');
|
|
}
|
|
|
|
$this->render('admin/shop/delete.html.twig', [
|
|
'title' => 'Shop löschen: ' . $shop['name'],
|
|
'shop' => $shop
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Shop-Konfiguration
|
|
*/
|
|
public function config($id)
|
|
{
|
|
if (!$this->checkAdminSession()) {
|
|
$this->redirect('/admin/login');
|
|
}
|
|
|
|
$shop = $this->multiShop->getShop($id);
|
|
|
|
if (!$shop) {
|
|
$this->addFlashMessage('Shop nicht gefunden', 'error');
|
|
$this->redirect('/admin/shop');
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$this->handleConfigUpdate($id);
|
|
}
|
|
|
|
$shopConfig = $this->getShopConfiguration($id);
|
|
$availableCurrencies = $this->getAvailableCurrencies();
|
|
$availableLanguages = $this->getAvailableLanguages();
|
|
$availableCountries = $this->getAvailableCountries();
|
|
|
|
$this->render('admin/shop/config.html.twig', [
|
|
'title' => 'Shop-Konfiguration: ' . $shop['name'],
|
|
'shop' => $shop,
|
|
'shop_config' => $shopConfig,
|
|
'currencies' => $availableCurrencies,
|
|
'languages' => $availableLanguages,
|
|
'countries' => $availableCountries
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Shop-Statistiken
|
|
*/
|
|
public function statistics($id)
|
|
{
|
|
if (!$this->checkAdminSession()) {
|
|
$this->redirect('/admin/login');
|
|
}
|
|
|
|
$shop = $this->multiShop->getShop($id);
|
|
|
|
if (!$shop) {
|
|
$this->addFlashMessage('Shop nicht gefunden', 'error');
|
|
$this->redirect('/admin/shop');
|
|
}
|
|
|
|
$statistics = $this->multiShop->getShopStatistics($id);
|
|
$recentOrders = $this->getRecentOrders($id);
|
|
$topProducts = $this->getTopProducts($id);
|
|
$customerGrowth = $this->getCustomerGrowth($id);
|
|
|
|
$this->render('admin/shop/statistics.html.twig', [
|
|
'title' => 'Shop-Statistiken: ' . $shop['name'],
|
|
'shop' => $shop,
|
|
'statistics' => $statistics,
|
|
'recent_orders' => $recentOrders,
|
|
'top_products' => $topProducts,
|
|
'customer_growth' => $customerGrowth
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Shop-Wechsel
|
|
*/
|
|
public function switch($id)
|
|
{
|
|
if (!$this->checkAdminSession()) {
|
|
$this->redirect('/admin/login');
|
|
}
|
|
|
|
if ($this->multiShop->switchShop($id)) {
|
|
$this->addFlashMessage('Shop gewechselt', 'success');
|
|
} else {
|
|
$this->addFlashMessage('Fehler beim Shop-Wechsel', 'error');
|
|
}
|
|
|
|
$this->redirect('/admin/shop');
|
|
}
|
|
|
|
/**
|
|
* Shop erstellen verarbeiten
|
|
*/
|
|
private function handleShopCreate()
|
|
{
|
|
$data = [
|
|
'name' => $_POST['name'] ?? '',
|
|
'description' => $_POST['description'] ?? '',
|
|
'domain' => $_POST['domain'] ?? '',
|
|
'ssl_enabled' => isset($_POST['ssl_enabled']),
|
|
'force_ssl' => isset($_POST['force_ssl']),
|
|
'active' => isset($_POST['active']),
|
|
'sort_order' => intval($_POST['sort_order'] ?? 0)
|
|
];
|
|
|
|
// Validierung
|
|
if (empty($data['name'])) {
|
|
$this->addFlashMessage('Shop-Name ist erforderlich', 'error');
|
|
return;
|
|
}
|
|
|
|
if (!empty($data['domain']) && !$this->multiShop->validateShopDomain($data['domain'])) {
|
|
$this->addFlashMessage('Domain ist bereits vergeben oder ungültig', 'error');
|
|
return;
|
|
}
|
|
|
|
$shopId = $this->multiShop->createShop($data);
|
|
|
|
if ($shopId) {
|
|
$this->addFlashMessage('Shop erfolgreich erstellt', 'success');
|
|
$this->redirect('/admin/shop');
|
|
} else {
|
|
$this->addFlashMessage('Fehler beim Erstellen des Shops', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shop aktualisieren verarbeiten
|
|
*/
|
|
private function handleShopUpdate($id)
|
|
{
|
|
$data = [
|
|
'name' => $_POST['name'] ?? '',
|
|
'description' => $_POST['description'] ?? '',
|
|
'domain' => $_POST['domain'] ?? '',
|
|
'ssl_enabled' => isset($_POST['ssl_enabled']),
|
|
'force_ssl' => isset($_POST['force_ssl']),
|
|
'active' => isset($_POST['active']),
|
|
'sort_order' => intval($_POST['sort_order'] ?? 0)
|
|
];
|
|
|
|
// Validierung
|
|
if (empty($data['name'])) {
|
|
$this->addFlashMessage('Shop-Name ist erforderlich', 'error');
|
|
return;
|
|
}
|
|
|
|
if (!empty($data['domain']) && !$this->multiShop->validateShopDomain($data['domain'], $id)) {
|
|
$this->addFlashMessage('Domain ist bereits vergeben oder ungültig', 'error');
|
|
return;
|
|
}
|
|
|
|
if ($this->multiShop->updateShop($id, $data)) {
|
|
$this->addFlashMessage('Shop erfolgreich aktualisiert', 'success');
|
|
$this->redirect('/admin/shop');
|
|
} else {
|
|
$this->addFlashMessage('Fehler beim Aktualisieren des Shops', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Konfiguration aktualisieren
|
|
*/
|
|
private function handleConfigUpdate($id)
|
|
{
|
|
$configKeys = [
|
|
'SHOP_NAME', 'SHOP_DESCRIPTION', 'SHOP_EMAIL', 'SHOP_PHONE',
|
|
'SHOP_ADDRESS', 'SHOP_CITY', 'SHOP_POSTAL_CODE', 'SHOP_COUNTRY',
|
|
'SHOP_CURRENCY', 'SHOP_LANGUAGE', 'SHOP_TIMEZONE',
|
|
'SHOP_DATE_FORMAT', 'SHOP_TIME_FORMAT', 'SHOP_TAX_RATE',
|
|
'SHOP_SHIPPING_COST', 'SHOP_FREE_SHIPPING_THRESHOLD',
|
|
'SHOP_MIN_ORDER_AMOUNT', 'SHOP_MAX_ORDER_AMOUNT',
|
|
'SHOP_STOCK_WARNING', 'SHOP_REVIEWS_ENABLED',
|
|
'SHOP_NEWSLETTER_ENABLED', 'SHOP_MAINTENANCE_MODE',
|
|
'SHOP_MAINTENANCE_MESSAGE'
|
|
];
|
|
|
|
foreach ($configKeys as $key) {
|
|
$value = $_POST[strtolower($key)] ?? '';
|
|
$this->multiShop->setShopConfig($key, $value);
|
|
}
|
|
|
|
$this->addFlashMessage('Shop-Konfiguration aktualisiert', 'success');
|
|
$this->redirect("/admin/shop/config/$id");
|
|
}
|
|
|
|
/**
|
|
* Shop-Konfiguration abrufen
|
|
*/
|
|
private function getShopConfiguration($shopId)
|
|
{
|
|
$config = [];
|
|
$configKeys = [
|
|
'SHOP_NAME', 'SHOP_DESCRIPTION', 'SHOP_EMAIL', 'SHOP_PHONE',
|
|
'SHOP_ADDRESS', 'SHOP_CITY', 'SHOP_POSTAL_CODE', 'SHOP_COUNTRY',
|
|
'SHOP_CURRENCY', 'SHOP_LANGUAGE', 'SHOP_TIMEZONE',
|
|
'SHOP_DATE_FORMAT', 'SHOP_TIME_FORMAT', 'SHOP_TAX_RATE',
|
|
'SHOP_SHIPPING_COST', 'SHOP_FREE_SHIPPING_THRESHOLD',
|
|
'SHOP_MIN_ORDER_AMOUNT', 'SHOP_MAX_ORDER_AMOUNT',
|
|
'SHOP_STOCK_WARNING', 'SHOP_REVIEWS_ENABLED',
|
|
'SHOP_NEWSLETTER_ENABLED', 'SHOP_MAINTENANCE_MODE',
|
|
'SHOP_MAINTENANCE_MESSAGE'
|
|
];
|
|
|
|
foreach ($configKeys as $key) {
|
|
$config[strtolower($key)] = $this->multiShop->getShopConfig($key, '');
|
|
}
|
|
|
|
return $config;
|
|
}
|
|
|
|
/**
|
|
* Verfügbare Währungen
|
|
*/
|
|
private function getAvailableCurrencies()
|
|
{
|
|
return [
|
|
'EUR' => 'Euro (€)',
|
|
'USD' => 'US Dollar ($)',
|
|
'GBP' => 'British Pound (£)',
|
|
'CHF' => 'Swiss Franc (CHF)',
|
|
'JPY' => 'Japanese Yen (¥)',
|
|
'CAD' => 'Canadian Dollar (C$)',
|
|
'AUD' => 'Australian Dollar (A$)'
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Verfügbare Sprachen
|
|
*/
|
|
private function getAvailableLanguages()
|
|
{
|
|
return [
|
|
'de' => 'Deutsch',
|
|
'en' => 'English',
|
|
'fr' => 'Français',
|
|
'es' => 'Español',
|
|
'it' => 'Italiano',
|
|
'nl' => 'Nederlands',
|
|
'pl' => 'Polski',
|
|
'ru' => 'Русский'
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Verfügbare Länder
|
|
*/
|
|
private function getAvailableCountries()
|
|
{
|
|
try {
|
|
$stmt = $this->conn->prepare('
|
|
SELECT code, name FROM ws_country
|
|
WHERE active = 1
|
|
ORDER BY name ASC
|
|
');
|
|
$stmt->execute();
|
|
$countries = $stmt->fetchAllAssociative();
|
|
|
|
$result = [];
|
|
foreach ($countries as $country) {
|
|
$result[$country['code']] = $country['name'];
|
|
}
|
|
|
|
return $result;
|
|
|
|
} catch (Exception $e) {
|
|
return [
|
|
'DE' => 'Deutschland',
|
|
'AT' => 'Österreich',
|
|
'CH' => 'Schweiz',
|
|
'US' => 'United States',
|
|
'GB' => 'United Kingdom',
|
|
'FR' => 'France',
|
|
'IT' => 'Italy',
|
|
'ES' => 'Spain'
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Letzte Bestellungen
|
|
*/
|
|
private function getRecentOrders($shopId)
|
|
{
|
|
try {
|
|
$stmt = $this->conn->prepare('
|
|
SELECT o.*, c.first_name, c.last_name
|
|
FROM ws_order o
|
|
LEFT JOIN ws_customer c ON o.customer_id = c.id
|
|
WHERE o.shop_id = ?
|
|
ORDER BY o.created_at DESC
|
|
LIMIT 10
|
|
');
|
|
$stmt->execute([$shopId]);
|
|
|
|
return $stmt->fetchAllAssociative();
|
|
|
|
} catch (Exception $e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Top-Produkte
|
|
*/
|
|
private function getTopProducts($shopId)
|
|
{
|
|
try {
|
|
$stmt = $this->conn->prepare('
|
|
SELECT p.*,
|
|
COUNT(oi.id) as order_count,
|
|
SUM(oi.quantity) as total_quantity,
|
|
SUM(oi.price * oi.quantity) as total_revenue
|
|
FROM ws_product p
|
|
LEFT JOIN ws_order_item oi ON p.id = oi.product_id
|
|
LEFT JOIN ws_order o ON oi.order_id = o.id
|
|
WHERE p.shop_id = ? AND o.status = "completed"
|
|
GROUP BY p.id
|
|
ORDER BY total_revenue DESC
|
|
LIMIT 10
|
|
');
|
|
$stmt->execute([$shopId]);
|
|
|
|
return $stmt->fetchAllAssociative();
|
|
|
|
} catch (Exception $e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Kundenwachstum
|
|
*/
|
|
private function getCustomerGrowth($shopId)
|
|
{
|
|
try {
|
|
$stmt = $this->conn->prepare('
|
|
SELECT DATE(created_at) as date,
|
|
COUNT(*) as new_customers
|
|
FROM ws_customer
|
|
WHERE shop_id = ?
|
|
AND created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
|
|
GROUP BY DATE(created_at)
|
|
ORDER BY date ASC
|
|
');
|
|
$stmt->execute([$shopId]);
|
|
|
|
return $stmt->fetchAllAssociative();
|
|
|
|
} catch (Exception $e) {
|
|
return [];
|
|
}
|
|
}
|
|
}
|