Passed
Push — master ( 9e6441...cfa3d6 )
by Christian
14:05
created

OrderRoute::checkGuestAuth()   B

Complexity

Conditions 11
Paths 18

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 13
nc 18
nop 2
dl 0
loc 19
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\Checkout\Order\SalesChannel;
4
5
use OpenApi\Annotations as OA;
6
use Shopware\Core\Checkout\Cart\Exception\CustomerNotLoggedInException;
7
use Shopware\Core\Checkout\Cart\Rule\PaymentMethodRule;
8
use Shopware\Core\Checkout\Order\Exception\GuestNotAuthenticatedException;
9
use Shopware\Core\Checkout\Order\Exception\WrongGuestCredentialsException;
10
use Shopware\Core\Checkout\Order\OrderDefinition;
11
use Shopware\Core\Checkout\Order\OrderEntity;
12
use Shopware\Core\Checkout\Promotion\PromotionCollection;
13
use Shopware\Core\Checkout\Promotion\PromotionEntity;
14
use Shopware\Core\Content\Rule\RuleEntity;
15
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
16
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
17
use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
18
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
19
use Shopware\Core\Framework\DataAbstractionLayer\Search\RequestCriteriaBuilder;
20
use Shopware\Core\Framework\Plugin\Exception\DecorationPatternException;
21
use Shopware\Core\Framework\Routing\Annotation\Entity;
22
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
23
use Shopware\Core\Framework\Routing\Annotation\Since;
24
use Shopware\Core\Framework\Rule\Container\Container;
25
use Shopware\Core\System\SalesChannel\SalesChannelContext;
26
use Symfony\Component\HttpFoundation\Request;
27
use Symfony\Component\Routing\Annotation\Route;
28
29
/**
30
 * @RouteScope(scopes={"store-api"})
31
 */
