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') ]; } }