Issues (183)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/bb-modules/Cart/Service.php (2 issues)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * BoxBilling
4
 *
5
 * @copyright BoxBilling, Inc (http://www.boxbilling.com)
6
 * @license   Apache-2.0
7
 *
8
 * Copyright BoxBilling, Inc
9
 * This source file is subject to the Apache-2.0 License that is bundled
10
 * with this source code in the file LICENSE
11
 */
12
13
namespace Box\Mod\Cart;
14
15
use Box\InjectionAwareInterface;
16
17
class Service implements InjectionAwareInterface
18
{
19
20
    protected $di;
21
22 43
    public function setDi($di)
23
    {
24 43
        $this->di = $di;
25 43
    }
26
27 1
    public function getDi()
28
    {
29 1
        return $this->di;
30
    }
31
32 1
    public function getSearchQuery($data)
33
    {
34
        $sql = "
35
            SELECT cart.id FROM cart
36
            LEFT JOIN currency ON cart.currency_id = currency.id
37 1
            LEFT JOIN promo ON cart.promo_id = promo.id";
38
39 1
        return array($sql, array());
40
    }
41
42
    /**
43
     * @return \Model_Cart
44
     */
45 8
    public function getSessionCart()
46
    {
47 3
        $sqlBindings = array(':session_id' => $this->di['session']->getId());
48 3
        $cart        = $this->di['db']->findOne('Cart', 'session_id = :session_id', $sqlBindings);
49
50 3
        if ($cart instanceof \Model_Cart) {
51 1
            return $cart;
52
        }
53
54 2
        $cc = $this->di['mod_service']('currency');
55
56 2
        if ($this->di['session']->get('client_id')) {
57 1
            $client_id = $this->di['session']->get('client_id');
58 1
            $currency  = $cc->getCurrencyByClientId($client_id);
59 1
        } else {
60 1
            $currency = $cc->getDefault();
61
        }
62
63 8
        $cart              = $this->di['db']->dispense('Cart');
64 2
        $cart->session_id  = $this->di['session']->getId();
65 2
        $cart->currency_id = $currency->id;
66 2
        $cart->created_at  = date('Y-m-d H:i:s');
67 2
        $cart->updated_at  = date('Y-m-d H:i:s');
68 2
        $this->di['db']->store($cart);
69
70 2
        return $cart;
71
    }
72
73 6
    public function addItem(\Model_Cart $cart, \Model_Product $product, array $data)
74
    {
75 6
        $event_params = array_merge($data, array('cart_id' => $cart->id, 'product_id' => $product->id));
76 6
        $this->di['events_manager']->fire(array('event' => 'onBeforeProductAddedToCart', 'params' => $event_params));
77
78 6
        $productService = $product->getService();
79
80 6
        if ($this->isRecurrentPricing($product)) {
81
            $required = array(
82 2
                'period' => 'Period parameter not passed',
83 2
            );
84 2
            $this->di['validator']->checkRequiredParamsForArray($required, $data);
85
86 1
            if (!$this->isPeriodEnabledForProduct($product, $data['period'])) {
87 1
                throw new \Box_Exception('Selected billing period is not valid');
88
            }
89
        }
90
91 4
        $qty =  $this->di['array_get']($data, 'quantity', 1);
92
        // check stock
93 4
        if (!$this->isStockAvailable($product, $qty)) {
94 1
            throw new \Box_Exception("I'm afraid we are out of stock.");
95
        }
96
97 3
        $addons =  $this->di['array_get']($data, 'addons', array());
98 3
        unset($data['id']);
99 3
        unset($data['addons']);
100
101 3
        $list   = array();
102 3
        $list[] = array(
103 3
            'product' => $product,
104 3
            'config'  => $data,
105
        );
106
107
        //check for required domain product
108 3
        if (method_exists($productService, 'getDomainProductFromConfig')) {
109 1
            $dc = $productService->getDomainProductFromConfig($product, $data);
110 1
            if (isset($dc['config']) && $dc['product'] && $dc['product'] instanceof \Model_Product) {
111 1
                $list[] = $dc;
112 1
            }
113 1
        }
114
115 3
        $productService = $this->di['mod_service']('Product');
116 3
        foreach ($addons as $id => $ac) {
117
            if (isset($ac['selected']) && (bool)$ac['selected']) {
118
                $addon = $productService->getAddonById($id);
119
                if ($addon instanceof \Model_Product) {
120
                    if ($this->isRecurrentPricing($addon)) {
121
122
                        $required = array(
123
                            'period' => 'Addon period parameter not passed',
124
                        );
125
                        $this->di['validator']->checkRequiredParamsForArray($required, $ac);
126
127
                        if (!$this->isPeriodEnabledForProduct($addon, $ac['period'])) {
128
                            throw new \Box_Exception('Selected billing period is not valid for addon');
129
                        }
130
                    }
131
                    $ac['parent_id'] = $product->id;
132
133
                    $list[] = array(
134
                        'product' => $addon,
135
                        'config'  => $ac,
136
                    );
137
                } else {
138
                    error_log('Addon not found by id ' . $id);
139
                }
140
            }
141 3
        }
142
143 3
        foreach ($list as $c) {
144 3
            $productFromList = $c['product'];
145 3
            $productFromListConfig = $c['config'];
146
147 3
            $productServiceFromList = $productFromList->getService();
148
149
            //@deprecated logic
150 3
            if (method_exists($productServiceFromList, 'prependOrderConfig')) {
151 1
                $productFromListConfig = $productServiceFromList->prependOrderConfig($productFromList, $productFromListConfig);
152 1
            }
153
154 3
            if (method_exists($productServiceFromList, 'attachOrderConfig')) {
155 1
                $model = $this->di['db']->load('Product', $productFromList->id);
156 1
                $productFromListConfig    = $productServiceFromList->attachOrderConfig($model, $productFromListConfig);
157 1
            }
158 3
            if (method_exists($productServiceFromList, 'validateOrderData')) {
159 2
                $productServiceFromList->validateOrderData($productFromListConfig);
160 2
            }
161 3
            if (method_exists($productServiceFromList, 'validateCustomForm')) {
162 1
                $productServiceFromList->validateCustomForm($productFromListConfig, $this->di['db']->toArray($productFromList));
163 1
            }
164 3
            $this->addProduct($cart, $productFromList, $productFromListConfig);
165 3
        }
166
167 3
        $this->di['logger']->info('Added "%s" to shopping cart', $product->title);
168
169 3
        $this->di['events_manager']->fire(array('event' => 'onAfterProductAddedToCart', 'params' => $event_params));
170
171 3
        return true;
172
    }
173
174 2
    public function isStockAvailable(\Model_Product $product, $qty)
175
    {
176 2
        if ($product->stock_control) {
177 1
            return ($product->quantity_in_stock >= $qty);
178
        }
179
180 1
        return TRUE;
181
    }
182
183 1
    public function isRecurrentPricing(\Model_Product $model)
184
    {
185 1
        $productTable = $model->getTable();
186 1
        $pricing        = $productTable->getPricingArray($model);
187 1
        return (isset($pricing['type']) && $pricing['type'] == \Model_ProductPayment::RECURRENT);
188
    }
189
190 2
    public function isPeriodEnabledForProduct(\Model_Product $model, $period)
191
    {
192 2
        $productTable = $model->getTable();
193 2
        $pricing        = $productTable->getPricingArray($model);
194 2
        if ($pricing['type'] == \Model_ProductPayment::RECURRENT) {
195 1
            return (bool)$pricing['recurrent'][$period]['enabled'];
196
        }
197
198 1
        return true;
199
    }
200
201 1
    protected function addProduct(\Model_Cart $cart, \Model_Product $product, array $data)
202
    {
203 1
        $item             = $this->di['db']->dispense('CartProduct');
204 1
        $item->cart_id    = $cart->id;
205 1
        $item->product_id = $product->id;
206 1
        $item->config     = json_encode($data);
207 1
        $this->di['db']->store($item);
208
209 1
        return true;
210
    }
211
212 2
    public function removeProduct(\Model_Cart $cart, $id, $removeAddons = true)
213
    {
214
        $bindings = array(
215 2
            ':cart_id' => $cart->id,
216
            ':id'      => $id
217 2
        );
218
219 2
        $cartProduct = $this->di['db']->findOne('CartProduct', 'id = :id AND cart_id = :cart_id', $bindings);
220 2
        if (!$cartProduct instanceof \Model_CartProduct) {
221 1
            throw new \Box_Exception('Product not found');
222
        }
223
224 1
        if ($removeAddons) {
225 1
            $allCartProducts = $this->di['db']->find('CartProduct', 'cart_id = :cart_id', array(':cart_id' => $cart->id));
226 1
            foreach ((array)$allCartProducts as $cProduct) {
227 1
                $config = json_decode($cProduct->config, true);
228 1
                if (isset($config['parent_id']) && $config['parent_id'] == $cartProduct->product_id) {
229
                    $this->di['db']->trash($cProduct);
230
                    $this->di['logger']->info('Removed product addon from shopping cart');
231
                }
232 1
            }
233 1
        }
234
235 1
        $this->di['db']->trash($cartProduct);
236
237 1
        $this->di['logger']->info('Removed product from shopping cart');
238
239 1
        return true;
240
    }
241
242 1
    public function changeCartCurrency(\Model_Cart $cart, \Model_Currency $currency)
243
    {
244 1
        $cart->currency_id = $currency->id;
245 1
        $this->di['db']->store($cart);
246
247 1
        $this->di['logger']->info('Changed shopping cart #%s currency to %s', $cart->id, $currency->title);
248
249 1
        return true;
250
    }
251
252 1
    public function resetCart(\Model_Cart $cart)
253
    {
254 1
        $cartProducts = $this->di['db']->find('CartProduct', 'cart_id = :cart_id', array(':cart_id' => $cart->id));
255 1
        foreach ($cartProducts as $cartProduct) {
256 1
            $this->di['db']->trash($cartProduct);
257 1
        }
258 1
        $cart->promo_id   = NULL;
259 1
        $cart->updated_at = date('Y-m-d H:i:s');
260 1
        $this->di['db']->store($cart);
261
262 1
        return true;
263
    }
264
265 1
    public function removePromo(\Model_Cart $cart)
266
    {
267 1
        $cart->promo_id   = NULL;
268 1
        $cart->updated_at = date('Y-m-d H:i:s');
269 1
        $this->di['db']->store($cart);
270
271 1
        $this->di['logger']->info('Removed promo code from shopping cart #%s', $cart->id);
272
273 1
        return true;
274
    }
275
276 3
    public function applyPromo(\Model_Cart $cart, \Model_Promo $promo)
277
    {
278 3
        if ($cart->promo_id == $promo->id) {
279 1
            return true;
280
        }
281
282 2
        if ($this->isEmptyCart($cart)) {
283 1
            throw new \Box_Exception('Add products to cart before applying promo code');
284
        }
285
286 1
        $cart->promo_id = $promo->id;
287 1
        $this->di['db']->store($cart);
288
289 1
        $this->di['logger']->info('Applied promo code %s to shopping cart', $promo->code);
290
291 1
        return true;
292
    }
293
294 1
    protected function isEmptyCart(\Model_Cart $cart)
295
    {
296 1
        $cartProducts = $this->di['db']->find('CartProduct', 'cart_id = :cart_id', array(':cart_id' => $cart->id));
297
298 1
        return (count($cartProducts) == 0);
299
    }
300
301 1
    public function rm(\Model_Cart $cart)
302
    {
303 1
        $cartProducts = $this->di['db']->find('CartProduct', 'cart_id = :cart_id', array(':cart_id' => $cart->id));
304
305 1
        foreach ($cartProducts as $cartProduct) {
306 1
            $this->di['db']->trash($cartProduct);
307 1
        }
308
309 1
        $this->di['db']->trash($cart);
310
311 1
        return true;
312
    }
313
314 1
    public function toApiArray(\Model_Cart $model, $deep = false, $identity = null)
315
    {
316 1
        $products = $this->getCartProducts($model);
317
318 1
        $currency = $this->di['db']->getExistingModelById('Currency', $model->currency_id);
319
320 1
        $items          = array();
321 1
        $total          = 0;
322 1
        $cart_discount  = 0;
323 1
        $items_discount = 0;
324 1
        foreach ($products as $product) {
325 1
            $p = $this->cartProductToApiArray($product);
326 1
            $total += $p['total'] + $p['setup_price'];
327 1
            $items_discount += $p['discount'];
328 1
            $items[] = $p;
329 1
        }
330
331 1
        if ($model->promo_id) {
332
            $promo = $this->di['db']->getExistingModelById('Promo', $model->promo_id, 'Promo not found');
333
            $promocode = $promo->code;
334
        } else {
335 1
            $promocode = NULL;
336
        }
337
338 1
        $currencyService = $this->di['mod_service']('currency');
339
        $result          = array(
340 1
            'promocode' => $promocode,
341 1
            'discount'  => $items_discount,
342 1
            'subtotal'  => $total,
343 1
            'total'     => $total - $items_discount,
344 1
            'items'     => $items,
345 1
            'currency'  => $currencyService->toApiArray($currency),
346
        );
347 1
348
        return $result;
349
    }
350 4
351
    public function isClientAbleToUsePromo(\Model_Client $client, \Model_Promo $promo)
352 4
    {
353 1
        if (!$this->promoCanBeApplied($promo)) {
354
            return false;
355
        }
356 3
357 1
        if (!$promo->once_per_client) {
358
            return true;
359
        }
360 2
361
        return !$this->clientHadUsedPromo($client, $promo);
362
    }
363 5
364
    public function promoCanBeApplied(\Model_Promo $promo)
365 5
    {
366 1
        if (!$promo->active) {
367
            return false;
368
        }
369 4
370 1
        if ($promo->maxuses && $promo->maxuses <= $promo->used) {
371
            return false;
372
        }
373 3
374 1
        if ($promo->start_at && (strtotime($promo->start_at) - time() > 0)) {
375
            return false;
376
        }
377 2
378 1
        if ($promo->end_at && (strtotime($promo->end_at) - time() < 0)) {
379
            return false;
380
        }
381 1
382
        return true;
383
    }
384 6
385
    public function isPromoAvailableForClientGroup(\Model_Promo $promo)
386 6
    {
387
        $clientGroups = $this->di['tools']->decodeJ($promo->client_groups);
388 6
389 2
        if (empty($clientGroups)) {
390
            return true;
391
        }
392
393 4
        try {
394 4
            $client = $this->di['loggedin_client'];
395
        } catch (\Exception $e) {
396
            $client = null;
397
        }
398 4
399 1
        if (is_null($client)){
400
            return false;
401
        }
402 3
403 1
        if (!$client->client_group_id) {
404
            return false;
405
        }
406 2
407
        return in_array($client->client_group_id, $clientGroups);
408
    }
409 1
410
    protected function clientHadUsedPromo(\Model_Client $client, \Model_Promo $promo)
411 1
    {
412 1
        $sql     = "SELECT id FROM client_order WHERE promo_id = :promo AND client_id = :cid LIMIT 1";
413
        $promoId = $this->di['db']->getCell($sql, array(':promo' => $promo->id, ':cid' => $client->id));
414 1
415
        return ($promoId !== null);
416
    }
417 1
418
    public function getCartProducts(\Model_Cart $model)
419 1
    {
420
        return $this->di['db']->find('CartProduct', 'cart_id = :cart_id ORDER BY id ASC', array(':cart_id' => $model->id));
421
    }
422 2
423
    public function checkoutCart(\Model_Cart $cart, \Model_Client $client, $gateway_id = null)
424 2
    {
425 2
        if ($cart->promo_id) {
426 2
            $promo = $this->di['db']->getExistingModelById('Promo', $cart->promo_id, 'Promo not found');
427 1
            if (!$this->isClientAbleToUsePromo($client, $promo)) {
428
                throw new \Box_Exception('You have already used this promo code. Please remove promo code and checkout again.', null, 9874);
429 1
            }
430
        }
431 1
432
        $this->di['events_manager']->fire(
433 1
            array(
434
                'event'  => 'onBeforeClientCheckout',
435 1
                'params' => array(
436 1
                    'ip'        => $this->di['request']->getClientAddress(),
437 1
                    'client_id' => $client->id,
438 1
                    'cart_id'   => $cart->id)
439 1
            )
440
        );
441 1
442
        list($order, $invoice, $orders) = $this->createFromCart($client, $gateway_id);
443 1
444
        $this->rm($cart);
445 1
446
        $this->di['logger']->info('Checked out shopping cart');
447 1
448
        $this->di['events_manager']->fire(
449 1
            array(
450
                'event'  => 'onAfterClientOrderCreate',
451 1
                'params' => array(
452 1
                    'ip'        => $this->di['request']->getClientAddress(),
453 1
                    'client_id' => $client->id,
454 1
                    'id'        => $order->id
455 1
                )
456 1
            )
457
        );
458
459 1
        $result = array(
460 1
            'gateway_id'   => $gateway_id,
461 1
            'invoice_hash' => null,
462 1
            'order_id'     => $order->id,
463 1
            'orders'       => $orders,
464
        );
465
466 1
        // invoice may not be created if total is 0
467
        if ($invoice instanceof \Model_Invoice && $invoice->status == \Model_Invoice::STATUS_UNPAID) {
468
            $result['invoice_hash'] = $invoice->hash;
469
        }
470 1
471
        return $result;
472
    }
473
474
    public function createFromCart(\Model_Client $client, $gateway_id = null)
475
    {
476
        $cart = $this->getSessionCart();
477
        $ca = $this->toApiArray($cart);
478
        if (count($ca['items']) == 0) {
479
            throw new \Box_Exception('Can not checkout empty cart.');
480
        }
481
482
483
        $currency = $this->di['db']->getExistingModelById('Currency', $cart->currency_id, 'Currency not found.');
484
485
        //set default client currency
486
        if (!$client->currency) {
487
            $client->currency = $currency->code;
488
            $this->di['db']->store($client);
489
        }
490
491
        if ($client->currency != $currency->code) {
492
            throw new \Box_Exception('Selected currency :selected does not match your profile currency :code. Please change cart currency to continue.',
493
                array(':selected' => $currency->code, ':code' => $client->currency));
494
        }
495
496
        $clientService = $this->di['mod_service']('client');
497
        $taxed         = $clientService->isClientTaxable($client);
498
499
        $orders        = array();
500
        $invoice_items = array();
501
        $master_order  = null;
502
        $i             = 0;
503
504
        foreach ($this->getCartProducts($cart) as $p) {
505
            $item = $this->cartProductToApiArray($p);
506
507
            $order             = $this->di['db']->dispense('ClientOrder');
508
            $order->client_id  = $client->id;
509
            $order->promo_id   = $cart->promo_id;
510
            $order->product_id = $item['product_id'];
511
            $order->form_id    = $item['form_id'];
512
513
            $order->group_id       = $cart->id;
514
            $order->group_master   = ($i == 0);
515
            $order->invoice_option = 'issue-invoice';
516
            $order->title          = $item['title'];
517
            $order->currency       = $currency->code;
518
            $order->service_type   = $item['type'];
519
            $order->unit           = $this->di['array_get']($item, 'unit', NULL);
520
            $order->period         = $this->di['array_get']($item, 'period', NULL);
521
            $order->quantity       = $this->di['array_get']($item, 'quantity', NULL);
522
            $order->price          = $item['price'] * $currency->conversion_rate;
523
            $order->discount       = $item['discount_price'] * $currency->conversion_rate;
524
            $order->status         = \Model_ClientOrder::STATUS_PENDING_SETUP;
525
            $order->notes          = $this->di['array_get']($item, 'notes', NULL);
526
            $order->config         = json_encode($item);
527
            $order->created_at     = date('Y-m-d H:i:s');
528
            $order->updated_at     = date('Y-m-d H:i:s');
529
            $this->di['db']->store($order);
530
531
            $orders[] = $order;
532
533
            // mark promo as used
534
            if ($cart->promo_id) {
535
                $promo = $this->di['db']->getExistingModelById('Promo', $cart->promo_id, 'Promo not found.');
536
                $this->usePromo($promo);
537
538
                //set promo info for later use
539
                $order->promo_recurring = $promo->recurring;
540
                $order->promo_used      = 1;
541
                $this->di['db']->store($order);
542
            }
543
544
            $orderService = $this->di['mod_service']('order');
545
            $orderService->saveStatusChange($order, 'Order created');
546
547
            $invoice_items[] = array(
548
                'title'    => $order->title,
549
                'price'    => $order->price,
550
                'quantity' => $order->quantity,
551
                'unit'     => $order->unit,
552
                'period'   => $order->period,
553
                'taxed'    => $taxed,
554
                'type'     => \Model_InvoiceItem::TYPE_ORDER,
555
                'rel_id'   => $order->id,
556
                'task'     => \Model_InvoiceItem::TASK_ACTIVATE,
557
            );
558
559
            if($order->discount > 0){ 
560
                $invoice_items[] = array(
561
                    'title'    => __('Discount: :product', array(':product' => $order->title)),
562
                    'price'    => $order->discount * -1,
563
                    'quantity' => 1,
564
                    'unit'     => 'discount',
565
                    'rel_id'    => $order->id,
566
                    'taxed'    => $taxed,
567
                );
568
            }
569
570
            if ($item['setup_price'] > 0) {
571
                $setup_price     = ($item['setup_price'] * $currency->conversion_rate) - ($item['discount_setup'] * $currency->conversion_rate);
572
                $invoice_items[] = array(
573
                    'title'    => __(':product setup', array(':product' => $order->title)),
574
                    'price'    => $setup_price,
575
                    'quantity' => 1,
576
                    'unit'     => 'service',
577
                    'taxed'    => $taxed,
578
                );
579
            }
580
581
            //define master order to be returned
582
            if (null === $master_order) {
583
                $master_order = $order;
584
            }
585
586
            $i++;
587
        }
588
589
        if ($ca['total'] > 0) { //crete invoice if order total > 0
590
591
            $invoiceService =  $this->di['mod_service']('Invoice');
592
            $invoiceModel   = $invoiceService->prepareInvoice($client, array('client_id' => $client->id, 'items' => $invoice_items, 'gateway_id' => $gateway_id));
593
594
            $clientBalanceService = $this->di['mod_service']('Client', 'Balance');
595
            $balanceAmount = $clientBalanceService->getClientBalance($client);
596
            $useCredits = $balanceAmount >= $ca['total'];
597
598
            $invoiceService->approveInvoice($invoiceModel, array('id' => $invoiceModel->id, 'use_credits' => $useCredits));
599
600
            if ($invoiceModel->status == \Model_Invoice::STATUS_UNPAID) {
601
                foreach ($orders as $order) {
602
                    $order->unpaid_invoice_id = $invoiceModel->id;
603
                    $this->di['db']->store($order);
604
                }
605
            }
606
        }
607
608
        //activate orders if product is setup to be activated after order place or order total is $0
609
        $orderService = $this->di['mod_service']('Order');
610
        $ids = array();
611
        foreach ($orders as $order) {
612
            $ids[] = $order->id;
613
            $oa      = $orderService->toApiArray($order, false, $client);
614
            $product = $this->di['db']->getExistingModelById('Product', $oa['product_id']);
615
            try {
616
                if ($product->setup == \Model_ProductTable::SETUP_AFTER_ORDER){
617
                    $orderService->activateOrder($order);
618
                }
619
620
                if ($ca['total'] <= 0 && $product->setup == \Model_ProductTable::SETUP_AFTER_PAYMENT && $oa['total'] - $oa['discount'] <= 0){
621
                    $orderService->activateOrder($order);
622
                }
623
624
                if ($ca['total'] > 0 && $product->setup == \Model_ProductTable::SETUP_AFTER_PAYMENT && $invoiceModel->status == \Model_Invoice::STATUS_PAID ){
625
                    $orderService->activateOrder($order);
626
                }
627
            }
628
            catch (\Exception $e) {
629
                error_log($e->getMessage());
630
                $status = 'error';
631 1
                $notes  = 'Order could not be activated after checkout due to error: ' . $e->getMessage();
632
                $orderService->orderStatusAdd($order, $status, $notes);
633 1
            }
634 1
        }
635 1
636 1
        return array(
637
            $master_order,
638 1
            isset($invoiceModel) ? $invoiceModel : null,
639
            $ids,
640 1
        );
641
    }
642
643
    public function usePromo(\Model_Promo $promo)
644
    {
645
        $promo->used++;
646
        $promo->updated_at = date('Y-m-d H:i:s');
647
        $this->di['db']->store($promo);
648
    }
649
650
    public function findActivePromoByCode($code)
651
    {
652
        return $this->di['db']->findOne('Promo', 'code = :code AND active = 1 ORDER BY id ASC', array(':code' => $code));
653
    }
654
655
    private function getItemPrice(\Model_CartProduct $model)
0 ignored issues
show
This method is not used, and could be removed.
Loading history...
656
    {
657
        $product = $this->di['db']->load('Product', $model->product_id);
658
        $config = $this->getItemConfig($model);
659
        $repo = $product->getTable();
660
        return $repo->getProductPrice($product, $config);
661
    }
662
663
    private function getItemSetupPrice(\Model_CartProduct $model)
0 ignored issues
show
This method is not used, and could be removed.
Loading history...
664
    {
665
        $product = $this->di['db']->load('Product', $model->product_id);
666
        $config = $this->getItemConfig($model);
667
        $repo = $product->getTable();
668
        return $repo->getProductSetupPrice($product, $config);
669
    }
670
671
    /**
672
     * Function checks if product is related to other products in cart
673
     * If relation exists then count discount for this
674
     *
675
     * @param \Model_Cart $cart
676
     * @param \Model_CartProduct $model
677
     * @return number
678
     */
679
    protected function getRelatedItemsDiscount(\Model_Cart $cart, \Model_CartProduct $model)
680
    {
681
        $product = $this->di['db']->load('Product', $model->product_id);
682
        $repo = $product->getTable();
683
        $config = $this->getItemConfig($model);
684
685
        $discount = 0;
686
        if(method_exists($repo, 'getRelatedDiscount')) {
687
            $list = array();
688
            $products = $this->getCartProducts($cart);
689
            foreach($products as $p) {
690
                $item = $this->di['db']->toArray($p);
691
                $item['config'] = $this->getItemConfig($p);
692
                $list[] = $item;
693
            }
694
            $discount = $repo->getRelatedDiscount($list, $product, $config);
695
        }
696
        return $discount;
697
    }
698
699
    private function getItemTitle(\Model_CartProduct $model)
700
    {
701
        $product = $this->di['db']->load('Product', $model->product_id);
702
        $config = $this->getItemConfig($model);
703
        $service = $product->getService();
704
        if(method_exists($service, 'getCartProductTitle')) {
705
            return $service->getCartProductTitle($product, $config);
706
        } else {
707
            return __(':product_title', array(':product_title'=>$product->title));
708
        }
709
    }
710
711
    protected function getItemPromoDiscount(\Model_CartProduct $model, \Model_Promo $promo)
712
    {
713
        $product = $this->di['db']->load('Product', $model->product_id);
714
        $repo = $this->di['mod_service']('product');
715
        $config = $this->getItemConfig($model);
716
        return $repo->getProductDiscount($product, $promo, $config);
717
    }
718
719
    public function getItemConfig(\Model_CartProduct $model)
720
    {
721
        return $this->di['tools']->decodeJ($model->config);
722
    }
723
724
    public function cartProductToApiArray(\Model_CartProduct $model)
725
    {
726
        $product = $this->di['db']->load('Product', $model->product_id);
727
        $repo = $product->getTable();
728
        $config = $this->getItemConfig($model);
729
        $setup = $repo->getProductSetupPrice($product, $config);
730
        $price = $repo->getProductPrice($product, $config);
731
        $qty =  $this->di['array_get']($config, 'quantity', 1) ;
732
733
        list ($discount_price, $discount_setup) = $this->getProductDiscount($model, $setup);
734
735
        $discount_total = $discount_price + $discount_setup;
736
737
        $subtotal = ($price * $qty); 
738
        if(abs($discount_total) > ($subtotal + $setup) ) {
739
            $discount_total = $subtotal;
740
            $discount_price = $subtotal;
741
        }
742
743
        $data = array_merge($config, array(
744
            'id'            => $model->id,
745
            'product_id'    => $product->id,
746
            'form_id'       => $product->form_id,
747
            'title'         => $this->getItemTitle($model),
748
            'type'          => $product->type,
749 3
            'quantity'      => $qty,
750
            'unit'          => $repo->getUnit($product),
751 3
            'price'         => $price,
752 3
            'setup_price'   => $setup,
753 3
            'discount'      => $discount_total,
754 3
            'discount_price'=> $discount_price,
755 2
            'discount_setup'=> $discount_setup,
756 2
            'total'         => $subtotal,
757
        ));
758 2
        return $data;
759 1
    }
760 1
761 2
    public function getProductDiscount(\Model_CartProduct $cartProduct, $setup)
762 3
    {
763
        $cart = $this->di['db']->load('Cart', $cartProduct->cart_id);
764
        $discount_price = $this->getRelatedItemsDiscount($cart, $cartProduct);
765
        $discount_setup = 0; // discount for setup price
766
        if($cart->promo_id) {
767
            $promo = $this->di['db']->getExistingModelById('Promo', $cart->promo_id, 'Promo not found');
768
            //Promo discount should override related item discount
769
            $discount_price = $this->getItemPromoDiscount($cartProduct, $promo);
770
771
            if($promo->freesetup) {
772
                $discount_setup = $setup;
773
            }
774
        }
775
        return array($discount_price, $discount_setup);
776
    }
777
}