Completed
Push — 5.6 ( 534367...9eeda9 )
by Christian
10:06 queued 10s
created

Shopware_Controllers_Backend_Order   F

Complexity

Total Complexity 227

Size/Duplication

Total Lines 2182
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 24

Importance

Changes 0
Metric Value
wmc 227
lcom 1
cbo 24
dl 0
loc 2182
rs 0.8
c 0
b 0
f 0

57 Methods

Rating   Name   Duplication   Size   Complexity  
C batchProcessAction() 0 109 13
B mergeDocumentsAction() 0 44 7
A initAcl() 0 11 1
A getPaymentStatusAction() 0 9 1
A preDispatch() 0 7 2
A getWhitelistedCSRFActions() 0 8 1
A loadListAction() 0 30 1
A getOrderDocumentsQuery() 0 17 1
A getOrderStatusQuery() 0 23 4
B loadStoresAction() 0 62 2
A getListAction() 0 24 3
A getTaxAction() 0 10 1
A getVouchersAction() 0 5 1
A getDocumentTypesAction() 0 5 1
A getStatusHistoryAction() 0 30 2
F saveAction() 0 147 21
A deleteAction() 0 26 3
C savePositionAction() 0 108 9
C deletePositionAction() 0 81 9
B sendMailAction() 0 66 3
A deleteDocumentAction() 0 35 3
A createMailAction() 0 23 1
B getMailTemplatesAction() 0 45 5
A createDocumentAction() 0 25 3
A openPdfAction() 0 40 3
A getPartnersAction() 0 24 1
A getManager() 0 8 2
A getRepository() 0 8 2
A getShopRepository() 0 8 2
A getCountryRepository() 0 8 2
A getPaymentRepository() 0 8 2
A getDispatchRepository() 0 8 2
A getDocumentRepository() 0 10 2
B getList() 0 75 7
B prepareAddressData() 0 20 7
B resolveSortParameter() 0 37 7
A assignAssociation() 0 13 3
A getOrder() 0 7 1
A mergeDocuments() 0 20 3
B createOrderDocuments() 0 26 6
B checkOrderStatus() 0 44 11
A getDocument() 0 21 3
A getDocumentFromDatabase() 0 27 2
A addAttachments() 0 17 3
A createAttachment() 0 13 1
A getFileName() 0 13 3
A getOrderLocaleId() 0 11 1
A getDefaultName() 0 12 1
B createDocument() 0 46 5
F getPositionAssociatedData() 0 89 16
B getAssociatedData() 0 59 9
A getMailForOrder() 0 31 3
A getVariantsStock() 0 10 1
A downloadFileFromFilesystem() 0 10 1
C createCriteria() 0 62 12
A getTemplateNameForDocumentTypeId() 0 22 4
A getCurrentLocale() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Shopware_Controllers_Backend_Order often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Shopware_Controllers_Backend_Order, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Shopware 5
4
 * Copyright (c) shopware AG
5
 *
6
 * According to our dual licensing model, this program can be used either
7
 * under the terms of the GNU Affero General Public License, version 3,
8
 * or under a proprietary license.
9
 *
10
 * The texts of the GNU Affero General Public License with an additional
11
 * permission and of our proprietary license can be found at and
12
 * in the LICENSE file you have received along with this program.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * "Shopware" is a registered trademark of shopware AG.
20
 * The licensing of the program under the AGPLv3 does not imply a
21
 * trademark license. Therefore any rights, title and interest in
22
 * our trademarks remain entirely with us.
23
 */
