|
|
@@ -111,10 +111,14 @@ function getRateLimitStatePath(string $name): ?string
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @return bool true if under limit (and counter updated), false if blocked
|
|
|
+ * @return bool true if under limit; when $consume is true, increments counter on allow
|
|
|
*/
|
|
|
-function rateLimitTryConsume(string $name, int $maxAttempts, int $windowSeconds): bool
|
|
|
-{
|
|
|
+function rateLimitEvaluate(
|
|
|
+ string $name,
|
|
|
+ int $maxAttempts,
|
|
|
+ int $windowSeconds,
|
|
|
+ bool $consume,
|
|
|
+): bool {
|
|
|
if ($maxAttempts < 1 || $windowSeconds < 1) {
|
|
|
return true;
|
|
|
}
|
|
|
@@ -185,21 +189,23 @@ function rateLimitTryConsume(string $name, int $maxAttempts, int $windowSeconds)
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- $count++;
|
|
|
- $data[$ip] = ["w" => $windowStart, "c" => $count];
|
|
|
+ if ($consume) {
|
|
|
+ $count++;
|
|
|
+ $data[$ip] = ["w" => $windowStart, "c" => $count];
|
|
|
|
|
|
- $payload = json_encode($data, JSON_UNESCAPED_UNICODE);
|
|
|
- if ($payload === false) {
|
|
|
- flock($fh, LOCK_UN);
|
|
|
- fclose($fh);
|
|
|
- return true;
|
|
|
- }
|
|
|
+ $payload = json_encode($data, JSON_UNESCAPED_UNICODE);
|
|
|
+ if ($payload === false) {
|
|
|
+ flock($fh, LOCK_UN);
|
|
|
+ fclose($fh);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
|
|
|
- ftruncate($fh, 0);
|
|
|
- rewind($fh);
|
|
|
- fwrite($fh, $payload);
|
|
|
- fflush($fh);
|
|
|
- @chmod($path, 0660);
|
|
|
+ ftruncate($fh, 0);
|
|
|
+ rewind($fh);
|
|
|
+ fwrite($fh, $payload);
|
|
|
+ fflush($fh);
|
|
|
+ @chmod($path, 0660);
|
|
|
+ }
|
|
|
|
|
|
flock($fh, LOCK_UN);
|
|
|
fclose($fh);
|
|
|
@@ -207,6 +213,14 @@ function rateLimitTryConsume(string $name, int $maxAttempts, int $windowSeconds)
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * @return bool true if under limit (and counter updated), false if blocked
|
|
|
+ */
|
|
|
+function rateLimitTryConsume(string $name, int $maxAttempts, int $windowSeconds): bool
|
|
|
+{
|
|
|
+ return rateLimitEvaluate($name, $maxAttempts, $windowSeconds, true);
|
|
|
+}
|
|
|
+
|
|
|
function rateLimitClearIp(string $name, ?string $ip = null): void
|
|
|
{
|
|
|
$path = getRateLimitStatePath($name);
|
|
|
@@ -338,6 +352,16 @@ function getCheckoutRateLimitWindow(): int
|
|
|
: 3600;
|
|
|
}
|
|
|
|
|
|
+function checkoutRateLimitWouldAllow(): bool
|
|
|
+{
|
|
|
+ return rateLimitEvaluate(
|
|
|
+ "checkout",
|
|
|
+ getCheckoutRateLimitMax(),
|
|
|
+ getCheckoutRateLimitWindow(),
|
|
|
+ false,
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
function checkoutRateLimitTryConsume(): bool
|
|
|
{
|
|
|
return rateLimitTryConsume(
|
|
|
@@ -1356,6 +1380,47 @@ function getStartpageIntroLines()
|
|
|
return $lines;
|
|
|
}
|
|
|
|
|
|
+function backorderStatusEmpty(): string
|
|
|
+{
|
|
|
+ return "";
|
|
|
+}
|
|
|
+
|
|
|
+function backorderStatusToBeBackordered(): string
|
|
|
+{
|
|
|
+ return "to_be_backordered";
|
|
|
+}
|
|
|
+
|
|
|
+function backorderStatusOrdered(): string
|
|
|
+{
|
|
|
+ return "ordered";
|
|
|
+}
|
|
|
+
|
|
|
+/** Statuses persisted on order line items (load/save). */
|
|
|
+function getBackorderStatusesForStorage(): array
|
|
|
+{
|
|
|
+ return [
|
|
|
+ backorderStatusEmpty(),
|
|
|
+ backorderStatusToBeBackordered(),
|
|
|
+ backorderStatusOrdered(),
|
|
|
+ ];
|
|
|
+}
|
|
|
+
|
|
|
+/** Statuses settable from order detail (mark for external reorder). */
|
|
|
+function getBackorderStatusesForAdminMarking(): array
|
|
|
+{
|
|
|
+ return [backorderStatusEmpty(), backorderStatusToBeBackordered()];
|
|
|
+}
|
|
|
+
|
|
|
+function isValidBackorderStatusForStorage(string $status): bool
|
|
|
+{
|
|
|
+ return in_array($status, getBackorderStatusesForStorage(), true);
|
|
|
+}
|
|
|
+
|
|
|
+function isValidBackorderStatusForAdminMarking(string $status): bool
|
|
|
+{
|
|
|
+ return in_array($status, getBackorderStatusesForAdminMarking(), true);
|
|
|
+}
|
|
|
+
|
|
|
function normalizeOrderItem($item)
|
|
|
{
|
|
|
if (!is_array($item)) {
|
|
|
@@ -1383,9 +1448,8 @@ function normalizeOrderItem($item)
|
|
|
}
|
|
|
|
|
|
$backorderStatus = trim((string) ($item["backorder_status"] ?? ""));
|
|
|
- $allowedBackorderStatuses = ["", "to_be_backordered", "ordered"];
|
|
|
- if (!in_array($backorderStatus, $allowedBackorderStatuses, true)) {
|
|
|
- $backorderStatus = "";
|
|
|
+ if (!isValidBackorderStatusForStorage($backorderStatus)) {
|
|
|
+ $backorderStatus = backorderStatusEmpty();
|
|
|
}
|
|
|
|
|
|
return [
|
|
|
@@ -1593,28 +1657,6 @@ function getOrderById($orderId)
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
-function findOrderByConfirmationToken($token)
|
|
|
-{
|
|
|
- $token = trim((string) $token);
|
|
|
- if ($token === "") {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- foreach (getOrders() as $order) {
|
|
|
- if (($order["confirmation_token"] ?? "") === $token) {
|
|
|
- return $order;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return null;
|
|
|
-}
|
|
|
-
|
|
|
-function buildOrderConfirmationUrl($token)
|
|
|
-{
|
|
|
- $path = "/order-confirm.php?token=" . urlencode($token);
|
|
|
- return buildAbsoluteUrl($path);
|
|
|
-}
|
|
|
-
|
|
|
function buildAbsoluteUrl($path)
|
|
|
{
|
|
|
$path = "/" . ltrim((string) $path, "/");
|
|
|
@@ -1733,7 +1775,13 @@ function createOrder(
|
|
|
|
|
|
$orders = getOrders();
|
|
|
$orders[] = $order;
|
|
|
- saveOrders($orders);
|
|
|
+ if (!saveOrders($orders)) {
|
|
|
+ return [
|
|
|
+ "success" => false,
|
|
|
+ "message" =>
|
|
|
+ "Die Bestellung konnte nicht gespeichert werden. Bitte versuchen Sie es erneut.",
|
|
|
+ ];
|
|
|
+ }
|
|
|
|
|
|
logAccess("Order created in createOrder", [
|
|
|
"order_id" => $order["id"],
|
|
|
@@ -1892,8 +1940,7 @@ function orderItemCanBeManaged($order)
|
|
|
|
|
|
function setOrderItemBackorderStatus($orderId, $itemIndex, $status)
|
|
|
{
|
|
|
- $allowedStatuses = ["", "to_be_backordered"];
|
|
|
- if (!in_array($status, $allowedStatuses, true)) {
|
|
|
+ if (!isValidBackorderStatusForAdminMarking($status)) {
|
|
|
return ["success" => false, "message" => "Ungültiger Nachbestellstatus."];
|
|
|
}
|
|
|
|
|
|
@@ -1917,7 +1964,7 @@ function setOrderItemBackorderStatus($orderId, $itemIndex, $status)
|
|
|
];
|
|
|
}
|
|
|
|
|
|
- if ($status === "to_be_backordered") {
|
|
|
+ if ($status === backorderStatusToBeBackordered()) {
|
|
|
if (!empty($order["items"][$itemIndex]["is_processed"])) {
|
|
|
return [
|
|
|
"success" => false,
|
|
|
@@ -1925,11 +1972,12 @@ function setOrderItemBackorderStatus($orderId, $itemIndex, $status)
|
|
|
"Bearbeitete Positionen können nicht als Nachbestellung markiert werden.",
|
|
|
];
|
|
|
}
|
|
|
- $order["items"][$itemIndex]["backorder_status"] = "to_be_backordered";
|
|
|
+ $order["items"][$itemIndex]["backorder_status"] =
|
|
|
+ backorderStatusToBeBackordered();
|
|
|
$order["items"][$itemIndex]["backordered_at"] = $now;
|
|
|
$order["items"][$itemIndex]["ordered_at"] = "";
|
|
|
} else {
|
|
|
- $order["items"][$itemIndex]["backorder_status"] = "";
|
|
|
+ $order["items"][$itemIndex]["backorder_status"] = backorderStatusEmpty();
|
|
|
$order["items"][$itemIndex]["backordered_at"] = "";
|
|
|
$order["items"][$itemIndex]["ordered_at"] = "";
|
|
|
}
|
|
|
@@ -2284,11 +2332,11 @@ function applyBackorderBulkUpdate($productId, $size, $fromStatus, $toStatus, $qu
|
|
|
continue 2;
|
|
|
}
|
|
|
|
|
|
- if ($toStatus === "ordered") {
|
|
|
- $order["items"][$itemIndex]["backorder_status"] = "ordered";
|
|
|
+ if ($toStatus === backorderStatusOrdered()) {
|
|
|
+ $order["items"][$itemIndex]["backorder_status"] = backorderStatusOrdered();
|
|
|
$order["items"][$itemIndex]["ordered_at"] = $now;
|
|
|
} else {
|
|
|
- $order["items"][$itemIndex]["backorder_status"] = "";
|
|
|
+ $order["items"][$itemIndex]["backorder_status"] = backorderStatusEmpty();
|
|
|
$order["items"][$itemIndex]["ordered_at"] = "";
|
|
|
}
|
|
|
|
|
|
@@ -2557,13 +2605,23 @@ function addCartItem($productId, $size = "")
|
|
|
];
|
|
|
}
|
|
|
|
|
|
-function removeCartItemByIndex($index)
|
|
|
+function removeCartItem($productId, $size)
|
|
|
{
|
|
|
+ $productId = (int) $productId;
|
|
|
+ $size = trim((string) $size);
|
|
|
$cart = getCart();
|
|
|
- if (isset($cart[$index])) {
|
|
|
- unset($cart[$index]);
|
|
|
- $_SESSION["cart"] = array_values($cart);
|
|
|
+ $filtered = [];
|
|
|
+
|
|
|
+ foreach ($cart as $item) {
|
|
|
+ $itemProductId = (int) ($item["product_id"] ?? 0);
|
|
|
+ $itemSize = trim((string) ($item["size"] ?? ""));
|
|
|
+ if ($itemProductId === $productId && $itemSize === $size) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $filtered[] = $item;
|
|
|
}
|
|
|
+
|
|
|
+ $_SESSION["cart"] = array_values($filtered);
|
|
|
}
|
|
|
|
|
|
function clearCart()
|
|
|
@@ -2574,7 +2632,7 @@ function clearCart()
|
|
|
function getCartItemsDetailed()
|
|
|
{
|
|
|
$items = [];
|
|
|
- foreach (getCart() as $index => $cartItem) {
|
|
|
+ foreach (getCart() as $cartItem) {
|
|
|
$product = getProductById($cartItem["product_id"]);
|
|
|
if ($product === null) {
|
|
|
continue;
|
|
|
@@ -2582,7 +2640,6 @@ function getCartItemsDetailed()
|
|
|
|
|
|
$size = trim((string) ($cartItem["size"] ?? ""));
|
|
|
$items[] = [
|
|
|
- "cart_index" => $index,
|
|
|
"product" => $product,
|
|
|
"size" => $size,
|
|
|
"availability_label" =>
|