functions.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. <?php
  2. require_once __DIR__ . '/../config.php';
  3. /**
  4. * Read JSON file and return decoded data
  5. */
  6. function readJsonFile($file) {
  7. if (!file_exists($file)) {
  8. return [];
  9. }
  10. $content = file_get_contents($file);
  11. if (empty($content)) {
  12. return [];
  13. }
  14. $data = json_decode($content, true);
  15. return $data ? $data : [];
  16. }
  17. /**
  18. * Write data to JSON file
  19. */
  20. function writeJsonFile($file, $data) {
  21. $dir = dirname($file);
  22. if (!is_dir($dir)) {
  23. mkdir($dir, 0755, true);
  24. }
  25. file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
  26. }
  27. /**
  28. * Get all products
  29. */
  30. function getProducts() {
  31. $data = readJsonFile(PRODUCTS_FILE);
  32. return isset($data['products']) ? $data['products'] : [];
  33. }
  34. /**
  35. * Get product by ID
  36. */
  37. function getProductById($id) {
  38. $products = getProducts();
  39. foreach ($products as $product) {
  40. if ($product['id'] == $id) {
  41. return $product;
  42. }
  43. }
  44. return null;
  45. }
  46. /**
  47. * Save products
  48. */
  49. function saveProducts($products) {
  50. $data = ['products' => $products];
  51. writeJsonFile(PRODUCTS_FILE, $data);
  52. }
  53. /**
  54. * Get all reservations
  55. */
  56. function getReservations() {
  57. $data = readJsonFile(RESERVATIONS_FILE);
  58. return isset($data['reservations']) ? $data['reservations'] : [];
  59. }
  60. /**
  61. * Get reservation by code
  62. */
  63. function getReservationByCode($code) {
  64. $reservations = getReservations();
  65. foreach ($reservations as $reservation) {
  66. if ($reservation['code'] === $code) {
  67. return $reservation;
  68. }
  69. }
  70. return null;
  71. }
  72. /**
  73. * Save reservations
  74. */
  75. function saveReservations($reservations) {
  76. $data = ['reservations' => $reservations];
  77. writeJsonFile(RESERVATIONS_FILE, $data);
  78. }
  79. /**
  80. * Generate unique reservation code
  81. */
  82. function generateReservationCode() {
  83. $code = '';
  84. $characters = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Exclude confusing characters
  85. $max = strlen($characters) - 1;
  86. do {
  87. $code = '';
  88. for ($i = 0; $i < 6; $i++) {
  89. $code .= $characters[random_int(0, $max)];
  90. }
  91. // Check if code already exists
  92. $existing = getReservationByCode($code);
  93. } while ($existing !== null);
  94. return $code;
  95. }
  96. /**
  97. * Generate reservation ID
  98. */
  99. function generateReservationId() {
  100. $reservations = getReservations();
  101. $count = count($reservations) + 1;
  102. $year = date('Y');
  103. return sprintf('RES-%s-%03d', $year, $count);
  104. }
  105. /**
  106. * Check if product has enough stock
  107. * For apparel: checks stock for specific size
  108. * For merch: checks general stock
  109. */
  110. function checkStock($productId, $quantity, $size = null) {
  111. $product = getProductById($productId);
  112. if (!$product) {
  113. return false;
  114. }
  115. // For apparel with sizes, check stock per size
  116. if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) {
  117. if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) {
  118. return false;
  119. }
  120. $sizeStock = isset($product['stock_by_size'][$size]) ? (int)$product['stock_by_size'][$size] : 0;
  121. return $sizeStock >= $quantity;
  122. }
  123. // For merch or apparel without size-specific stock, use general stock
  124. $stock = isset($product['stock']) ? (int)$product['stock'] : 0;
  125. return $stock >= $quantity;
  126. }
  127. /**
  128. * Get stock for a product (per size for apparel, general for merch)
  129. */
  130. function getStock($product, $size = null) {
  131. if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) {
  132. if (isset($product['stock_by_size']) && is_array($product['stock_by_size'])) {
  133. return isset($product['stock_by_size'][$size]) ? (int)$product['stock_by_size'][$size] : 0;
  134. }
  135. }
  136. return isset($product['stock']) ? (int)$product['stock'] : 0;
  137. }
  138. /**
  139. * Get total stock for a product (sum of all sizes for apparel)
  140. */
  141. function getTotalStock($product) {
  142. if ($product['category'] === 'apparel' && isset($product['stock_by_size']) && is_array($product['stock_by_size'])) {
  143. return array_sum($product['stock_by_size']);
  144. }
  145. return isset($product['stock']) ? (int)$product['stock'] : 0;
  146. }
  147. /**
  148. * Allocate stock for reservation
  149. */
  150. function allocateStock($productId, $quantity, $size = null) {
  151. $products = getProducts();
  152. foreach ($products as &$product) {
  153. if ($product['id'] == $productId) {
  154. // For apparel with sizes, allocate per size
  155. if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) {
  156. if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) {
  157. $product['stock_by_size'] = [];
  158. }
  159. if (!isset($product['stock_by_size'][$size])) {
  160. $product['stock_by_size'][$size] = 0;
  161. }
  162. $product['stock_by_size'][$size] -= $quantity;
  163. if ($product['stock_by_size'][$size] < 0) {
  164. $product['stock_by_size'][$size] = 0;
  165. }
  166. } else {
  167. // For merch or general stock
  168. if (!isset($product['stock'])) {
  169. $product['stock'] = 0;
  170. }
  171. $product['stock'] -= $quantity;
  172. if ($product['stock'] < 0) {
  173. $product['stock'] = 0;
  174. }
  175. }
  176. break;
  177. }
  178. }
  179. saveProducts($products);
  180. }
  181. /**
  182. * Release stock from reservation
  183. */
  184. function releaseStock($productId, $quantity, $size = null) {
  185. $products = getProducts();
  186. foreach ($products as &$product) {
  187. if ($product['id'] == $productId) {
  188. // For apparel with sizes, release per size
  189. if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) {
  190. if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) {
  191. $product['stock_by_size'] = [];
  192. }
  193. if (!isset($product['stock_by_size'][$size])) {
  194. $product['stock_by_size'][$size] = 0;
  195. }
  196. $product['stock_by_size'][$size] += $quantity;
  197. } else {
  198. // For merch or general stock
  199. if (!isset($product['stock'])) {
  200. $product['stock'] = 0;
  201. }
  202. $product['stock'] += $quantity;
  203. }
  204. break;
  205. }
  206. }
  207. saveProducts($products);
  208. }
  209. /**
  210. * Create new reservation
  211. */
  212. function createReservation($customerName, $customerEmail, $items) {
  213. $reservations = getReservations();
  214. // Validate stock for all items
  215. foreach ($items as $item) {
  216. $size = isset($item['size']) ? $item['size'] : null;
  217. if (!checkStock($item['product_id'], $item['quantity'], $size)) {
  218. $product = getProductById($item['product_id']);
  219. $productName = $product ? $product['name'] : 'Produkt';
  220. $sizeInfo = $size ? " (Größe: $size)" : '';
  221. return ['success' => false, 'message' => "Nicht genügend Lagerbestand für: $productName$sizeInfo"];
  222. }
  223. }
  224. // Allocate stock
  225. foreach ($items as $item) {
  226. $size = isset($item['size']) ? $item['size'] : null;
  227. allocateStock($item['product_id'], $item['quantity'], $size);
  228. }
  229. // Create reservation
  230. $now = new DateTime();
  231. $expires = clone $now;
  232. $expires->modify('+' . RESERVATION_EXPIRY_DAYS . ' days');
  233. $reservation = [
  234. 'id' => generateReservationId(),
  235. 'code' => generateReservationCode(),
  236. 'customer_name' => $customerName,
  237. 'customer_email' => $customerEmail,
  238. 'items' => $items,
  239. 'created' => $now->format('Y-m-d H:i:s'),
  240. 'expires' => $expires->format('Y-m-d H:i:s'),
  241. 'status' => 'open',
  242. 'picked_up' => false
  243. ];
  244. $reservations[] = $reservation;
  245. saveReservations($reservations);
  246. // Send confirmation emails
  247. sendReservationEmails($reservation);
  248. return ['success' => true, 'reservation' => $reservation];
  249. }
  250. /**
  251. * Mark reservation as picked up
  252. */
  253. function markReservationPickedUp($reservationId) {
  254. $reservations = getReservations();
  255. foreach ($reservations as &$reservation) {
  256. if ($reservation['id'] === $reservationId) {
  257. $reservation['picked_up'] = true;
  258. $reservation['status'] = 'picked_up';
  259. break;
  260. }
  261. }
  262. saveReservations($reservations);
  263. }
  264. /**
  265. * Check and expire old reservations
  266. */
  267. function expireOldReservations() {
  268. $reservations = getReservations();
  269. $now = new DateTime();
  270. $changed = false;
  271. foreach ($reservations as &$reservation) {
  272. if ($reservation['status'] === 'open' && !$reservation['picked_up']) {
  273. $expires = new DateTime($reservation['expires']);
  274. if ($now > $expires) {
  275. $reservation['status'] = 'expired';
  276. // Release stock
  277. foreach ($reservation['items'] as $item) {
  278. $size = isset($item['size']) ? $item['size'] : null;
  279. releaseStock($item['product_id'], $item['quantity'], $size);
  280. }
  281. $changed = true;
  282. }
  283. }
  284. }
  285. if ($changed) {
  286. saveReservations($reservations);
  287. }
  288. }
  289. /**
  290. * Sanitize input
  291. */
  292. function sanitize($input) {
  293. return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8');
  294. }
  295. /**
  296. * Format price
  297. */
  298. function formatPrice($price) {
  299. return number_format($price, 2, ',', '.') . ' €';
  300. }
  301. /**
  302. * Format date
  303. */
  304. function formatDate($dateString) {
  305. $date = new DateTime($dateString);
  306. return $date->format('d.m.Y H:i');
  307. }
  308. /**
  309. * Send email
  310. */
  311. function sendEmail($to, $subject, $message, $isHtml = true) {
  312. $headers = [];
  313. $headers[] = 'From: ' . FROM_NAME . ' <' . FROM_EMAIL . '>';
  314. $headers[] = 'Reply-To: ' . FROM_EMAIL;
  315. $headers[] = 'X-Mailer: PHP/' . phpversion();
  316. if ($isHtml) {
  317. $headers[] = 'MIME-Version: 1.0';
  318. $headers[] = 'Content-type: text/html; charset=UTF-8';
  319. }
  320. return mail($to, $subject, $message, implode("\r\n", $headers));
  321. }
  322. /**
  323. * Send reservation confirmation emails
  324. */
  325. function sendReservationEmails($reservation) {
  326. $products = getProducts();
  327. // Build items list
  328. $itemsHtml = '<ul>';
  329. foreach ($reservation['items'] as $item) {
  330. $product = getProductById($item['product_id']);
  331. if ($product) {
  332. $sizeInfo = '';
  333. if (isset($item['size']) && !empty($item['size'])) {
  334. $sizeInfo = ' - Größe: ' . htmlspecialchars($item['size']);
  335. }
  336. $itemsHtml .= '<li>' . htmlspecialchars($product['name']) . $sizeInfo . ' - Menge: ' . $item['quantity'] . '</li>';
  337. }
  338. }
  339. $itemsHtml .= '</ul>';
  340. // Customer email
  341. $customerSubject = 'Ihre Reservierung bei ' . SITE_NAME;
  342. $customerMessage = '
  343. <html>
  344. <head>
  345. <meta charset="UTF-8">
  346. </head>
  347. <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
  348. <h2 style="color: #c41e3a;">Reservierung bestätigt</h2>
  349. <p>Sehr geehrte/r ' . htmlspecialchars($reservation['customer_name']) . ',</p>
  350. <p>vielen Dank für Ihre Reservierung bei ' . SITE_NAME . '.</p>
  351. <div style="background: #fff3cd; border: 2px solid #ffc107; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px; text-align: center;">
  352. <h3 style="margin-top: 0;">Ihr Abholcode:</h3>
  353. <h2 style="font-size: 2rem; letter-spacing: 0.2rem; color: #856404; font-family: monospace;">' . htmlspecialchars($reservation['code']) . '</h2>
  354. </div>
  355. <h3>Reservierungsdetails:</h3>
  356. <p><strong>Reservierungsnummer:</strong> ' . htmlspecialchars($reservation['id']) . '</p>
  357. <p><strong>Erstellt am:</strong> ' . formatDate($reservation['created']) . '</p>
  358. <p><strong>Gültig bis:</strong> ' . formatDate($reservation['expires']) . '</p>
  359. <h3>Reservierte Artikel:</h3>
  360. ' . $itemsHtml . '
  361. <p><strong>Wichtig:</strong> Bitte bringen Sie diesen Abholcode zur Abholung mit. Die Reservierung ist bis zum ' . formatDate($reservation['expires']) . ' gültig.</p>
  362. <p>Mit freundlichen Grüßen<br>' . SITE_NAME . '</p>
  363. </body>
  364. </html>';
  365. sendEmail($reservation['customer_email'], $customerSubject, $customerMessage);
  366. // Admin email
  367. $adminSubject = 'Neue Reservierung: ' . $reservation['code'];
  368. $adminMessage = '
  369. <html>
  370. <head>
  371. <meta charset="UTF-8">
  372. </head>
  373. <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
  374. <h2 style="color: #c41e3a;">Neue Reservierung</h2>
  375. <p>Eine neue Reservierung wurde erstellt:</p>
  376. <div style="background: #d1ecf1; border: 2px solid #bee5eb; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px;">
  377. <h3 style="margin-top: 0;">Abholcode:</h3>
  378. <h2 style="font-size: 2rem; letter-spacing: 0.2rem; color: #0c5460; font-family: monospace;">' . htmlspecialchars($reservation['code']) . '</h2>
  379. </div>
  380. <h3>Kundendaten:</h3>
  381. <p><strong>Name:</strong> ' . htmlspecialchars($reservation['customer_name']) . '</p>
  382. <p><strong>E-Mail:</strong> ' . htmlspecialchars($reservation['customer_email']) . '</p>
  383. <h3>Reservierungsdetails:</h3>
  384. <p><strong>Reservierungsnummer:</strong> ' . htmlspecialchars($reservation['id']) . '</p>
  385. <p><strong>Erstellt am:</strong> ' . formatDate($reservation['created']) . '</p>
  386. <p><strong>Gültig bis:</strong> ' . formatDate($reservation['expires']) . '</p>
  387. <h3>Reservierte Artikel:</h3>
  388. ' . $itemsHtml . '
  389. </body>
  390. </html>';
  391. sendEmail(ADMIN_EMAIL, $adminSubject, $adminMessage);
  392. }