Newwebshop/app/Core/MultiShop.php

575 lines
18 KiB
PHP

<?php
/**
* Copyright seit 2024 Webshop System
*
* MultiShop Core-Klasse für das Webshop-System
*
* @author Webshop System
* @license GPL v3
*/
namespace App\Core;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception;
class MultiShop
{
private $conn;
private $config;
private $currentShop;
private $shops = [];
private $defaultShop;
public function __construct()
{
$this->conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$this->config = new Configuration();
$this->loadShops();
$this->detectCurrentShop();
}
/**
* Alle Shops laden
*/
private function loadShops()
{
try {
$stmt = $this->conn->prepare('
SELECT s.*, d.domain, d.ssl_enabled, d.force_ssl
FROM ws_shop s
LEFT JOIN ws_shop_domain d ON s.id = d.shop_id
WHERE s.active = 1
ORDER BY s.sort_order ASC, s.name ASC
');
$stmt->execute();
$shops = $stmt->fetchAllAssociative();
foreach ($shops as $shop) {
$this->shops[$shop['id']] = $shop;
if ($shop['is_default']) {
$this->defaultShop = $shop;
}
}
} catch (Exception $e) {
error_log('Error loading shops: ' . $e->getMessage());
}
}
/**
* Aktuellen Shop basierend auf Domain erkennen
*/
private function detectCurrentShop()
{
$host = $_SERVER['HTTP_HOST'] ?? '';
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
// Shop-ID aus URL-Parameter
if (preg_match('/\/shop\/(\d+)/', $requestUri, $matches)) {
$shopId = intval($matches[1]);
if (isset($this->shops[$shopId])) {
$this->currentShop = $this->shops[$shopId];
return;
}
}
// Shop basierend auf Domain
foreach ($this->shops as $shop) {
if ($shop['domain'] && $host === $shop['domain']) {
$this->currentShop = $shop;
return;
}
}
// Fallback auf Standard-Shop
$this->currentShop = $this->defaultShop ?? reset($this->shops);
}
/**
* Aktuellen Shop abrufen
*/
public function getCurrentShop()
{
return $this->currentShop;
}
/**
* Shop-ID abrufen
*/
public function getCurrentShopId()
{
return $this->currentShop['id'] ?? null;
}
/**
* Alle Shops abrufen
*/
public function getAllShops()
{
return $this->shops;
}
/**
* Shop nach ID abrufen
*/
public function getShop($id)
{
return $this->shops[$id] ?? null;
}
/**
* Shop nach Domain abrufen
*/
public function getShopByDomain($domain)
{
foreach ($this->shops as $shop) {
if ($shop['domain'] === $domain) {
return $shop;
}
}
return null;
}
/**
* Shop-Konfiguration abrufen
*/
public function getShopConfig($key, $default = null)
{
if (!$this->currentShop) {
return $default;
}
try {
$stmt = $this->conn->prepare('
SELECT value FROM ws_shop_config
WHERE shop_id = ? AND config_key = ?
');
$stmt->execute([$this->currentShop['id'], $key]);
$result = $stmt->fetchAssociative();
return $result ? $result['value'] : $default;
} catch (Exception $e) {
error_log('Error getting shop config: ' . $e->getMessage());
return $default;
}
}
/**
* Shop-Konfiguration setzen
*/
public function setShopConfig($key, $value)
{
if (!$this->currentShop) {
return false;
}
try {
$stmt = $this->conn->prepare('
INSERT INTO ws_shop_config (shop_id, config_key, value, updated_at)
VALUES (?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = NOW()
');
$stmt->execute([$this->currentShop['id'], $key, $value]);
return true;
} catch (Exception $e) {
error_log('Error setting shop config: ' . $e->getMessage());
return false;
}
}
/**
* Shop erstellen
*/
public function createShop($data)
{
try {
$this->conn->beginTransaction();
// Shop erstellen
$stmt = $this->conn->prepare('
INSERT INTO ws_shop (name, description, active, is_default, sort_order, created_at)
VALUES (?, ?, ?, ?, ?, NOW())
');
$stmt->execute([
$data['name'],
$data['description'] ?? '',
$data['active'] ?? true,
$data['is_default'] ?? false,
$data['sort_order'] ?? 0
]);
$shopId = $this->conn->lastInsertId();
// Domain hinzufügen
if (!empty($data['domain'])) {
$stmt = $this->conn->prepare('
INSERT INTO ws_shop_domain (shop_id, domain, ssl_enabled, force_ssl, created_at)
VALUES (?, ?, ?, ?, NOW())
');
$stmt->execute([
$shopId,
$data['domain'],
$data['ssl_enabled'] ?? false,
$data['force_ssl'] ?? false
]);
}
// Standard-Konfiguration kopieren
$this->copyDefaultConfig($shopId);
$this->conn->commit();
// Shops neu laden
$this->loadShops();
return $shopId;
} catch (Exception $e) {
$this->conn->rollBack();
error_log('Error creating shop: ' . $e->getMessage());
return false;
}
}
/**
* Shop aktualisieren
*/
public function updateShop($id, $data)
{
try {
$this->conn->beginTransaction();
// Shop aktualisieren
$stmt = $this->conn->prepare('
UPDATE ws_shop
SET name = ?, description = ?, active = ?, sort_order = ?, updated_at = NOW()
WHERE id = ?
');
$stmt->execute([
$data['name'],
$data['description'] ?? '',
$data['active'] ?? true,
$data['sort_order'] ?? 0,
$id
]);
// Domain aktualisieren
if (isset($data['domain'])) {
$stmt = $this->conn->prepare('
INSERT INTO ws_shop_domain (shop_id, domain, ssl_enabled, force_ssl, created_at)
VALUES (?, ?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE
domain = VALUES(domain),
ssl_enabled = VALUES(ssl_enabled),
force_ssl = VALUES(force_ssl),
updated_at = NOW()
');
$stmt->execute([
$id,
$data['domain'],
$data['ssl_enabled'] ?? false,
$data['force_ssl'] ?? false
]);
}
$this->conn->commit();
// Shops neu laden
$this->loadShops();
return true;
} catch (Exception $e) {
$this->conn->rollBack();
error_log('Error updating shop: ' . $e->getMessage());
return false;
}
}
/**
* Shop löschen
*/
public function deleteShop($id)
{
try {
$this->conn->beginTransaction();
// Prüfen ob Shop Daten hat
$stmt = $this->conn->prepare('
SELECT COUNT(*) as count FROM ws_order WHERE shop_id = ?
');
$stmt->execute([$id]);
$orderCount = $stmt->fetchAssociative()['count'];
if ($orderCount > 0) {
throw new Exception('Shop kann nicht gelöscht werden - hat Bestellungen');
}
// Shop-Daten löschen
$tables = [
'ws_shop_config',
'ws_shop_domain',
'ws_shop_currency',
'ws_shop_language'
];
foreach ($tables as $table) {
$stmt = $this->conn->prepare("DELETE FROM $table WHERE shop_id = ?");
$stmt->execute([$id]);
}
// Shop löschen
$stmt = $this->conn->prepare('DELETE FROM ws_shop WHERE id = ?');
$stmt->execute([$id]);
$this->conn->commit();
// Shops neu laden
$this->loadShops();
return true;
} catch (Exception $e) {
$this->conn->rollBack();
error_log('Error deleting shop: ' . $e->getMessage());
return false;
}
}
/**
* Standard-Konfiguration kopieren
*/
private function copyDefaultConfig($shopId)
{
$defaultConfigs = [
'SHOP_NAME' => 'Neuer Shop',
'SHOP_DESCRIPTION' => 'Beschreibung des Shops',
'SHOP_EMAIL' => 'info@example.com',
'SHOP_PHONE' => '',
'SHOP_ADDRESS' => '',
'SHOP_CITY' => '',
'SHOP_POSTAL_CODE' => '',
'SHOP_COUNTRY' => 'DE',
'SHOP_CURRENCY' => 'EUR',
'SHOP_LANGUAGE' => 'de',
'SHOP_TIMEZONE' => 'Europe/Berlin',
'SHOP_DATE_FORMAT' => 'd.m.Y',
'SHOP_TIME_FORMAT' => 'H:i',
'SHOP_TAX_RATE' => '19.00',
'SHOP_SHIPPING_COST' => '5.90',
'SHOP_FREE_SHIPPING_THRESHOLD' => '50.00',
'SHOP_MIN_ORDER_AMOUNT' => '0.00',
'SHOP_MAX_ORDER_AMOUNT' => '0.00',
'SHOP_STOCK_WARNING' => '5',
'SHOP_REVIEWS_ENABLED' => '1',
'SHOP_NEWSLETTER_ENABLED' => '1',
'SHOP_MAINTENANCE_MODE' => '0',
'SHOP_MAINTENANCE_MESSAGE' => 'Shop ist zurzeit nicht verfügbar'
];
foreach ($defaultConfigs as $key => $value) {
$this->setShopConfig($key, $value);
}
}
/**
* Shop-Statistiken abrufen
*/
public function getShopStatistics($shopId = null)
{
$shopId = $shopId ?: $this->getCurrentShopId();
if (!$shopId) {
return [];
}
try {
$stats = [];
// Bestellungen
$stmt = $this->conn->prepare('
SELECT COUNT(*) as total_orders,
SUM(total_amount) as total_revenue,
COUNT(CASE WHEN status = "completed" THEN 1 END) as completed_orders,
COUNT(CASE WHEN status = "pending" THEN 1 END) as pending_orders
FROM ws_order
WHERE shop_id = ?
');
$stmt->execute([$shopId]);
$orderStats = $stmt->fetchAssociative();
$stats['orders'] = $orderStats;
// Produkte
$stmt = $this->conn->prepare('
SELECT COUNT(*) as total_products,
COUNT(CASE WHEN active = 1 THEN 1 END) as active_products,
COUNT(CASE WHEN stock <= 0 THEN 1 END) as out_of_stock
FROM ws_product
WHERE shop_id = ?
');
$stmt->execute([$shopId]);
$productStats = $stmt->fetchAssociative();
$stats['products'] = $productStats;
// Kunden
$stmt = $this->conn->prepare('
SELECT COUNT(*) as total_customers,
COUNT(CASE WHEN created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY) THEN 1 END) as new_customers_30d
FROM ws_customer
WHERE shop_id = ?
');
$stmt->execute([$shopId]);
$customerStats = $stmt->fetchAssociative();
$stats['customers'] = $customerStats;
// Bewertungen
$stmt = $this->conn->prepare('
SELECT COUNT(*) as total_reviews,
AVG(rating) as avg_rating
FROM ws_review r
JOIN ws_product p ON r.product_id = p.id
WHERE p.shop_id = ?
');
$stmt->execute([$shopId]);
$reviewStats = $stmt->fetchAssociative();
$stats['reviews'] = $reviewStats;
return $stats;
} catch (Exception $e) {
error_log('Error getting shop statistics: ' . $e->getMessage());
return [];
}
}
/**
* Shop-Domain validieren
*/
public function validateShopDomain($domain, $excludeShopId = null)
{
if (empty($domain)) {
return true; // Leere Domain ist erlaubt
}
// Domain-Format prüfen
if (!filter_var('http://' . $domain, FILTER_VALIDATE_URL)) {
return false;
}
// Prüfen ob Domain bereits verwendet wird
try {
$stmt = $this->conn->prepare('
SELECT shop_id FROM ws_shop_domain
WHERE domain = ? AND shop_id != ?
');
$stmt->execute([$domain, $excludeShopId]);
return $stmt->rowCount() === 0;
} catch (Exception $e) {
error_log('Error validating shop domain: ' . $e->getMessage());
return false;
}
}
/**
* Shop-URL generieren
*/
public function getShopUrl($shopId = null, $path = '')
{
$shop = $shopId ? $this->getShop($shopId) : $this->currentShop;
if (!$shop) {
return '/';
}
$baseUrl = $shop['domain'] ? 'https://' . $shop['domain'] : '';
$shopPath = $shop['is_default'] ? '' : '/shop/' . $shop['id'];
return $baseUrl . $shopPath . $path;
}
/**
* Shop-Wechsel
*/
public function switchShop($shopId)
{
if (isset($this->shops[$shopId])) {
$this->currentShop = $this->shops[$shopId];
return true;
}
return false;
}
/**
* Shop-Kontext für Datenbank-Queries
*/
public function getShopContext()
{
return [
'shop_id' => $this->getCurrentShopId(),
'shop_name' => $this->currentShop['name'] ?? '',
'shop_domain' => $this->currentShop['domain'] ?? '',
'is_default' => $this->currentShop['is_default'] ?? false
];
}
/**
* Shop-spezifische Datenbank-Query
*/
public function addShopFilter($query, $shopId = null)
{
$shopId = $shopId ?: $this->getCurrentShopId();
if (!$shopId) {
return $query;
}
// Prüfen ob Query bereits WHERE hat
if (stripos($query, 'WHERE') !== false) {
return str_replace('WHERE', "WHERE shop_id = $shopId AND", $query);
} else {
return $query . " WHERE shop_id = $shopId";
}
}
/**
* Shop-Konfiguration für Template
*/
public function getShopTemplateConfig()
{
return [
'shop_name' => $this->getShopConfig('SHOP_NAME', 'Webshop'),
'shop_description' => $this->getShopConfig('SHOP_DESCRIPTION', ''),
'shop_email' => $this->getShopConfig('SHOP_EMAIL', ''),
'shop_phone' => $this->getShopConfig('SHOP_PHONE', ''),
'shop_address' => $this->getShopConfig('SHOP_ADDRESS', ''),
'shop_city' => $this->getShopConfig('SHOP_CITY', ''),
'shop_postal_code' => $this->getShopConfig('SHOP_POSTAL_CODE', ''),
'shop_country' => $this->getShopConfig('SHOP_COUNTRY', 'DE'),
'shop_currency' => $this->getShopConfig('SHOP_CURRENCY', 'EUR'),
'shop_language' => $this->getShopConfig('SHOP_LANGUAGE', 'de'),
'shop_timezone' => $this->getShopConfig('SHOP_TIMEZONE', 'Europe/Berlin'),
'shop_date_format' => $this->getShopConfig('SHOP_DATE_FORMAT', 'd.m.Y'),
'shop_time_format' => $this->getShopConfig('SHOP_TIME_FORMAT', 'H:i'),
'shop_tax_rate' => floatval($this->getShopConfig('SHOP_TAX_RATE', '19.00')),
'shop_shipping_cost' => floatval($this->getShopConfig('SHOP_SHIPPING_COST', '5.90')),
'shop_free_shipping_threshold' => floatval($this->getShopConfig('SHOP_FREE_SHIPPING_THRESHOLD', '50.00')),
'shop_min_order_amount' => floatval($this->getShopConfig('SHOP_MIN_ORDER_AMOUNT', '0.00')),
'shop_max_order_amount' => floatval($this->getShopConfig('SHOP_MAX_ORDER_AMOUNT', '0.00')),
'shop_stock_warning' => intval($this->getShopConfig('SHOP_STOCK_WARNING', '5')),
'shop_reviews_enabled' => boolval($this->getShopConfig('SHOP_REVIEWS_ENABLED', '1')),
'shop_newsletter_enabled' => boolval($this->getShopConfig('SHOP_NEWSLETTER_ENABLED', '1')),
'shop_maintenance_mode' => boolval($this->getShopConfig('SHOP_MAINTENANCE_MODE', '0')),
'shop_maintenance_message' => $this->getShopConfig('SHOP_MAINTENANCE_MESSAGE', 'Shop ist zurzeit nicht verfügbar')
];
}
}