32
class OrderRoute extends AbstractOrderRoute
33
{
34
    /**
35
     * @var EntityRepositoryInterface
36
     */
37
    private $orderRepository;
38
39
    /**
40
     * @var RequestCriteriaBuilder
41
     */
42
    private $requestCriteriaBuilder;
43
44
    /**
45
     * @var OrderDefinition
46
     */
47
    private $orderDefinition;
48
49
    /**
50
     * @var EntityRepositoryInterface
51
     */
52
    private $promotionRepository;
53
54
    public function __construct(
55
        EntityRepositoryInterface $orderRepository,
56
        RequestCriteriaBuilder $requestCriteriaBuilder,
57
        OrderDefinition $salesChannelOrderDefinition,
58
        EntityRepositoryInterface $promotionRepository
59
    ) {
60
        $this->orderRepository = $orderRepository;
61
        $this->requestCriteriaBuilder = $requestCriteriaBuilder;
62
        $this->orderDefinition = $salesChannelOrderDefinition;
63
        $this->promotionRepository = $promotionRepository;
64
    }
65
66
    public function getDecorated(): AbstractOrderRoute
67
    {
68
        throw new DecorationPatternException(self::class);
69
    }
70
71
    /**
72
     * @Since("6.2.0.0")
73
     * @Entity("order")
74
     * @OA\Post(
75
     *      path="/order",
76
     *      summary="Listing orders",
77
     *      operationId="readOrder",
78
     *      tags={"Store API", "Order"},
79
     *      @OA\Parameter(name="Api-Basic-Parameters"),
80
     *      @OA\RequestBody(
81
     *          required=true,
82
     *          @OA\JsonContent(
83
     *              @OA\Property(property="checkPromotion", description="Wether to check the Promotions of orders", type="string"),
84
     *          )
85
     *      ),
86
     *      @OA\Response(
87
     *          response="200",
88
     *          description="",
89
     *          @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/order_flat"))
90
     *     )
91
     * )
92
     * @Route(path="/store-api/v{version}/order", name="store-api.order", methods={"GET", "POST"})
93
     */
94
    public function load(Request $request, SalesChannelContext $context, ?Criteria $criteria = null): OrderRouteResponse
95
    {
96
        // @deprecated tag:v6.4.0 - Criteria will be required
97
        if (!$criteria) {
98
            $criteria = $this->requestCriteriaBuilder->handleRequest($request, new Criteria(), $this->orderDefinition, $context->getContext());
99
        }
100
101
        $criteria->addFilter(new EqualsFilter('order.salesChannelId', $context->getSalesChannel()->getId()));
102
103
        $criteria->getAssociation('documents')
104
            ->addFilter(new EqualsFilter('config.displayInCustomerAccount', 'true'))
105
            ->addFilter(new EqualsFilter('sent', true));
106
107
        $criteria->addAssociation('billingAddress');
108
109
        if ($context->getCustomer()) {
110
            $criteria->addFilter(new EqualsFilter('order.orderCustomer.customerId', $context->getCustomer()->getId()));
111
        } elseif (!$criteria->hasEqualsFilter('deepLinkCode')) {
112
            throw new CustomerNotLoggedInException();
113
        }
114
115
        $orders = $this->orderRepository->search($criteria, $context->getContext());
116
117
        if ($criteria->hasEqualsFilter('deepLinkCode')) {
118
            $orders = $this->filterOldOrders($orders);
119
        }
120
121
        // Handle guest authentication if deeplink is set
122
        if (!$context->getCustomer() && $criteria->hasEqualsFilter('deepLinkCode')) {
123
            /** @var OrderEntity $order */
124
            $order = $orders->first();
125
            $this->checkGuestAuth($order, $request);
126
        }
127
128
        $response = new OrderRouteResponse($orders);
129
        if ($request->get('checkPromotion') === true) {
130
            /** @var OrderEntity $order */
131
            foreach ($orders as $order) {
132
                $promotions = $this->getActivePromotions($order, $context);
0 ignored issues
show
Bug introduced by
$order of type array is incompatible with the type Shopware\Core\Checkout\Order\OrderEntity expected by parameter $order of Shopware\Core\Checkout\O...::getActivePromotions(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

132
                $promotions = $this->getActivePromotions(/** @scrutinizer ignore-type */ $order, $context);
Loading history...
133
                $changeable = true;
134
                foreach ($promotions as $promotion) {
135
                    $changeable = $this->checkPromotion($promotion);
136
                    if ($changeable === true) {
137
                        break;
138
                    }
139
                }
140
                $response->addPaymentChangeable([$order->getId() => $changeable]);
141
            }
142
        }
143
144
        return $response;
145
    }
146
147
    private function getActivePromotions(OrderEntity $order, SalesChannelContext $context): PromotionCollection
148
    {
149
        $promotionIds = [];
150
        foreach ($order->getLineItems() ?? [] as $lineItem) {
151
            $payload = $lineItem->getPayload();
152
            if (isset($payload['promotionId']) && $payload['promotionId'] !== null) {
153
                $promotionIds[] = $payload['promotionId'];
154
            }
155
        }
156
157
        $promotions = new PromotionCollection();
158
159
        if (!empty($promotionIds)) {
160
            $criteria = new Criteria($promotionIds);
161
            $criteria->addAssociation('cartRules');
162
            /** @var PromotionCollection $promotions */
163
            $promotions = $this->promotionRepository->search($criteria, $context->getContext())->getEntities();
164
        }
165
166
        return $promotions;
167
    }
168
169
    private function checkRuleType(Container $rule): bool
170
    {
171
        foreach ($rule->getRules() as $nestedRule) {
172
            if ($nestedRule instanceof Container && $this->checkRuleType($nestedRule) === false) {
173
                return false;
174
            }
175
            if ($nestedRule instanceof PaymentMethodRule) {
176
                return false;
177
            }
178
        }
179
180
        return true;
181
    }
182
183
    private function checkPromotion(PromotionEntity $promotion): bool
184
    {
185
        foreach ($promotion->getCartRules() as $cartRule) {
186
            if ($this->checkCartRule($cartRule) === false) {
187
                return false;
188
            }
189
        }
190
191
        return true;
192
    }
193
194
    private function checkCartRule(RuleEntity $cartRule): bool
195
    {
196
        $payload = $cartRule->getPayload();
197
        foreach ($payload->getRules() as $rule) {
0 ignored issues
show
Bug introduced by
The method getRules() does not exist on Shopware\Core\Framework\Rule\Rule. It seems like you code against a sub-type of Shopware\Core\Framework\Rule\Rule such as Shopware\Core\Framework\Rule\Container\FilterRule or Shopware\Core\Framework\Rule\Container\Container. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

197
        foreach ($payload->/** @scrutinizer ignore-call */ getRules() as $rule) {
Loading history...
198
            if ($this->checkRuleType($rule) === false) {
199
                return false;
200
            }
201
        }
202
203
        return true;
204
    }
205
206
    private function filterOldOrders(EntitySearchResult $orders): EntitySearchResult
207
    {
208
        // Search with deepLinkCode needs updatedAt Filter
209
        $latestOrderDate = (new \DateTime())->setTimezone(new \DateTimeZone('UTC'))->modify(-abs(30) . ' Day');
210
        $orders = $orders->filter(function (OrderEntity $order) use ($latestOrderDate) {
211
            return $order->getCreatedAt() > $latestOrderDate || $order->getUpdatedAt() > $latestOrderDate;
212
        });
213
214
        return $orders;
215
    }
216
217
    private function checkGuestAuth(OrderEntity $order, Request $request): void
218
    {
219
        $orderCustomer = $order->getOrderCustomer();
220
        $guest = $orderCustomer !== null && $orderCustomer->getCustomer() !== null && $orderCustomer->getCustomer()->getGuest();
221
        // Throw exception when customer is not guest
222
        if (!$guest) {
223
            throw new CustomerNotLoggedInException();
224
        }
225
        // Verify email and zip code with this order
226
        if ($request->get('email', false) && $request->get('zipcode', false)) {
227
            $billingAddress = $order->getAddresses() !== null ? $order->getAddresses()->get($order->getBillingAddressId()) : null;
228
            if ($billingAddress === null
229
                || $orderCustomer === null
230
                || $request->get('email') !== $orderCustomer->getEmail()
231
                || $request->get('zipcode') !== $billingAddress->getZipcode()) {
232
                throw new WrongGuestCredentialsException();
233
            }
234
        } else {
235
            throw new GuestNotAuthenticatedException();
236
        }
237
    }
238
}
239