24
25
use Doctrine\DBAL\Connection;
26
use Doctrine\ORM\AbstractQuery;
27
use Shopware\Bundle\AttributeBundle\Repository\SearchCriteria;
28
use Shopware\Bundle\MailBundle\Service\LogEntryBuilder;
29
use Shopware\Components\CSRFWhitelistAware;
30
use Shopware\Components\Model\QueryBuilder;
31
use Shopware\Components\Random;
32
use Shopware\Components\StateTranslatorService;
33
use Shopware\Models\Article\Detail as ArticleDetail;
34
use Shopware\Models\Article\Unit;
35
use Shopware\Models\Country\Country;
36
use Shopware\Models\Country\State;
37
use Shopware\Models\Customer\Customer;
38
use Shopware\Models\Dispatch\Dispatch;
39
use Shopware\Models\Document\Document as DocumentType;
40
use Shopware\Models\Mail\Mail;
41
use Shopware\Models\Order\Billing;
42
use Shopware\Models\Order\Detail;
43
use Shopware\Models\Order\DetailStatus;
44
use Shopware\Models\Order\Document\Document;
45
use Shopware\Models\Order\Order;
46
use Shopware\Models\Order\Shipping;
47
use Shopware\Models\Order\Status;
48
use Shopware\Models\Payment\Payment;
49
use Shopware\Models\Shop\Shop;
50
use Shopware\Models\Tax\Tax;
51
52
class Shopware_Controllers_Backend_Order extends Shopware_Controllers_Backend_ExtJs implements CSRFWhitelistAware
53
{
54
    /**
55
     * Order repository. Declared for an fast access to the order repository.
56
     *
57
     * @var \Shopware\Models\Order\Repository
58
     */
59
    public static $repository;
60
61
    /**
62
     * Shop repository. Declared for an fast access to the shop repository.
63
     *
64
     * @var \Shopware\Models\Shop\Repository
65
     */
66
    public static $shopRepository;
67
68
    /**
69
     * Country repository. Declared for an fast access to the country repository.
70
     *
71
     * @var \Shopware\Models\Country\Repository
72
     */
73
    public static $countryRepository;
74
75
    /**
76
     * Payment repository. Declared for an fast access to the country repository.
77
     *
78
     * @var \Shopware\Models\Payment\Repository
79
     */
80
    public static $paymentRepository;
81
82
    /**
83
     * Dispatch repository. Declared for an fast access to the dispatch repository.
84
     *
85
     * @var \Shopware\Models\Dispatch\Repository
86
     */
87
    public static $dispatchRepository;
88
89
    /**
90
     * Contains the shopware model manager
91
     *
92
     * @var \Shopware\Components\Model\ModelManager
93
     */
94
    public static $manager;
95
96
    /**
97
     * Contains the dynamic receipt repository
98
     *
99
     * @var \Shopware\Components\Model\ModelRepository
100
     */
101
    public static $documentRepository;
102
103
    /**
104
     * Registers the different acl permission for the different controller actions.
105
     */
106
    public function initAcl()
107
    {
108
        $this->addAclPermission('loadStores', 'read', 'Insufficient Permissions');
109
        $this->addAclPermission('save', 'update', 'Insufficient Permissions');
110
        $this->addAclPermission('deletePosition', 'update', 'Insufficient Permissions');
111
        $this->addAclPermission('savePosition', 'update', 'Insufficient Permissions');
112
        $this->addAclPermission('createDocument', 'update', 'Insufficient Permissions');
113
        $this->addAclPermission('batchProcess', 'update', 'Insufficient Permissions');
114
        $this->addAclPermission('delete', 'delete', 'Insufficient Permissions');
115
        $this->addAclPermission('deleteDocument', 'deleteDocument', 'Insufficient Permissions');
116
    }
117
118
    /**
119
     * Get a list of available payment status
120
     */
121
    public function getPaymentStatusAction()
122
    {
123
        $orderStatus = $this->getRepository()->getPaymentStatusQuery()->getArrayResult();
124
125
        $this->View()->assign([
126
            'success' => true,
127
            'data' => $orderStatus,
128
        ]);
129
    }
130
131
    /**
132
     * Enable json renderer for index / load action
133
     * Check acl rules
134
     */
135
    public function preDispatch()
136
    {
137
        $actions = ['index', 'load', 'skeleton', 'extends', 'mergeDocuments'];
138
        if (!in_array($this->Request()->getActionName(), $actions)) {
139
            $this->Front()->Plugins()->Json()->setRenderer();
140
        }
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146
    public function getWhitelistedCSRFActions()
147
    {
148
        return [
149
            'openPdf',
150
            'createDocument',
151
            'mergeDocuments',
152
        ];
153
    }
154
155
    public function loadListAction()
156
    {
157
        $filters = [['property' => 'status.id', 'expression' => '!=', 'value' => '-1']];
158
        $orderState = $this->getRepository()->getOrderStatusQuery($filters)->getArrayResult();
159
        $paymentState = $this->getRepository()->getPaymentStatusQuery()->getArrayResult();
160
        $positionStatus = $this->getRepository()->getDetailStatusQuery()->getArrayResult();
161
162
        $stateTranslator = $this->get('shopware.components.state_translator');
163
164
        $orderState = array_map(function ($orderStateItem) use ($stateTranslator) {
165
            $orderStateItem = $stateTranslator->translateState(StateTranslatorService::STATE_ORDER, $orderStateItem);
166
167
            return $orderStateItem;
168
        }, $orderState);
169
170
        $paymentState = array_map(function ($paymentStateItem) use ($stateTranslator) {
171
            $paymentStateItem = $stateTranslator->translateState(StateTranslatorService::STATE_PAYMENT, $paymentStateItem);
172
173
            return $paymentStateItem;
174
        }, $paymentState);
175
176
        $this->View()->assign([
177
            'success' => true,
178
            'data' => [
179
                'orderStatus' => $orderState,
180
                'paymentStatus' => $paymentState,
181
                'positionStatus' => $positionStatus,
182
            ],
183
        ]);
184
    }
185
186
    /**
187
     * Get documents of a specific type for the given orders
188
     *
189
     * @param int[]  $orderIds
190
     * @param string $docType
191
     *
192
     * @return \Doctrine\ORM\Query
193
     */
194
    public function getOrderDocumentsQuery($orderIds, $docType)
195
    {
196
        $builder = Shopware()->Models()->createQueryBuilder();
197
        $builder->select([
198
            'orders',
199
            'documents',
200
        ]);
201
202
        $builder->from(Order::class, 'orders');
203
        $builder->leftJoin('orders.documents', 'documents')
204
            ->where('documents.typeId = :type')
205
            ->andWhere('orders.id IN (:orderIds)')
206
            ->setParameter('orderIds', $orderIds, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY)
207
            ->setParameter(':type', $docType);
208
209
        return $builder->getQuery();
210
    }
211
212
    /**
213
     * This class has its own OrderStatusQuery as we need to get rid of states with status.id = -1
214
     *
215
     * @param array|null $filter
216
     * @param array|null $order
217
     * @param int|null   $offset
218
     * @param int|null   $limit
219
     *
220
     * @return \Doctrine\ORM\Query
221
     */
222
    public function getOrderStatusQuery($filter = null, $order = null, $offset = null, $limit = null)
223
    {
224
        $builder = Shopware()->Models()->createQueryBuilder();
225
        $builder->select(['status'])
226
            ->from(Status::class, 'status')
227
            ->andWhere("status.group = 'state'");
228
229
        if ($filter !== null) {
230
            $builder->addFilter($filter);
231
        }
232
        if ($order !== null) {
233
            $builder->addOrderBy($order);
234
        } else {
235
            $builder->orderBy('status.position', 'ASC');
236
        }
237
238
        if ($offset !== null) {
239
            $builder->setFirstResult($offset)
240
                ->setMaxResults($limit);
241
        }
242
243
        return $builder->getQuery();
244
    }
245
246
    /**
247
     * batch function which summarizes some queries in order to speed up the order-detail startup
248
     */
249
    public function loadStoresAction()
250
    {
251
        $id = $this->Request()->getParam('orderId');
252
        if ($id === null) {
253
            $this->View()->assign(['success' => false, 'message' => 'No orderId passed']);
254
255
            return;
256
        }
257
258
        $stateTranslator = $this->get('shopware.components.state_translator');
259
260
        $orderState = $this->getOrderStatusQuery()->getArrayResult();
261
        $paymentState = $this->getRepository()->getPaymentStatusQuery()->getArrayResult();
262
263
        $orderState = array_map(function ($orderStateItem) use ($stateTranslator) {
264
            $orderStateItem = $stateTranslator->translateState(StateTranslatorService::STATE_ORDER, $orderStateItem);
265
266
            return $orderStateItem;
267
        }, $orderState);
268
269
        $paymentState = array_map(function ($paymentStateItem) use ($stateTranslator) {
270
            $paymentStateItem = $stateTranslator->translateState(StateTranslatorService::STATE_PAYMENT, $paymentStateItem);
271
272
            return $paymentStateItem;
273
        }, $paymentState);
274
275
        $countriesSort = [
276
            [
277
                'property' => 'countries.active',
278
                'direction' => 'DESC',
279
            ],
280
            [
281
                'property' => 'countries.name',
282
                'direction' => 'ASC',
283
            ],
284
        ];
285
286
        $shops = $this->getShopRepository()->getBaseListQuery()->getArrayResult();
287
        $countries = $this->getCountryRepository()->getCountriesQuery(null, $countriesSort)->getArrayResult();
288
        $payments = $this->getPaymentRepository()->getAllPaymentsQuery()->getArrayResult();
289
        $dispatches = $this->getDispatchRepository()->getDispatchesQuery()->getArrayResult();
290
        $documentTypes = $this->getRepository()->getDocumentTypesQuery()->getArrayResult();
291
292
        // translate objects
293
        $translationComponent = $this->get('translation');
294
        $payments = $translationComponent->translatePaymentMethods($payments);
295
        $documentTypes = $translationComponent->translateDocuments($documentTypes);
296
        $dispatches = $translationComponent->translateDispatchMethods($dispatches);
297
298
        $this->View()->assign([
299
            'success' => true,
300
            'data' => [
301
                'orderStatus' => $orderState,
302
                'paymentStatus' => $paymentState,
303
                'shops' => $shops,
304
                'countries' => $countries,
305
                'payments' => $payments,
306
                'dispatches' => $dispatches,
307
                'documentTypes' => $documentTypes,
308
            ],
309
        ]);
310
    }
311
312
    /**
313
     * Event listener method which fires when the order store is loaded. Returns an array of order data
314
     * which displayed in an Ext.grid.Panel. The order data contains all associations of an order (positions, shop,
315
     * customer, ...). The limit, filter and order parameter are used in the id query. The result of the id query are
316
     * used to filter the detailed query which created over the getListQuery function.
317
     */
318
    public function getListAction()
319
    {
320
        // Read store parameter to filter and paginate the data.
321
        $limit = (int) $this->Request()->getParam('limit', 20);
322
        $offset = (int) $this->Request()->getParam('start', 0);
323
        $sort = $this->Request()->getParam('sort', []);
324
        $filter = $this->Request()->getParam('filter', []);
325
        $orderId = $this->Request()->getParam('orderID');
326
327
        if ($orderId !== null) {
328
            $orderIdFilter = ['property' => 'orders.id', 'value' => $orderId];
329
            if (!is_array($filter)) {
330
                $filter = [];
331
            }
332
            $filter[] = $orderIdFilter;
333
        }
334
335
        $list = $this->getList($filter, $sort, $offset, $limit);
336
337
        $translationComponent = $this->get('translation');
338
        $list['data'] = $translationComponent->translateOrders($list['data']);
339
340
        $this->View()->assign($list);
341
    }
342
343
    /**
344
     * Returns an array of all defined taxes. Used for the position grid combo box on the detail page of the backend
345
     * order module.
346
     */
347
    public function getTaxAction()
348
    {
349
        $builder = Shopware()->Models()->createQueryBuilder();
350
        $tax = $builder->select(['tax'])
351
            ->from(Tax::class, 'tax')
352
            ->getQuery()
353
            ->getArrayResult();
354
355
        $this->View()->assign(['success' => true, 'data' => $tax]);
356
    }
357
358
    /**
359
     * The getVouchers function is used by the extJs voucher store which used for a
360
     * combo box on the order detail page.
361
     */
362
    public function getVouchersAction()
363
    {
364
        $vouchers = $this->getRepository()->getVoucherQuery()->getArrayResult();
365
        $this->View()->assign(['success' => true, 'data' => $vouchers]);
366
    }
367
368
    /**
369
     * Returns all supported document types. The data is used for the configuration panel.
370
     */
371
    public function getDocumentTypesAction()
372
    {
373
        $types = $this->getRepository()->getDocumentTypesQuery()->getArrayResult();
374
        $this->View()->assign(['success' => true, 'data' => $types]);
375
    }
376
377
    /**
378
     * Event listener function of the history store in the order backend module.
379
     * Returns the status history of the passed order.
380
     */
381
    public function getStatusHistoryAction()
382
    {
383
        $orderId = $this->Request()->getParam('orderID');
384
        $limit = $this->Request()->getParam('limit', 20);
385
        $offset = $this->Request()->getParam('start', 0);
386
        $sort = $this->Request()->getParam('sort', [['property' => 'history.changeDate', 'direction' => 'DESC']]);
387
388
        /** @var Enlight_Components_Snippet_Namespace $namespace */
389
        $namespace = Shopware()->Snippets()->getNamespace('backend/order');
390
391
        //the backend order module have no function to create a new order so an order id must be passed.
392
        if (empty($orderId)) {
393
            $this->View()->assign([
394
                'success' => false,
395
                'data' => $this->Request()->getParams(),
396
                'message' => $namespace->get('no_order_id_passed', 'No valid order id passed.'),
397
            ]);
398
399
            return;
400
        }
401
402
        $history = $this->getRepository()
403
            ->getOrderStatusHistoryListQuery($orderId, $sort, $offset, $limit)
404
            ->getArrayResult();
405
406
        $this->View()->assign([
407
            'success' => true,
408
            'data' => $history,
409
        ]);
410
    }
411
412
    /**
413
     * CRUD function save and update of the order model.
414
     *
415
     * Event listener method of the backend/order/model/order.js model which
416
     * is used for the backend order module detail page to edit a single order.
417
     */
418
    public function saveAction()
419
    {
420
        $id = (int) $this->Request()->getParam('id');
421
422
        /** @var Enlight_Components_Snippet_Namespace $namespace */
423
        $namespace = Shopware()->Snippets()->getNamespace('backend/order/main');
424
425
        // The backend order module have no function to create a new order so an order id must be passed.
426
        if (empty($id)) {
427
            $this->View()->assign([
428
                'success' => false,
429
                'data' => $this->Request()->getParams(),
430
                'message' => $namespace->get('no_order_id_passed', 'No valid order id passed.'),
431
            ]);
432
433
            return;
434
        }
435
436
        $order = $this->getRepository()->find($id);
437
438
        // The backend order module have no function to create a new order so an order id must be passed.
439
        if (!($order instanceof Order)) {
440
            $this->View()->assign([
441
                'success' => false,
442
                'data' => $this->Request()->getParams(),
443
                'message' => $namespace->get('no_order_id_passed', 'No valid order id passed.'),
444
            ]);
445
446
            return;
447
        }
448
449
        $billing = $order->getBilling();
450
        $shipping = $order->getShipping();
451
452
        // Check if the shipping and billing model already exist. If not create a new instance.
453
        if (!$shipping instanceof \Shopware\Models\Order\Shipping) {
454
            $shipping = new Shipping();
0 ignored issues
show
Unused Code introduced by
$shipping is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
455
        }
456
457
        if (!$billing instanceof \Shopware\Models\Order\Billing) {
458
            $billing = new Billing();
0 ignored issues
show
Unused Code introduced by
$billing is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
459
        }
460
        // Get all passed order data
461
        $data = $this->Request()->getParams();
462
463
        if ($order->getChanged() !== null) {
464
            try {
465
                $changed = new \DateTime($data['changed']);
466
            } catch (Exception $e) {
467
                // If we have a invalid date caused by imports
468
                $changed = $order->getChanged();
469
            }
470
471
            if ($changed->getTimestamp() < 0 && $changed->getChanged()->getTimestamp() < 0) {
472
                $changed = $order->getChanged();
473
            }
474
475
            // We have timestamp conversion issues on Windows Users
476
            $diff = abs($order->getChanged()->getTimestamp() - $changed->getTimestamp());
477
478
            // Check whether the order has been modified in the meantime
479
            if ($diff > 1) {
480
                $this->View()->assign([
481
                    'success' => false,
482
                    'data' => $this->getOrder($order->getId()),
483
                    'overwriteAble' => true,
484
                    'message' => $namespace->get('order_has_been_changed', 'The order has been changed in the meantime. To prevent overwriting these changes, saving the order was aborted. Please close the order and re-open it.'),
485
                ]);
486
487
                return;
488
            }
489
        }
490
491
        // Prepares the associated data of an order.
492
        $data = $this->getAssociatedData($data);
493
494
        // Before we can create the status mail, we need to save the order data. Otherwise
495
        // the status mail would be created with the old order status and amount.
496
        /** @var \Shopware\Models\Order\Order $order */
497
        $statusBefore = $order->getOrderStatus();
498
        $clearedBefore = $order->getPaymentStatus();
499
        $invoiceShippingBefore = $order->getInvoiceShipping();
500
        $invoiceShippingNetBefore = $order->getInvoiceShippingNet();
501
502
        if (!empty($data['clearedDate'])) {
503
            try {
504
                $data['clearedDate'] = new \DateTime($data['clearedDate']);
505
            } catch (\Exception $e) {
506
                $data['clearedDate'] = null;
507
            }
508
        }
509
510
        if (isset($data['orderTime'])) {
511
            unset($data['orderTime']);
512
        }
513
514
        $order->fromArray($data);
515
516
        // Check if the invoice shipping has been changed
517
        $invoiceShippingChanged = (bool) ($invoiceShippingBefore != $order->getInvoiceShipping());
518
        $invoiceShippingNetChanged = (bool) ($invoiceShippingNetBefore != $order->getInvoiceShippingNet());
519
        if ($invoiceShippingChanged || $invoiceShippingNetChanged) {
520
            // Recalculate the new invoice amount
521
            $order->calculateInvoiceAmount();
522
        }
523
524
        Shopware()->Models()->flush();
525
        Shopware()->Models()->clear();
526
        $order = $this->getRepository()->find($id);
527
528
        //if the status has been changed an status mail is created.
529
        $warning = null;
530
        $mail = null;
531
        if ($order->getOrderStatus()->getId() !== $statusBefore->getId() || $order->getPaymentStatus()->getId() !== $clearedBefore->getId()) {
532
            if ($order->getOrderStatus()->getId() !== $statusBefore->getId()) {
533
                $status = $order->getOrderStatus();
534
            } else {
535
                $status = $order->getPaymentStatus();
536
            }
537
            try {
538
                $mail = $this->getMailForOrder($order->getId(), $status->getId());
539
            } catch (\Exception $e) {
540
                $warning = sprintf(
541
                    $namespace->get('warning/mail_creation_failed'),
542
                    $status->getName(),
543
                    $e->getMessage()
544
                );
545
            }
546
        }
547
548
        $data = $this->getOrder($order->getId());
549
        if (!empty($mail)) {
550
            $data['mail'] = $mail['data'];
551
        } else {
552
            $data['mail'] = null;
553
        }
554
555
        $result = [
556
            'success' => true,
557
            'data' => $data,
558
        ];
559
        if (isset($warning)) {
560
            $result['warning'] = $warning;
561
        }
562
563
        $this->View()->assign($result);
564
    }
565
566
    /**
567
     * Deletes a single order from the database.
568
     * Expects a single order id which placed in the parameter id
569
     */
570
    public function deleteAction()
571
    {
572
        /** @var Enlight_Components_Snippet_Namespace $namespace */
573
        $namespace = Shopware()->Snippets()->getNamespace('backend/order');
574
575
        // Get posted customers
576
        $orderId = $this->Request()->getParam('id');
577
578
        if (empty($orderId) || !is_numeric($orderId)) {
579
            $this->View()->assign([
580
                'success' => false,
581
                'data' => $this->Request()->getParams(),
582
                'message' => $namespace->get('no_order_id_passed', 'No valid order id passed.'),
583
            ]);
584
585
            return;
586
        }
587
588
        $entity = $this->getRepository()->find($orderId);
589
        $this->getManager()->remove($entity);
590
591
        // Performs all of the collected actions.
592
        $this->getManager()->flush();
593
594
        $this->View()->assign(['success' => true]);
595
    }
596
597
    /**
598
     * CRUD function save and update of the position store of the backend order module.
599
     * The function handles the update and insert routine of a single order position.
600
     * After the position has been added to the order, the order invoice amount will be recalculated.
601
     * The refreshed order will be assigned to the view to refresh the panels and grids.
602
     */
603
    public function savePositionAction()
604
    {
605
        $id = $this->Request()->getParam('id');
606
607
        $orderId = $this->Request()->getParam('orderId');
608
609
        /** @var Enlight_Components_Snippet_Namespace $namespace */
610
        $namespace = Shopware()->Snippets()->getNamespace('backend/order/controller/main');
611
612
        // Check if an order id is passed. If no order id passed, return success false
613
        if (empty($orderId)) {
614
            $this->View()->assign([
615
                'success' => false,
616
                'data' => $this->Request()->getParams(),
617
                'message' => $namespace->get('no_order_id_passed', 'No valid order id passed.'),
618
            ]);
619
620
            return;
621
        }
622
623
        // Find the order model. If no model founded, return success false
624
        $order = $this->getRepository()->find($orderId);
625
        if (empty($order)) {
626
            $this->View()->assign([
627
                'success' => false,
628
                'data' => $this->Request()->getParams(),
629
                'message' => $namespace->get('no_order_id_passed', 'No valid order id passed.'),
630
            ]);
631
632
            return;
633
        }
634
635
        // Check whether the order has been modified in the meantime
636
        $lastOrderChange = new \DateTime($this->Request()->getParam('changed'));
637
        if ($order->getChanged() !== null && $order->getChanged()->getTimestamp() != $lastOrderChange->getTimestamp()) {
638
            $params = $this->Request()->getParams();
639
            $params['changed'] = $order->getChanged();
640
            $this->View()->assign([
641
                'success' => false,
642
                'data' => $params,
643
                'overwriteAble' => true,
644
                'message' => $namespace->get('order_has_been_changed', 'The order has been changed in the meantime. To prevent overwriting these changes, saving the order was aborted. Please close the order and re-open it.'),
645
            ]);
646
647
            return;
648
        }
649
650
        // Check if the passed position data is a new position or an existing position.
651
        if (empty($id)) {
652
            $position = new Detail();
653
            $attribute = new Shopware\Models\Attribute\OrderDetail();
654
            $position->setAttribute($attribute);
655
            Shopware()->Models()->persist($position);
656
        } else {
657
            $detailRepository = Shopware()->Models()->getRepository(Detail::class);
658
            $position = $detailRepository->find($id);
659
        }
660
661
        $data = $this->Request()->getParams();
662
        $data['number'] = $order->getNumber();
663
664
        $data = $this->getPositionAssociatedData($data);
665
        // If $data === null, the product was not found
666
        if ($data === null) {
667
            $this->View()->assign([
668
                'success' => false,
669
                'data' => [],
670
                'message' => 'The productnumber "' . $this->Request()->getParam('articleNumber', '') . '" is not valid',
671
            ]);
672
673
            return;
674
        }
675
676
        $position->fromArray($data);
677
        $position->setOrder($order);
678
679
        Shopware()->Models()->flush();
680
681
        // If the passed data is a new position, the flush function will add the new id to the position model
682
        $data['id'] = $position->getId();
683
684
        // The position model will refresh the product stock, so the product stock
685
        // will be assigned to the view to refresh the grid or form panel.
686
        $variantRepository = Shopware()->Models()->getRepository(ArticleDetail::class);
687
        $variant = $variantRepository->findOneBy(['number' => $position->getArticleNumber()]);
688
        if ($variant instanceof ArticleDetail) {
689
            $data['inStock'] = $variant->getInStock();
690
        }
691
        $order = $this->getRepository()->find($order->getId());
692
693
        Shopware()->Models()->persist($order);
694
        Shopware()->Models()->flush();
695
696
        $invoiceAmount = $order->getInvoiceAmount();
697
        $changed = $order->getChanged();
698
699
        if ($position->getOrder() instanceof \Shopware\Models\Order\Order) {
700
            $invoiceAmount = $position->getOrder()->getInvoiceAmount();
701
            $changed = $position->getOrder()->getChanged();
702
        }
703
704
        $this->View()->assign([
705
            'success' => true,
706
            'data' => $data,
707
            'invoiceAmount' => $invoiceAmount,
708
            'changed' => $changed,
709
        ]);
710
    }
711
712
    /**
713
     * CRUD function delete of the position and list store of the backend order module.
714
     * The function can delete one or many order positions. After the positions has been deleted
715
     * the order invoice amount will be recalculated. The refreshed order will be assigned to the
716
     * view to refresh the panels and grids.
717
     */
718
    public function deletePositionAction()
719
    {
720
        /** @var Enlight_Components_Snippet_Namespace $namespace */
721
        $namespace = Shopware()->Snippets()->getNamespace('backend/order/controller/main');
722
723
        $positions = $this->Request()->getParam('positions', [['id' => $this->Request()->getParam('id')]]);
724
725
        // Check if any positions is passed.
726
        if (empty($positions)) {
727
            $this->View()->assign([
728
                'success' => false,
729
                'data' => $this->Request()->getParams(),
730
                'message' => $namespace->get('no_order_passed', 'No orders passed'),
731
            ]);
732
733
            return;
734
        }
735
736
        // If no order id passed it isn't possible to update the order amount, so we will cancel the position deletion here.
737
        $orderId = $this->Request()->getParam('orderID');
738
739
        if (empty($orderId)) {
740
            $this->View()->assign([
741
                'success' => false,
742
                'data' => $this->Request()->getParams(),
743
                'message' => $namespace->get('no_order_id_passed', 'No valid order id passed.'),
744
            ]);
745
746
            return;
747
        }
748
749
        /** @var \Shopware\Models\Order\Order $order */
750
        $order = $this->getRepository()->find($orderId);
751
        if (empty($order)) {
752
            $this->View()->assign([
753
                'success' => false,
754
                'data' => $this->Request()->getParams(),
755
                'message' => $namespace->get('no_order_id_passed', 'No valid order id passed.'),
756
            ]);
757
758
            return;
759
        }
760
761
        // Check whether the order has been modified in the meantime
762
        $lastOrderChange = new \DateTime($this->Request()->getParam('changed'));
763
        if ($order->getChanged() !== null && $order->getChanged()->getTimestamp() != $lastOrderChange->getTimestamp()) {
764
            $params = $this->Request()->getParams();
765
            $params['changed'] = $order->getChanged();
766
            $this->View()->assign([
767
                'success' => false,
768
                'data' => $params,
769
                'overwriteAble' => true,
770
                'message' => $namespace->get('order_has_been_changed', 'The order has been changed in the meantime. To prevent overwriting these changes, saving the order was aborted. Please close the order and re-open it.'),
771
            ]);
772
773
            return;
774
        }
775
776
        foreach ($positions as $position) {
777
            if (empty($position['id'])) {
778
                continue;
779
            }
780
            $model = Shopware()->Models()->find(Detail::class, $position['id']);
781
782
            // Check if the model was founded.
783
            if ($model instanceof \Shopware\Models\Order\Detail) {
784
                Shopware()->Models()->remove($model);
785
            }
786
        }
787
        // After each model has been removed to executes the doctrine flush.
788
        Shopware()->Models()->flush();
789
790
        $order->calculateInvoiceAmount();
791
        Shopware()->Models()->flush();
792
793
        $data = $this->getOrder($order->getId());
794
        $this->View()->assign([
795
            'success' => true,
796
            'data' => $data,
797
        ]);
798
    }
799
800
    /**
801
     * The batchProcessAction function handles the request of the batch window in order backend module.
802
     * It is responsible to create the order document for the passed parameters and updates the order
803
     * or|and payment status that passed for each order. If the order or payment status has been changed
804
     * the function will create for each order an status mail which will be assigned to the passed order
805
     * and will be displayed in the email panel on the right side of the batch window.
806
     * If the parameter "autoSend" is set to true (configurable over the checkbox in the form panel) each
807
     * created status mail will be send directly.
808
     */
809
    public function batchProcessAction()
810
    {
811
        $autoSend = $this->Request()->getParam('autoSend') === 'true';
812
        $orders = $this->Request()->getParam('orders', [0 => $this->Request()->getParams()]);
813
        $documentType = $this->Request()->getParam('docType');
814
        $documentMode = $this->Request()->getParam('mode');
815
        $addAttachments = $this->request->getParam('addAttachments') === 'true';
816
817
        /** @var Enlight_Components_Snippet_Namespace $namespace */
818
        $namespace = $this->get('snippets')->getNamespace('backend/order');
819
820
        if (empty($orders)) {
821
            $this->View()->assign([
822
                'success' => false,
823
                'data' => $this->Request()->getParams(),
824
                'message' => $namespace->get('no_order_id_passed', 'No valid order id passed.'),
825
            ]);
826
827
            return;
828
        }
829
830
        $modelManager = $this->get('models');
831
        /** @var \Shopware\Components\StateTranslatorServiceInterface $stateTranslator */
832
        $stateTranslator = $this->get('shopware.components.state_translator');
833
834
        $previousLocale = $this->getCurrentLocale();
835
836
        foreach ($orders as &$data) {
837
            $data['success'] = false;
838
            $data['errorMessage'] = $namespace->get('no_order_id_passed', 'No valid order id passed.');
839
840
            if (empty($data) || empty($data['id'])) {
841
                continue;
842
            }
843
844
            /** @var Order|null $order */
845
            $order = $modelManager->find(Order::class, $data['id']);
846
            if (!$order) {
847
                continue;
848
            }
849
850
            /*
851
                We have to flush the status changes directly, because the "createStatusMail" function in the
852
                sOrder.php core class, use the order data from the database. So we have to save the new status before we
853
                create the status mail
854
            */
855
            $statusBefore = $order->getOrderStatus();
856
            $clearedBefore = $order->getPaymentStatus();
857
858
            // Refresh the status models to return the new status data which will be displayed in the batch list
859
            if (!empty($data['status']) || $data['status'] === 0) {
860
                $order->setOrderStatus($modelManager->find(Status::class, $data['status']));
861
            }
862
            if (!empty($data['cleared'])) {
863
                $order->setPaymentStatus($modelManager->find(Status::class, $data['cleared']));
864
            }
865
866
            try {
867
                $modelManager->flush($order);
868
            } catch (Exception $e) {
869
                $data['success'] = false;
870
                $data['errorMessage'] = sprintf(
871
                    $namespace->get('save_order_failed', 'Error when saving the order. Error: %s'),
872
                    $e->getMessage()
873
                );
874
                continue;
875
            }
876
877
            /*
878
                The setOrder function of the Shopware_Components_Document change the currency of the shop.
879
                This would create a new Shop if we execute an flush(); Only create order documents when requested.
880
            */
881
            if ($documentType) {
882
                $this->createOrderDocuments($documentType, $documentMode, $order);
883
            }
884
885
            if ($previousLocale) {
886
                // This is necessary, since the "checkOrderStatus" method might change the locale due to translation issues
887
                // when sending an order status mail. Therefore we reset it here to the chosen backend language.
888
                $this->get('snippets')->setLocale($previousLocale);
889
                $this->get('snippets')->resetShop();
890
            }
891
892
            $data['paymentStatus'] = $stateTranslator->translateState(StateTranslatorService::STATE_PAYMENT, $modelManager->toArray($order->getPaymentStatus()));
893
            $data['orderStatus'] = $stateTranslator->translateState(StateTranslatorService::STATE_ORDER, $modelManager->toArray($order->getOrderStatus()));
894
895
            try {
896
                // The method '$this->checkOrderStatus()' (even its name would not imply that) sends mails and can fail
897
                // with an exception. Catch this exception, so the batch process does not abort.
898
                $data['mail'] = $this->checkOrderStatus($order, $statusBefore, $clearedBefore, $autoSend, $documentType, $addAttachments);
899
            } catch (\Exception $e) {
900
                $data['mail'] = null;
901
                $data['success'] = false;
902
                $data['errorMessage'] = sprintf(
903
                    $namespace->get('send_mail_failed', 'Error when sending mail. Error: %s'),
904
                    $e->getMessage()
905
                );
906
                continue;
907
            }
908
909
            $data['success'] = true;
910
            $data['errorMessage'] = null;
911
        }
912
913
        $this->View()->assign([
914
            'success' => true,
915
            'data' => $orders,
916
        ]);
917
    }
918
919
    /**
920
     * This function is called by the batch controller after all documents were created
921
     * It will read the created documents' hashes from database and merge them
922
     */
923
    public function mergeDocumentsAction()
924
    {
925
        $data = $this->Request()->getParam('data');
926
927
        // Disable Smarty rendering
928
        $this->Front()->Plugins()->ViewRenderer()->setNoRender();
929
930
        if ($data === null) {
931
            $this->View()->assign([
932
                'success' => false,
933
                'message' => 'No valid data passed.',
934
            ]);
935
936
            return;
937
        }
938
939
        $data = json_decode($data);
940
941
        if ($data->orders === null || count($data->orders) === 0) {
942
            $this->View()->assign([
943
                'success' => false,
944
                'message' => 'No valid order id passed.',
945
            ]);
946
947
            return;
948
        }
949
950
        $files = [];
951
        $query = $this->getOrderDocumentsQuery($data->orders, $data->docType);
952
        /** @var Order[] $models */
953
        $models = $query->getResult();
954
        foreach ($models as $model) {
955
            foreach ($model->getDocuments() as $document) {
956
                $files[] = $this->downloadFileFromFilesystem(sprintf('documents/%s.pdf', $document->getHash()));
957
            }
958
        }
959
960
        $this->mergeDocuments($files);
961
962
        // Remove temporary files
963
        foreach ($files as $file) {
964
            unlink($file);
965
        }
966
    }
967
968
    /**
969
     * The sendMailAction fired from the batch window in the order backend module when the user want to send the order
970
     * status mail manually.
971
     */
972
    public function sendMailAction()
973
    {
974
        $data = $this->Request()->getParams();
975
        $orderId = $this->request->getParam('orderId');
976
        $attachments = $this->request->getParam('attachment');
977
978
        /** @var Enlight_Components_Snippet_Namespace $namespace */
979
        $namespace = Shopware()->Snippets()->getNamespace('backend/order');
980
981
        if (empty($data)) {
982
            $this->View()->assign([
983
                'success' => false,
984
                'data' => $data,
985
                'message' => $namespace->get('no_data_passed', 'No mail data passed'),
986
            ]);
987
988
            return;
989
        }
990
991
        $mail = clone $this->container->get('mail');
992
        $mail->clearRecipients();
993
994
        $mailData = [
995
            'attachments' => $attachments,
996
            'subject' => $this->Request()->getParam('subject', ''),
997
            'fromMail' => $this->Request()->getParam('fromMail'),
998
            'fromName' => $this->Request()->getParam('fromName'),
999
            'to' => [$this->Request()->getParam('to')],
1000
            'isHtml' => $this->Request()->getParam('isHtml'),
1001
            'bodyHtml' => $this->Request()->getParam('contentHtml', ''),
1002
            'bodyText' => $this->Request()->getParam('content', ''),
1003
        ];
1004
1005
        /** @var Enlight_Event_EventManager $events */
1006
        $events = $this->get('events');
1007
        $mailData = $events->filter(
1008
            'Shopware_Controllers_Order_SendMail_Prepare',
1009
            $mailData,
1010
            [
1011
                'subject' => $this,
1012
                'orderId' => $orderId,
1013
                'mail' => $mail,
1014
            ]
1015
        );
1016
1017
        $mail->setSubject($mailData['subject']);
1018
1019
        $mail->setFrom($mailData['fromMail'], $mailData['fromName']);
1020
        $mail->addTo($mailData['to']);
1021
1022
        if ($mailData['isHtml']) {
1023
            $mail->setBodyHtml($mailData['bodyHtml']);
1024
        } else {
1025
            $mail->setBodyText($mailData['bodyText']);
1026
        }
1027
        $mail = $this->addAttachments($mail, $orderId, $mailData['attachments']);
1028
1029
        $mail->setAssociation(LogEntryBuilder::ORDER_ID_ASSOCIATION, $orderId);
1030
1031
        Shopware()->Modules()->Order()->sendStatusMail($mail);
1032
1033
        $this->View()->assign([
1034
            'success' => true,
1035
            'data' => $data,
1036
        ]);
1037
    }
1038
1039
    /**
1040
     * Deletes a document by the requested document id.
1041
     */
1042
    public function deleteDocumentAction()
1043
    {
1044
        $filesystem = $this->container->get('shopware.filesystem.private');
1045
        $documentId = $this->request->getParam('documentId');
1046
        /** @var \Doctrine\DBAL\Connection $connection */
1047
        $connection = $this->container->get('dbal_connection');
1048
        $queryBuilder = $connection->createQueryBuilder();
1049
1050
        try {
1051
            $documentHash = $queryBuilder->select('hash')
1052
                ->from('s_order_documents')
1053
                ->where('id = :documentId')
1054
                ->setParameter('documentId', $documentId)
1055
                ->execute()
1056
                ->fetchColumn();
1057
1058
            $queryBuilder = $connection->createQueryBuilder();
1059
            $queryBuilder->delete('s_order_documents')
1060
                ->where('id = :documentId')
1061
                ->setParameter('documentId', $documentId)
1062
                ->execute();
1063
1064
            $file = sprintf('documents/%s.pdf', $documentHash);
1065
            if ($filesystem->has($file)) {
1066
                $filesystem->delete($file);
1067
            }
1068
        } catch (\Exception $exception) {
1069
            $this->View()->assign([
1070
                'success' => false,
1071
                'errorMessage' => $exception->getMessage(),
1072
            ]);
1073
        }
1074
1075
        $this->View()->assign('success', true);
1076
    }
1077
1078
    /**
1079
     * Creates a mail by the requested orderId and assign it to the view.
1080
     */
1081
    public function createMailAction()
1082
    {
1083
        $orderId = (int) $this->Request()->getParam('orderId');
1084
        $mailTemplateName = $this->Request()->getParam('mailTemplateName', 'sORDERDOCUMENTS');
1085
1086
        /** @var Enlight_Components_Mail $mail */
1087
        $mail = Shopware()->Modules()->Order()->createStatusMail($orderId, 0, $mailTemplateName);
1088
1089
        $this->view->assign([
1090
            'mail' => [
1091
                'error' => false,
1092
                'content' => $mail->getPlainBodyText(),
1093
                'contentHtml' => $mail->getPlainBody(),
1094
                'subject' => $mail->getPlainSubject(),
1095
                'to' => implode(', ', $mail->getTo()),
1096
                'fromMail' => $mail->getFrom(),
1097
                'fromName' => $mail->getFromName(),
1098
                'sent' => false,
1099
                'isHtml' => !empty($mail->getPlainBody()),
1100
                'orderId' => $orderId,
1101
            ],
1102
        ]);
1103
    }
1104
1105
    /**
1106
     * Retrieves all available mail templates
1107
     */
1108
    public function getMailTemplatesAction()
1109
    {
1110
        $limit = (int) $this->Request()->getParam('limit', 100);
1111
        $offset = (int) $this->Request()->getParam('start', 0);
1112
        $order = $this->Request()->getParam('sort', []);
1113
        $filter = $this->Request()->getParam('filter', []);
1114
1115
        /** @var QueryBuilder $mailTemplatesQuery */
1116
        $mailTemplatesQuery = $this->getModelManager()->getRepository(Mail::class)->getMailsListQueryBuilder(
1117
            $filter,
1118
            $order,
1119
            $offset,
1120
            $limit
1121
        );
1122
1123
        $mailTemplates = $mailTemplatesQuery->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY);
1124
1125
        // Add a display name to the mail templates
1126
        $documentTypes = $this->getModelManager()->getRepository(DocumentType::class)->findAll();
1127
        $documentTypeNames = [];
1128
1129
        /** @var DocumentType $documentType */
1130
        foreach ($documentTypes as $documentType) {
1131
            $documentTypeNames['document_' . $documentType->getKey()] = $documentType->getName();
1132
        }
1133
        foreach ($mailTemplates as &$mailTemplate) {
1134
            if ($mailTemplate['name'] === 'sORDERDOCUMENTS') {
1135
                $mailTemplate['displayName'] = $this->get('snippets')->getNamespace(
1136
                    'backend/order/main'
1137
                )->get(
1138
                    'default_mail_template',
1139
                    'Default template'
1140
                );
1141
            } elseif (isset($documentTypeNames[$mailTemplate['name']])) {
1142
                $mailTemplate['displayName'] = $documentTypeNames[$mailTemplate['name']];
1143
            } else {
1144
                $mailTemplate['displayName'] = $mailTemplate['name'];
1145
            }
1146
        }
1147
1148
        $this->View()->assign([
1149
            'success' => true,
1150
            'data' => $mailTemplates,
1151
        ]);
1152
    }
1153
1154
    /**
1155
     * CRUD function of the document store. The function creates the order document with the passed
1156
     * request parameters.
1157
     */
1158
    public function createDocumentAction()
1159
    {
1160
        $orderId = $this->Request()->getParam('orderId');
1161
        $documentType = $this->Request()->getParam('documentType');
1162
1163
        // Needs to be called this early since $this->createDocument boots the
1164
        // shop, the order was made in, and thereby destroys the backend session
1165
        $translationComponent = $this->get('translation');
1166
1167
        if (!empty($orderId) && !empty($documentType)) {
1168
            $this->createDocument($orderId, $documentType);
1169
        }
1170
1171
        $query = $this->getRepository()->getOrdersQuery([['property' => 'orders.id', 'value' => $orderId]], null, 0, 1);
1172
        $query->setHydrationMode(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);
1173
        $paginator = $this->getModelManager()->createPaginator($query);
1174
        $order = $paginator->getIterator()->getArrayCopy();
1175
1176
        $order = $translationComponent->translateOrders($order);
1177
1178
        $this->View()->assign([
1179
            'success' => true,
1180
            'data' => $order,
1181
        ]);
1182
    }
1183
1184
    /**
1185
     * Fires when the user want to open a generated order document from the backend order module.
1186
     * Returns the created pdf file.
1187
     */
1188
    public function openPdfAction()
1189
    {
1190
        $filesystem = $this->container->get('shopware.filesystem.private');
1191
        $file = sprintf('documents/%s.pdf', basename($this->Request()->getParam('id', null)));
1192
1193
        if ($filesystem->has($file) === false) {
1194
            $this->View()->assign([
1195
                'success' => false,
1196
                'data' => $this->Request()->getParams(),
1197
                'message' => 'File not exist',
1198
            ]);
1199
1200
            return;
1201
        }
1202
1203
        // Disable Smarty rendering
1204
        $this->Front()->Plugins()->ViewRenderer()->setNoRender();
1205
        $this->Front()->Plugins()->Json()->setRenderer(false);
1206
1207
        $orderModel = Shopware()->Models()->getRepository(Document::class)->findBy(['hash' => $this->Request()->getParam('id')]);
1208
        $orderModel = Shopware()->Models()->toArray($orderModel);
1209
        $orderId = $orderModel[0]['documentId'];
1210
1211
        $response = $this->Response();
1212
        $response->headers->set('cache-control', 'public', true);
1213
        $response->headers->set('content-description', 'File Transfer');
1214
        $response->headers->set('content-disposition', 'attachment; filename=' . $orderId . '.pdf');
1215
        $response->headers->set('content-type', 'application/pdf');
1216
        $response->headers->set('content-transfer-encoding', 'binary');
1217
        $response->headers->set('content-length', $filesystem->getSize($file));
1218
        $response->sendHeaders();
1219
        $response->sendResponse();
1220
1221
        $upstream = $filesystem->readStream($file);
1222
        $downstream = fopen('php://output', 'wb');
1223
1224
        while (!feof($upstream)) {
1225
            fwrite($downstream, fread($upstream, 4096));
1226
        }
1227
    }
1228
1229
    /**
1230
     * Returns filterable partners
1231
     */
1232
    public function getPartnersAction()
1233
    {
1234
        $limit = $this->Request()->getParam('limit', 20);
1235
        $offset = $this->Request()->getParam('start', 0);
1236
1237
        /** @var \Doctrine\DBAL\Query\QueryBuilder $dbalBuilder */
1238
        $dbalBuilder = $this->get('dbal_connection')->createQueryBuilder();
1239
1240
        $data = $dbalBuilder
1241
            ->select(['SQL_CALC_FOUND_ROWS MAX(IFNULL(partner.company, orders.partnerID)) as name', 'orders.partnerID as `value`'])
1242
            ->from('s_order', 'orders')
1243
            ->leftJoin('orders', 's_emarketing_partner', 'partner', 'orders.partnerID = partner.idcode')
1244
            ->where('orders.partnerID IS NOT NULL')
1245
            ->andWhere('orders.partnerID != ""')
1246
            ->groupBy('orders.partnerID')
1247
            ->setFirstResult($offset)
1248
            ->setMaxResults($limit)
1249
            ->execute()
1250
            ->fetchAll();
1251
1252
        $total = (int) $this->get('dbal_connection')->fetchColumn('SELECT FOUND_ROWS()');
1253
1254
        $this->View()->assign(['success' => true, 'data' => $data, 'total' => $total]);
1255
    }
1256
1257
    /**
1258
     * Returns the shopware model manager
1259
     *
1260
     * @return Shopware\Components\Model\ModelManager
1261
     */
1262
    protected function getManager()
1263
    {
1264
        if (self::$manager === null) {
1265
            self::$manager = Shopware()->Models();
1266
        }
1267
1268
        return self::$manager;
1269
    }
1270
1271
    /**
1272
     * Helper function to get access on the static declared repository
1273
     *
1274
     * @return Shopware\Models\Order\Repository|null
1275
     */
1276
    protected function getRepository()
1277
    {
1278
        if (self::$repository === null) {
1279
            self::$repository = Shopware()->Models()->getRepository(Order::class);
1280
        }
1281
1282
        return self::$repository;
1283
    }
1284
1285
    /**
1286
     * Helper function to get access on the static declared repository
1287
     *
1288
     * @return Shopware\Models\Shop\Repository|null
1289
     */
1290
    protected function getShopRepository()
1291
    {
1292
        if (self::$shopRepository === null) {
1293
            self::$shopRepository = Shopware()->Models()->getRepository(Shop::class);
1294
        }
1295
1296
        return self::$shopRepository;
1297
    }
1298
1299
    /**
1300
     * Helper function to get access on the static declared repository
1301
     *
1302
     * @return Shopware\Models\Country\Repository|null
1303
     */
1304
    protected function getCountryRepository()
1305
    {
1306
        if (self::$countryRepository === null) {
1307
            self::$countryRepository = Shopware()->Models()->getRepository(Country::class);
1308
        }
1309
1310
        return self::$countryRepository;
1311
    }
1312
1313
    /**
1314
     * Helper function to get access on the static declared repository
1315
     *
1316
     * @return Shopware\Models\Payment\Repository|null
1317
     */
1318
    protected function getPaymentRepository()
1319
    {
1320
        if (self::$paymentRepository === null) {
1321
            self::$paymentRepository = Shopware()->Models()->getRepository(Payment::class);
1322
        }
1323
1324
        return self::$paymentRepository;
1325
    }
1326
1327
    /**
1328
     * Helper function to get access on the static declared repository
1329
     *
1330
     * @return Shopware\Models\Dispatch\Repository|null
1331
     */
1332
    protected function getDispatchRepository()
1333
    {
1334
        if (self::$dispatchRepository === null) {
1335
            self::$dispatchRepository = Shopware()->Models()->getRepository(Dispatch::class);
1336
        }
1337
1338
        return self::$dispatchRepository;
1339
    }
1340
1341
    /**
1342
     * @deprecated in 5.6, will be removed in 5.7 without a replacement
1343
     *
1344
     * Helper function to get access on the static declared repository
1345
     *
1346
     * @return \Shopware\Components\Model\ModelRepository
1347
     */
1348
    protected function getDocumentRepository()
1349
    {
1350
        trigger_error(sprintf('%s:%s is deprecated since Shopware 5.6 and will be removed with 5.7. Will be removed without replacement.', __CLASS__, __METHOD__), E_USER_DEPRECATED);
1351
1352
        if (self::$documentRepository === null) {
1353
            self::$documentRepository = Shopware()->Models()->getRepository(Document::class);
1354
        }
1355
1356
        return self::$documentRepository;
1357
    }
1358
1359
    /**
1360
     * @param array[]  $filter
1361
     * @param array[]  $sort
1362
     * @param int|null $offset
1363
     * @param int|null $limit
1364
     *
1365
     * @return array
1366
     */
1367
    protected function getList($filter, $sort, $offset, $limit)
1368
    {
1369
        $sort = $this->resolveSortParameter($sort);
1370
1371
        if ($this->container->getParameter('shopware.es.backend.enabled')) {
1372
            $repository = $this->container->get('shopware_attribute.order_repository');
1373
            $criteria = $this->createCriteria();
1374
            $result = $repository->search($criteria);
1375
1376
            $total = $result->getCount();
1377
            $ids = array_column($result->getData(), 'id');
1378
        } else {
1379
            $searchResult = $this->getRepository()->search($offset, $limit, $filter, $sort);
1380
            $total = $searchResult['total'];
1381
            $ids = array_column($searchResult['orders'], 'id');
1382
        }
1383
1384
        $orders = $this->getRepository()->getList($ids);
1385
        $documents = $this->getRepository()->getDocuments($ids);
1386
        $details = $this->getRepository()->getDetails($ids);
1387
        $payments = $this->getRepository()->getPayments($ids);
1388
1389
        $orders = $this->assignAssociation($orders, $documents, 'documents');
1390
        $orders = $this->assignAssociation($orders, $details, 'details');
1391
        $orders = $this->assignAssociation($orders, $payments, 'paymentInstances');
1392
1393
        /** @var Enlight_Components_Snippet_Namespace $namespace */
1394
        $namespace = $this->get('snippets')->getNamespace('frontend/salutation');
1395
1396
        /** @var \Shopware\Components\StateTranslatorServiceInterface $stateTranslator */
1397
        $stateTranslator = $this->get('shopware.components.state_translator');
1398
1399
        $numbers = [];
1400
        foreach ($orders as $orderKey => $order) {
1401
            $temp = array_column($order['details'], 'articleNumber');
1402
            $numbers = array_merge($numbers, (array) $temp);
1403
1404
            $orders[$orderKey]['orderStatus'] = $stateTranslator->translateState(StateTranslatorService::STATE_ORDER, $order['orderStatus']);
1405
            $orders[$orderKey]['paymentStatus'] = $stateTranslator->translateState(StateTranslatorService::STATE_PAYMENT, $order['paymentStatus']);
1406
        }
1407
1408
        $stocks = $this->getVariantsStock($numbers);
1409
1410
        $result = [];
1411
        foreach ($ids as $id) {
1412
            if (!array_key_exists($id, $orders)) {
1413
                continue;
1414
            }
1415
            $order = $orders[$id];
1416
1417
            $order['locale'] = $order['languageSubShop']['locale'];
1418
1419
            // Deprecated: use payment instance
1420
            $order['debit'] = $order['customer']['debit'];
1421
            $order['customerEmail'] = $order['customer']['email'];
1422
            $order['billing']['salutationSnippet'] = $namespace->get($order['billing']['salutation']);
1423
            $order['shipping']['salutationSnippet'] = $namespace->get($order['shipping']['salutation']);
1424
1425
            foreach ($order['details'] as &$orderDetail) {
1426
                $number = $orderDetail['articleNumber'];
1427
                $orderDetail['inStock'] = 0;
1428
                if (!isset($stocks[$number])) {
1429
                    continue;
1430
                }
1431
                $orderDetail['inStock'] = $stocks[$number];
1432
            }
1433
            $result[] = $order;
1434
        }
1435
1436
        return [
1437
            'success' => true,
1438
            'data' => $result,
1439
            'total' => $total,
1440
        ];
1441
    }
1442
1443
    /**
1444
     * Prepare address data - loads countryModel from a given countryId
1445
     *
1446
     * @return array
1447
     */
1448
    protected function prepareAddressData(array $data)
1449
    {
1450
        if (isset($data['countryId']) && !empty($data['countryId'])) {
1451
            $countryModel = $this->getCountryRepository()->find($data['countryId']);
1452
            if ($countryModel) {
1453
                $data['country'] = $countryModel;
1454
            }
1455
            unset($data['countryId']);
1456
        }
1457
1458
        if (isset($data['stateId']) && !empty($data['stateId'])) {
1459
            $stateModel = Shopware()->Models()->find(State::class, $data['stateId']);
1460
            if ($stateModel) {
1461
                $data['state'] = $stateModel;
1462
            }
1463
            unset($data['stateId']);
1464
        }
1465
1466
        return $data;
1467
    }
1468
1469
    /**
1470
     * @param array[] $sorts
1471
     *
1472
     * @return array[]
1473
     */
1474
    private function resolveSortParameter($sorts)
1475
    {
1476
        if (empty($sorts)) {
1477
            return [
1478
                ['property' => 'orders.orderTime', 'direction' => 'DESC'],
1479
            ];
1480
        }
1481
1482
        $resolved = [];
1483
        foreach ($sorts as $sort) {
1484
            $direction = $sort['direction'] ?: 'ASC';
1485
            switch (true) {
1486
                // Custom sort field for customer email
1487
                case $sort['property'] === 'customerEmail':
1488
                    $resolved[] = ['property' => 'customer.email', 'direction' => $direction];
1489
                    break;
1490
1491
                // Custom sort field for customer name
1492
                case $sort['property'] === 'customerName':
1493
                    $resolved[] = ['property' => 'billing.lastName', 'direction' => $direction];
1494
                    $resolved[] = ['property' => 'billing.firstName', 'direction' => $direction];
1495
                    $resolved[] = ['property' => 'billing.company', 'direction' => $direction];
1496
                    break;
1497
1498
                // Contains no sql prefix? add orders as default prefix
1499
                case strpos($sort['property'], '.') === false:
1500
                    $resolved[] = ['property' => 'orders.' . $sort['property'], 'direction' => $direction];
1501
                    break;
1502
1503
                // Already prefixed with an alias?
1504
                default:
1505
                    $resolved[] = $sort;
1506
            }
1507
        }
1508
1509
        return $resolved;
1510
    }
1511
1512
    /**
1513
     * @param array[] $orders
1514
     * @param array[] $associations
1515
     * @param string  $arrayKey
1516
     *
1517
     * @return array[]
1518
     */
1519
    private function assignAssociation($orders, $associations, $arrayKey)
1520
    {
1521
        foreach ($orders as &$order) {
1522
            $order[$arrayKey] = [];
1523
        }
1524
1525
        foreach ($associations as $association) {
1526
            $id = $association['orderId'];
1527
            $orders[$id][$arrayKey][] = $association;
1528
        }
1529
1530
        return $orders;
1531
    }
1532
1533
    /**
1534
     * Helper function to select a single order.
1535
     *
1536
     * @param int $id
1537
     */
1538
    private function getOrder($id)
1539
    {
1540
        $query = $this->getRepository()->getOrdersQuery([['property' => 'orders.id', 'value' => $id]], []);
1541
        $data = $query->getArrayResult();
1542
1543
        return $data[0];
1544
    }
1545
1546
    /**
1547
     * Simple helper function which actually merges a given array of document-paths
1548
     *
1549
     * @return string The created document's url
1550
     */
1551
    private function mergeDocuments(array $paths)
1552
    {
1553
        $pdf = new FPDI();
1554
1555
        foreach ($paths as $path) {
1556
            $numPages = $pdf->setSourceFile($path);
1557
            for ($i = 1; $i <= $numPages; ++$i) {
1558
                $template = $pdf->importPage($i);
1559
                $size = $pdf->getTemplateSize($template);
1560
                $pdf->AddPage('P', [$size['w'], $size['h']]);
1561
                $pdf->useTemplate($template);
1562
            }
1563
        }
1564
1565
        $hash = Random::getAlphanumericString(32);
1566
1567
        $pdf->Output($hash . '.pdf', 'D');
1568
1569
        $this->Response()->headers->set('content-type', 'application/x-download');
1570
    }
1571
1572
    /**
1573
     * Internal helper function which checks if the batch process needs a document creation.
1574
     *
1575
     * @param int                          $documentTypeId
1576
     * @param int                          $documentMode
1577
     * @param \Shopware\Models\Order\Order $order
1578
     */
1579
    private function createOrderDocuments($documentTypeId, $documentMode, $order)
1580
    {
1581
        if (!empty($documentTypeId)) {
1582
            $documentTypeId = (int) $documentTypeId;
1583
            $documentMode = (int) $documentMode;
1584
1585
            /** @var \Shopware\Models\Order\Document\Document[] $documents */
1586
            $documents = $order->getDocuments();
1587
1588
            // Create only not existing documents
1589
            if ($documentMode === 1) {
1590
                $alreadyCreated = false;
1591
                foreach ($documents as $document) {
1592
                    if ($document->getTypeId() === $documentTypeId) {
1593
                        $alreadyCreated = true;
1594
                        break;
1595
                    }
1596
                }
1597
                if ($alreadyCreated === false) {
1598
                    $this->createDocument($order->getId(), $documentTypeId);
1599
                }
1600
            } else {
1601
                $this->createDocument($order->getId(), $documentTypeId);
1602
            }
1603
        }
1604
    }
1605
1606
    /**
1607
     * Internal helper function to check if the order or payment status has been changed.
1608
     * If one of the status changed, the function will create a status mail.
1609
     * If the autoSend parameter is true, the created status mail will be sent directly,
1610
     * if addAttachments and documentType are true/selected as well, the according documents will be attached.
1611
     *
1612
     * @param Order                         $order
1613
     * @param \Shopware\Models\Order\Status $statusBefore
1614
     * @param \Shopware\Models\Order\Status $clearedBefore
1615
     * @param bool                          $autoSend
1616
     * @param int|null                      $documentTypeId
1617
     * @param bool                          $addAttachments
1618
     *
1619
     * @return array|null
1620
     */
1621
    private function checkOrderStatus($order, $statusBefore, $clearedBefore, $autoSend, $documentTypeId, $addAttachments)
1622
    {
1623
        $orderStatusChanged = $order->getOrderStatus()->getId() !== $statusBefore->getId();
1624
        $paymentStatusChanged = $order->getPaymentStatus()->getId() !== $clearedBefore->getId();
1625
        $documentMailSendable = $documentTypeId && $addAttachments;
0 ignored issues
show
Bug Best Practice introduced by
The expression $documentTypeId of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1626
        $mail = null;
1627
1628
        // Abort if autoSend isn't active and neither the order-, nor the payment-status changed
1629
        if (!$autoSend && !$orderStatusChanged && !$paymentStatusChanged) {
1630
            return null;
1631
        }
1632
1633
        if ($orderStatusChanged) {
1634
            // Generate mail with order status template
1635
            $mail = $this->getMailForOrder($order->getId(), $order->getOrderStatus()->getId());
1636
        } elseif ($paymentStatusChanged) {
1637
            // Generate mail with payment status template
1638
            $mail = $this->getMailForOrder($order->getId(), $order->getPaymentStatus()->getId());
1639
        } elseif ($documentMailSendable) {
1640
            // Generate mail with document template
1641
            $mail = $this->getMailForOrder($order->getId(), null, $documentTypeId);
1642
        }
1643
1644
        if (is_object($mail['mail'])) {
1645
            if ($addAttachments) {
1646
                // Attach documents
1647
                $document = $this->getDocument($documentTypeId, $order);
1648
                /** @var Enlight_Components_Mail $mailObject */
1649
                $mailObject = $mail['mail'];
1650
                $mail['mail'] = $this->addAttachments($mailObject, $order->getId(), [$document]);
1651
            }
1652
            if ($autoSend) {
1653
                // Send mail
1654
                /** @var Enlight_Components_Mail $mailObject */
1655
                $mailObject = $mail['mail'];
1656
                $result = Shopware()->Modules()->Order()->sendStatusMail($mailObject);
1657
                $mail['data']['sent'] = is_object($result);
1658
            }
1659
1660
            return $mail['data'];
1661
        }
1662
1663
        return null;
1664
    }
1665
1666
    /**
1667
     * @param int $documentTypeId
1668
     *
1669
     * @return array
1670
     */
1671
    private function getDocument($documentTypeId, Order $order)
1672
    {
1673
        $documentTypeId = (int) $documentTypeId;
1674
1675
        /** @var \Shopware\Models\Order\Document\Document $document */
1676
        foreach ($order->getDocuments()->toArray() as $document) {
1677
            if ($document->getTypeId() === $documentTypeId) {
1678
                return [
1679
                    'hash' => $document->getHash(),
1680
                    'type' => [
1681
                        [
1682
                            'id' => $document->getTypeId(),
1683
                            'name' => $document->getType()->getName(),
1684
                        ],
1685
                    ],
1686
                ];
1687
            }
1688
        }
1689
1690
        return $this->getDocumentFromDatabase($order->getId(), $documentTypeId);
1691
    }
1692
1693
    /**
1694
     * @param int $orderId
1695
     * @param int $documentTypeId
1696
     *
1697
     * @return array
1698
     */
1699
    private function getDocumentFromDatabase($orderId, $documentTypeId)
1700
    {
1701
        $queryBuilder = $this->container->get('dbal_connection')->createQueryBuilder();
1702
        $queryResult = $queryBuilder->select('doc.hash, template.id, template.name')
1703
            ->from('s_order_documents', 'doc')
1704
            ->join('doc', 's_core_documents', 'template', 'doc.type = template.id')
1705
            ->where('doc.orderID = :orderId')
1706
            ->andWhere('doc.type = :type')
1707
            ->setParameter('orderId', $orderId, \PDO::PARAM_INT)
1708
            ->setParameter('type', $documentTypeId, \PDO::PARAM_INT)
1709
            ->execute()
1710
            ->fetch(PDO::FETCH_ASSOC);
1711
1712
        if ($queryResult) {
1713
            return [
1714
                'hash' => $queryResult['hash'],
1715
                'type' => [
1716
                    [
1717
                        'id' => $queryResult['id'],
1718
                        'name' => $queryResult['name'],
1719
                    ],
1720
                ],
1721
            ];
1722
        }
1723
1724
        return [];
1725
    }
1726
1727
    /**
1728
     * Adds the requested attachments to the given $mail object
1729
     *
1730
     * @param int|string $orderId
1731
     *
1732
     * @return Enlight_Components_Mail
1733
     */
1734
    private function addAttachments(Enlight_Components_Mail $mail, $orderId, array $attachments = [])
1735
    {
1736
        $filesystem = $this->container->get('shopware.filesystem.private');
1737
1738
        foreach ($attachments as $attachment) {
1739
            $filePath = sprintf('documents/%s.pdf', $attachment['hash']);
1740
            $fileName = $this->getFileName($orderId, $attachment['type'][0]['id']);
1741
1742
            if ($filesystem->has($filePath) === false) {
1743
                continue;
1744
            }
1745
1746
            $mail->addAttachment($this->createAttachment($filePath, $fileName));
1747
        }
1748
1749
        return $mail;
1750
    }
1751
1752
    /**
1753
     * Creates a attachment by a file path.
1754
     *
1755
     * @param string $filePath
1756
     * @param string $fileName
1757
     *
1758
     * @return Zend_Mime_Part
1759
     */
1760
    private function createAttachment($filePath, $fileName)
1761
    {
1762
        $filesystem = $this->container->get('shopware.filesystem.private');
1763
1764
        $content = $filesystem->read($filePath);
1765
        $zendAttachment = new Zend_Mime_Part($content);
1766
        $zendAttachment->type = 'application/pdf';
1767
        $zendAttachment->disposition = Zend_Mime::DISPOSITION_ATTACHMENT;
1768
        $zendAttachment->encoding = Zend_Mime::ENCODING_BASE64;
1769
        $zendAttachment->filename = $fileName;
1770
1771
        return $zendAttachment;
1772
    }
1773
1774
    /**
1775
     * @param int|string $orderId
1776
     * @param int|string $typeId
1777
     * @param string     $fileExtension
1778
     *
1779
     * @return string
1780
     */
1781
    private function getFileName($orderId, $typeId, $fileExtension = '.pdf')
1782
    {
1783
        $localeId = $this->getOrderLocaleId($orderId);
1784
1785
        $translationReader = $this->container->get('translation');
1786
        $translations = $translationReader->read($localeId, 'documents', $typeId, true);
1787
1788
        if (empty($translations) || empty($translations['name'])) {
1789
            return $this->getDefaultName($typeId) . $fileExtension;
1790
        }
1791
1792
        return $translations['name'] . $fileExtension;
1793
    }
1794
1795
    /**
1796
     * Returns the locale id from the order
1797
     *
1798
     * @param int|string $orderId
1799
     *
1800
     * @return bool|string
1801
     */
1802
    private function getOrderLocaleId($orderId)
1803
    {
1804
        $queryBuilder = $this->container->get('dbal_connection')->createQueryBuilder();
1805
1806
        return $queryBuilder->select('language')
1807
            ->from('s_order')
1808
            ->where('id = :orderId')
1809
            ->setParameter('orderId', $orderId)
1810
            ->execute()
1811
            ->fetchColumn();
1812
    }
1813
1814
    /**
1815
     * Gets the default name from the document template
1816
     *
1817
     * @param int|string $typeId
1818
     *
1819
     * @return bool|string
1820
     */
1821
    private function getDefaultName($typeId)
1822
    {
1823
        /** @var \Doctrine\DBAL\Query\QueryBuilder $queryBuilder */
1824
        $queryBuilder = $this->container->get('dbal_connection')->createQueryBuilder();
1825
1826
        return $queryBuilder->select('name')
1827
            ->from('s_core_documents')
1828
            ->where('`id` = :typeId')
1829
            ->setParameter('typeId', $typeId)
1830
            ->execute()
1831
            ->fetchColumn();
1832
    }
1833
1834
    /**
1835
     * Internal helper function which is used from the batch function and the createDocumentAction.
1836
     * The batch function fired from the batch window to create multiple documents for many orders.
1837
     * The createDocumentAction fired from the detail page when the user clicks the "create Document button"
1838
     *
1839
     * @param int $orderId
1840
     * @param int $documentType
1841
     *
1842
     * @return bool
1843
     */
1844
    private function createDocument($orderId, $documentType)
1845
    {
1846
        $renderer = strtolower($this->Request()->getParam('renderer', 'pdf')); // html / pdf
1847
        if (!in_array($renderer, ['html', 'pdf'])) {
1848
            $renderer = 'pdf';
1849
        }
1850
1851
        $deliveryDate = $this->Request()->getParam('deliveryDate');
1852
        if (!empty($deliveryDate)) {
1853
            $deliveryDate = new \DateTime($deliveryDate);
1854
            $deliveryDate = $deliveryDate->format('d.m.Y');
1855
        }
1856
1857
        $displayDate = $this->Request()->getParam('displayDate');
1858
        if (!empty($displayDate)) {
1859
            $displayDate = new \DateTime($displayDate);
1860
            $displayDate = $displayDate->format('d.m.Y');
1861
        }
1862
1863
        $document = Shopware_Components_Document::initDocument(
1864
            $orderId,
1865
            $documentType,
1866
            [
1867
                'netto' => (bool) $this->Request()->getParam('taxFree', false),
1868
                'bid' => $this->Request()->getParam('invoiceNumber'),
1869
                'voucher' => $this->Request()->getParam('voucher'),
1870
                'date' => $displayDate,
1871
                'delivery_date' => $deliveryDate,
1872
                // Don't show shipping costs on delivery note #SW-4303
1873
                'shippingCostsAsPosition' => (int) $documentType !== 2,
1874
                '_renderer' => $renderer,
1875
                '_preview' => $this->Request()->getParam('preview', false),
1876
                '_previewForcePagebreak' => $this->Request()->getParam('pageBreak'),
1877
                '_previewSample' => $this->Request()->getParam('sampleData'),
1878
                'docComment' => $this->Request()->getParam('docComment'),
1879
                'forceTaxCheck' => $this->Request()->getParam('forceTaxCheck', false),
1880
            ]
1881
        );
1882
        $document->render();
1883
1884
        if ($renderer === 'html') {
1885
            exit;
1886
        }
1887
1888
        return true;
1889
    }
1890
1891
    /**
1892
     * Internal helper function which insert the order detail association data into the passed data array
1893
     *
1894
     * @param array $data
1895
     *
1896
     * @return array|null
1897
     */
1898
    private function getPositionAssociatedData($data)
1899
    {
1900
        // Checks if the status id for the position is passed and search for the assigned status model
1901
        if ($data['statusId'] >= 0) {
1902
            $data['status'] = Shopware()->Models()->find(DetailStatus::class, $data['statusId']);
1903
        } else {
1904
            unset($data['status']);
1905
        }
1906
1907
        // Checks if the tax id for the position is passed and search for the assigned tax model
1908
        if (!empty($data['taxId'])) {
1909
            $tax = Shopware()->Models()->find(Tax::class, $data['taxId']);
1910
            if ($tax instanceof \Shopware\Models\Tax\Tax) {
1911
                $data['tax'] = $tax;
1912
                $data['taxRate'] = $tax->getTax();
1913
            }
1914
        } else {
1915
            unset($data['tax']);
1916
        }
1917
1918
        /** @var ArticleDetail|null $variant */
1919
        $variant = Shopware()->Models()->getRepository(ArticleDetail::class)
1920
            ->findOneBy(['number' => $data['articleNumber']]);
1921
1922
        // Load ean, unit and pack unit (translate if needed)
1923
        if ($variant) {
1924
            $data['ean'] = $variant->getEan() ?: $variant->getArticle()->getMainDetail()->getEan();
1925
            /** @var Unit|null $unit */
1926
            $unit = $variant->getUnit() ?: $variant->getArticle()->getMainDetail()->getUnit();
1927
            $data['unit'] = $unit ? $unit->getName() : null;
1928
            $data['packunit'] = $variant->getPackUnit() ?: $variant->getArticle()->getMainDetail()->getPackUnit();
1929
1930
            $languageData = Shopware()->Db()->fetchRow(
1931
                'SELECT s_core_shops.default, s_order.language AS languageId
1932
                FROM s_core_shops
1933
                INNER JOIN s_order ON s_order.language = s_core_shops.id
1934
                WHERE s_order.id = :orderId
1935
                LIMIT 1',
1936
                [
1937
                    'orderId' => $data['orderId'],
1938
                ]
1939
            );
1940
1941
            if (!$languageData['default']) {
1942
                $translator = $this->container->get('translation');
1943
1944
                // Translate unit
1945
                if ($unit) {
1946
                    $unitTranslation = $translator->read(
1947
                        $languageData['languageId'],
1948
                        'config_units'
1949
                    );
1950
1951
                    $data['unit'] = $unit->getName();
1952
                    if (!empty($unitTranslation[$unit->getId()]['description'])) {
1953
                        $data['unit'] = $unitTranslation[$unit->getId()]['description'];
1954
                    }
1955
                }
1956
1957
                $productTranslation = [];
1958
1959
                // Load variant translations if we are adding a variant to the order
1960
                if ($variant->getId() != $variant->getArticle()->getMainDetail()->getId()) {
1961
                    $productTranslation = $translator->read(
1962
                        $languageData['languageId'],
1963
                        'variant',
1964
                        $variant->getId()
1965
                    );
1966
                }
1967
1968
                // Load product translations if we are adding a main product or the variant translation is incomplete
1969
                if ($variant->getId() == $variant->getArticle()->getMainDetail()->getId()
1970
                    || empty($productTranslation['packUnit'])
1971
                ) {
1972
                    $productTranslation = $translator->read(
1973
                        $languageData['languageId'],
1974
                        'article',
1975
                        $variant->getArticle()->getId()
1976
                    );
1977
                }
1978
1979
                if (!empty($productTranslation['packUnit'])) {
1980
                    $data['packUnit'] = $productTranslation['packUnit'];
1981
                }
1982
            }
1983
        }
1984
1985
        return $data;
1986
    }
1987
1988
    /**
1989
     * Internal helper function which insert the order association data into the passed data array.
1990
     *
1991
     * @return array
1992
     */
1993
    private function getAssociatedData(array $data)
1994
    {
1995
        // Check if a customer id has passed and fill the customer element with the associated customer model
1996
        if (!empty($data['customerId'])) {
1997
            $data['customer'] = Shopware()->Models()->find(Customer::class, $data['customerId']);
1998
        } else {
1999
            //if no customer id passed, we have to unset the array element, otherwise the existing customer model would be overwritten
2000
            unset($data['customer']);
2001
        }
2002
2003
        // If a payment id passed, load the associated payment model
2004
        if (!empty($data['paymentId'])) {
2005
            $data['payment'] = Shopware()->Models()->find(Payment::class, $data['paymentId']);
2006
        } else {
2007
            unset($data['payment']);
2008
        }
2009
2010
        // If a dispatch id is passed, load the associated dispatch model
2011
        if (!empty($data['dispatchId'])) {
2012
            $data['dispatch'] = Shopware()->Models()->find(Dispatch::class, $data['dispatchId']);
2013
        } else {
2014
            unset($data['dispatch']);
2015
        }
2016
2017
        // If a shop id is passed, load the associated shop model
2018
        if (!empty($data['shopId'])) {
2019
            $data['shop'] = Shopware()->Models()->find(Shop::class, $data['shopId']);
2020
        } else {
2021
            unset($data['shop']);
2022
        }
2023
2024
        // If a status id is passed, load the associated order status model
2025
        if (isset($data['status']) && $data['status'] !== null) {
2026
            $data['orderStatus'] = Shopware()->Models()->find(Status::class, $data['status']);
2027
        } else {
2028
            unset($data['orderStatus']);
2029
        }
2030
2031
        // If a payment status id is passed, load the associated payment status model
2032
        if (isset($data['cleared']) && $data['cleared'] !== null) {
2033
            $data['paymentStatus'] = Shopware()->Models()->find(Status::class, $data['cleared']);
2034
        } else {
2035
            unset($data['paymentStatus']);
2036
        }
2037
2038
        // The documents will be created over the "createDocumentAction" so we have to unset the array element, otherwise the
2039
        // created documents models would be overwritten.
2040
        // For now the paymentInstances information is not editable, so it's just discarded at this point
2041
        unset($data['documents'], $data['paymentInstances']);
2042
2043
        $data['billing'] = $this->prepareAddressData($data['billing'][0]);
2044
        $data['shipping'] = $this->prepareAddressData($data['shipping'][0]);
2045
2046
        // Unset calculated values
2047
        unset($data['invoiceAmountNet'], $data['invoiceAmountEuro']);
2048
2049
        // At last we return the prepared associated data
2050
        return $data;
2051
    }
2052
2053
    /**
2054
     * Creates the status mail order for the passed order id and new status object.
2055
     *
2056
     * @param int      $orderId
2057
     * @param int|null $statusId
2058
     * @param int|null $documentTypeId
2059
     *
2060
     * @throws \Doctrine\DBAL\DBALException
2061
     *
2062
     * @return array
2063
     */
2064
    private function getMailForOrder($orderId, $statusId, $documentTypeId = null)
2065
    {
2066
        $templateName = null;
2067
2068
        if ($documentTypeId !== null) {
2069
            $templateName = $this->getTemplateNameForDocumentTypeId($documentTypeId);
2070
        }
2071
2072
        /** @var Enlight_Components_Mail $mail */
2073
        $mail = Shopware()->Modules()->Order()->createStatusMail($orderId, (int) $statusId, $templateName);
2074
2075
        if ($mail instanceof Enlight_Components_Mail) {
0 ignored issues
show
Bug introduced by
The class Enlight_Components_Mail does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
2076
            return [
2077
                'mail' => $mail,
2078
                'data' => [
2079
                    'error' => false,
2080
                    'content' => $mail->getPlainBodyText(),
2081
                    'contentHtml' => $mail->getPlainBody(),
2082
                    'subject' => $mail->getPlainSubject(),
2083
                    'to' => implode(', ', $mail->getTo()),
2084
                    'fromMail' => $mail->getFrom(),
2085
                    'fromName' => $mail->getFromName(),
2086
                    'sent' => false,
2087
                    'isHtml' => !empty($mail->getPlainBody()),
2088
                    'orderId' => $orderId,
2089
                ],
2090
            ];
2091
        }
2092
2093
        return [];
2094
    }
2095
2096
    /**
2097
     * @param string[] $numbers
2098
     *
2099
     * @return array
2100
     */
2101
    private function getVariantsStock(array $numbers)
2102
    {
2103
        $query = Shopware()->Container()->get('dbal_connection')->createQueryBuilder();
2104
        $query->select(['variant.ordernumber', 'variant.instock']);
2105
        $query->from('s_articles_details', 'variant');
2106
        $query->where('variant.ordernumber IN (:numbers)');
2107
        $query->setParameter(':numbers', $numbers, Connection::PARAM_STR_ARRAY);
2108
2109
        return $query->execute()->fetchAll(PDO::FETCH_KEY_PAIR);
2110
    }
2111
2112
    /**
2113
     * @param string $path
2114
     *
2115
     * @return string
2116
     */
2117
    private function downloadFileFromFilesystem($path)
2118
    {
2119
        $filesystem = $this->container->get('shopware.filesystem.private');
2120
        $tmpFile = tempnam(sys_get_temp_dir(), 'merge_document');
2121
2122
        $downstream = fopen($tmpFile, 'wb');
2123
        stream_copy_to_stream($filesystem->readStream($path), $downstream);
2124
2125
        return $tmpFile;
2126
    }
2127
2128
    /**
2129
     * @return SearchCriteria
2130
     */
2131
    private function createCriteria()
2132
    {
2133
        $request = $this->Request();
2134
        $criteria = new SearchCriteria(Order::class);
2135
2136
        $criteria->offset = $request->getParam('start', 0);
2137
        $criteria->limit = $request->getParam('limit', 30);
2138
        $criteria->ids = $request->getParam('ids', []);
2139
        $criteria->sortings = $request->getParam('sort', []);
2140
        $conditions = $request->getParam('filter', []);
2141
2142
        if ($orderId = $this->Request()->getParam('orderID')) {
2143
            $criteria->ids[] = (int) $orderId;
2144
        }
2145
2146
        $mapped = [];
2147
        foreach ($conditions as $condition) {
2148
            if ($condition['property'] === 'free') {
2149
                $criteria->term = $condition['value'];
2150
                continue;
2151
            }
2152
2153
            if ($condition['property'] === 'billing.countryId') {
2154
                $condition['property'] = 'billingCountryId';
2155
            } elseif ($condition['property'] === 'shipping.countryId') {
2156
                $condition['property'] = 'shippingCountryId';
2157
            } else {
2158
                $name = explode('.', $condition['property']);
2159
                $name = array_pop($name);
2160
                $condition['property'] = $name;
2161
            }
2162
2163
            if ($condition['property'] === 'to') {
2164
                $condition['value'] = (new DateTime($condition['value']))->format('Y-m-d');
2165
                $condition['property'] = 'orderTime';
2166
                $condition['expression'] = '<=';
2167
            }
2168
2169
            if ($condition['property'] === 'from') {
2170
                $condition['value'] = (new DateTime($condition['value']))->format('Y-m-d');
2171
                $condition['property'] = 'orderTime';
2172
                $condition['expression'] = '>=';
2173
            }
2174
            $mapped[] = $condition;
2175
        }
2176
2177
        foreach ($criteria->sortings as &$sorting) {
2178
            if ($sorting['property'] === 'customerEmail') {
2179
                $sorting['property'] = 'email.raw';
2180
            }
2181
            if ($sorting['property'] === 'customerName') {
2182
                $sorting['property'] = 'lastname.raw';
2183
            }
2184
            if ($sorting['property'] === 'number') {
2185
                $sorting['property'] = 'number.raw';
2186
            }
2187
        }
2188
2189
        $criteria->conditions = $mapped;
2190
2191
        return $criteria;
2192
    }
2193
2194
    /**
2195
     * @param int|null $documentTypeId
2196
     *
2197
     * @throws \Doctrine\DBAL\DBALException
2198
     *
2199
     * @return string
2200
     */
2201
    private function getTemplateNameForDocumentTypeId($documentTypeId = null)
2202
    {
2203
        // Generic fallback template
2204
        $templateName = 'sORDERDOCUMENTS';
2205
2206
        if ($documentTypeId === null) {
2207
            return $templateName;
2208
        }
2209
2210
        $statement = $this->container->get('dbal_connection')
2211
            ->prepare('SELECT `name` FROM `s_core_config_mails` WHERE `name` = (SELECT CONCAT("document_", `key`) FROM `s_core_documents` WHERE id=:documentTypeId)');
2212
2213
        $statement->bindValue('documentTypeId', (int) $documentTypeId, \PDO::PARAM_INT);
2214
        $statement->execute();
2215
        $result = $statement->fetch(\PDO::FETCH_ASSOC);
2216
2217
        if (!empty($result) || !array_key_exists('name', $result)) {
2218
            $templateName = $result['name'];
2219
        }
2220
2221
        return $templateName;
2222
    }
2223
2224
    /**
2225
     * @return \Shopware\Models\Shop\Locale|null
2226
     */
2227
    private function getCurrentLocale()
2228
    {
2229
        $user = $this->get('auth')->getIdentity();
2230
2231
        return $user->locale;
2232
    }
2233
}
2234