Completed
Push — master ( fd5258...2f7b62 )
by Lawrence
02:10
created

Prestashop::onClose()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 6
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
namespace ABM\Wasabi;
4
5
use Ratchet\MessageComponentInterface;
6
use Ratchet\ConnectionInterface;
7
use Analog\Analog;
8
9
class Prestashop implements MessageComponentInterface
10
{
11
    protected $clients;
12
    protected $dbConn;
13
14
    public function __construct()
15
    {
16
        $this->clients = new \SplObjectStorage();
17
        // Now connect to PS DB using Simplon on Composer
18
        $this->dbConn = new \Simplon\Mysql\Mysql(
19
    '127.0.0.1',
20
    _DB_USER_,
21
    _DB_PASSWD_,
22
    _DB_NAME_
23
        );
24
        $log_file = 'wasabi.log';
25
        Analog::handler(\Analog\Handler\File::init($log_file));
0 ignored issues
show
Documentation introduced by
\Analog\Handler\File::init($log_file) is of type object<Closure>, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
26
    }
27
28
    public function onOpen(ConnectionInterface $conn)
29
    {
30
        // Store the new connection to send messages to later
31
        $this->clients->attach($conn);
32
33
        Analog::log("New connection: $conn->resourceId");
0 ignored issues
show
Bug introduced by
Accessing resourceId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
34
    }
35
36
    public function onMessage(ConnectionInterface $from, $msg)
37
    {
38
        $result = null;
39
        foreach ($this->clients as $client) {
40
            if ($from == $client) {
41
                $type = strtolower(substr($msg, 0, strpos($msg, '|')));
42
                $data = substr($msg, strpos($msg, '|') + 1);
43
                switch ($type) {
44
                    case 'cart': $result = $this->getCartData($data);
45
                                    break;
46
                    case 'prod': $result = $this->getProductData($data);
47
                                    break;
48
                    case 'comb': $result = $this->getCombinationData($data);
49
                                    break;
50
                    default:        break;
51
                }
52
                if (!empty($result)) {
53
                $client->send(json_encode($result));
54
                }
55
            }
56
        }
57
    }
58
59
    /**
60
     * @param string $data
61
     */
62
    private function getCombinationData($data) {
63
        $product = substr($data, 0, strpos($data, ','));
64
        $combinations = array();
65
                $vars = explode(',', $data);
66
                Analog::log("Product variables: $data");
67
                $choices = array_slice($vars, 1);
68
                $id_product_attribute = $this->getAttribute($product, $choices);
69
                Analog::log("Product combination: $id_product_attribute");
70
                $combo_groups = $this->getCombination($product,$id_product_attribute);
71
                if (!empty($combo_groups) && is_array($combo_groups) && $combo_groups) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $combo_groups of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
72
                    foreach ($combo_groups as $k => $row) {
73
                        $combinations = $this->buildAttributes($combinations, $id_product_attribute, $row);
74
                    }
75
                }
76
        return $combinations;
77
    }
78
79
    /**
80
     * @param string $data
81
     */
82
    private function getProductData($data) {
83
                $category = (int) substr($data, 0, strpos($data, ','));
84
                if ($category != 0) {
85
                    $products = $this->getProducts($category);
86
                } else {
87
                    $product = substr($data, strpos($data, ',') + 1);
88
                    $products = $this->getProducts($product);
89
                }
90
                Analog::log("Product variables: $data");
91
        return $products;
92
    }
93
94
    /**
95
     * @param string $data
96
     */
97
    private function getCartData($data) {
98
        $cart = substr($data, 0, strpos($data, ','));
99
        $cust = substr($data, strpos($data, ',') + 1);
100
101
        Analog::log("Cart & customer variables: $data");
102
        $otherCarts = $this->processFindCarts($cart, $cust);
103
        return $otherCarts;
104
    }
105
106
107
/**
108
 * @param string $cart
109
 * @param string $cust
110
 */
111
    private function processFindCarts($cart, $cust)
112
    {
113
        $sql = 'SELECT DISTINCT pc.id_cart as id, DATE_FORMAT(pc.date_upd,"%a %D %b %Y, %l:%i %p") as timer from '._DB_PREFIX_.'cart as pc
114
                LEFT JOIN  '._DB_PREFIX_.'cart_product as pcp on pcp.id_cart = pc.id_cart
115
                WHERE pc.id_cart NOT IN (SELECT po.id_cart FROM  '._DB_PREFIX_.'orders as po)
116
                AND pcp.id_product IS NOT NULL
117
                AND pc.id_customer = '.(int) $cust.'
118
                AND pc.id_cart != '.(int) $cart.'
119
                ORDER BY pc.date_upd DESC
120
                LIMIT 10';
121
        if ($results = $this->dbConn->fetchRowMany($sql)) {
122
            foreach ($results as &$row) {
123
                $row['token'] = md5(_COOKIE_KEY_.'recover_cart_'.$row['id']);
124
            }
125
126
            return $results;
127
        } 
128
    }
129
130
131
    /**
132
     * @param string $product
133
     */
134
    private function getAttribute($product, $choices)
135
    {
136
        $sql = 'SELECT pac.id_product_attribute from '._DB_PREFIX_.'product_attribute_combination as pac
137
                LEFT JOIN  '._DB_PREFIX_.'product_attribute as ppa on ppa.id_product_attribute = pac.id_product_attribute
138
                LEFT JOIN '._DB_PREFIX_.'attribute a ON (a.id_attribute = pac.id_attribute)
139
                WHERE ppa.id_product = '.$product;
140
        foreach ($choices as $value) {
141
            $sql .= ' AND pac.id_product_attribute IN
142
                    ( SELECT pac.id_product_attribute from '._DB_PREFIX_.'product_attribute_combination as pac
143
                    WHERE pac.id_attribute = '.(int) $value.')  ';
144
        }
145
        $sql .= ' GROUP BY pac.id_product_attribute';
146
        $id_product_attribute = $this->dbConn->fetchColumn($sql);
147
148
        return $id_product_attribute;
149
    }
150
151
    /**
152
     * @param null|string $id_product_attribute
153
     */
154
    private function buildAttributes($combinations, $id_product_attribute, $row)
155
    {
156
        $combinationSet = array();
157
        $specific_price = null;
158
159
        $combinations['name'] = $row['product_name'];
160
        $typcheck = array("id_product", "price", "base_price", "price", "ecotax", "weight", "quantity", "unit_impact", "minimal_quantity");
161
162
        foreach ($typcheck as $key=>$value) 
163
        { 
164
            ((strpos($value, 'price') !== false) || (strpos($value, 'weight') !== false) || (strpos($value, 'ecotax') !== false) || (strpos($value, 'impact') !== false)) ? $combinations[$value] = (float) $row[$value] : $combinations[$value] = (int) $row[$value];
165
            
166
        }
167
        $combinations['attributes_values'][$row['id_attribute_group']] = $row['attribute_name'];
168
        $combinations['attributes'][] = (int) $row['id_attribute'];
169
170
        list ($combinationSet[(int) $row['id_product_attribute']], $combinations['specific_price']) = $this->getCombinationSpecificPrice($combinationSet, $row, $id_product_attribute);
171
172
        $combinations['reference'] = $row['reference'];
173
        $combinations['available_date'] = $this->getAvailableDate($row);
174
        $combinations['image'] = $this->getComboImage($id_product_attribute);
175
        $combinations['final_price'] = $this->getFinalPrice($row, $specific_price);
176
177
        return $combinations;
178
    }
179
180
    private function getFinalPrice($row, $specific_price)
181
    {
182
        $specific_price['price'] != 0 ? $final_price = (((float) $row['base_price'] + (float) $row['price']) * (((int) 100 - $specific_price['reduction_percent']) / 100)) : $final_price = (float) $row['base_price'] + (float) $row['price'];
183
        return $final_price;
184
    }
185
186
187
    private function getAvailableDate($row) {
188
        ($row['available_date'] != '0000-00-00') ? $dater = $row['available_date'] : $dater = '';
189
        return $dater;
190
   }
191
192
    /**
193
     * @param null|string $id_product_attribute
194
     */
195
    private function getCombinationSpecificPrice($combinationSet, $row, $id_product_attribute) {
196
            // Call getSpecificPrice in order to set $combination_specific_price
197
                if (!isset($combinationSet[(int) $row['id_product_attribute']])) {
198
                    $specific_price = $this->getSpecificPrice($id_product_attribute, $row['id_product']);
199
                    $combinationSet[(int) $row['id_product_attribute']] = true;
200
                    return array($combinationSet, $specific_price);
201
                } else {
202
                    return array(false, null);
203
                }
204
    }
205
206
    /**
207
     * @param null|string $id_product_attribute
208
     */
209
    private function getCombination($product,$id_product_attribute)
210
    {
211
        $combo = $this->getAttributeBase($id_product_attribute);
212
213
        if (is_array($combo))
214
        {
215
         foreach($combo as $key => $value)
216
        {
217
            $combo['base_price'] = (float) $this->getOrderPrice($product);
218
            $combo['quantity'] = (int) $this->getStockQuantity($product,$id_product_attribute);
219
            $combo['id_product'] = (int) $product;
220
            $combo['product_name'] = (int) $this->getProductName($product);
221
            $pricing = $this->getAttributePricing($id_product_attribute);
222
            foreach($pricing as $ki => $val)
0 ignored issues
show
Bug introduced by
The expression $pricing of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
223
                {   
224
                $combo[$ki] = $val;
225
                }
226
            }
227
        }
228
        return $combo;
229
    }
230
231
    private function getAttributeBase($attribute) {
232
       $sql = 'SELECT ag.id_attribute_group, ag.is_color_group, agl.name AS group_name, agl.public_name AS public_group_name,
233
                    a.id_attribute, al.name AS attribute_name, a.color AS attribute_color, ag.group_type, pac.id_product_attribute
234
            FROM '._DB_PREFIX_.'product_attribute_combination pac
235
            LEFT JOIN '._DB_PREFIX_.'attribute a ON (a.id_attribute = pac.id_attribute)
236
            LEFT JOIN  '._DB_PREFIX_.'attribute_group ag ON (ag.id_attribute_group = a.id_attribute_group)
237
            LEFT JOIN '._DB_PREFIX_.'attribute_lang al ON (a.id_attribute = al.id_attribute)
238
            LEFT JOIN '._DB_PREFIX_.'attribute_group_lang agl ON (ag.id_attribute_group = agl.id_attribute_group)
239
            WHERE pac.id_product_attribute='.(int) $attribute;
240
        $result = $this->dbConn->fetchRowMany($sql);
241
242
        return $result;
243
    }
244
245
    private function getStockQuantity($product, $attribute) {
246
        $sql = 'SELECT stock.quantity from '._DB_PREFIX_.'stock_available as stock WHERE stock.id_product = '.(int) $product.'AND stock.id_product_attribute = '.(int) $attribute;
247
        $result = $this->dbConn->fetchColumn($sql);
248
        return $result;
249
    }
250
251
252
    private function getAttributePricing($attribute) {
253
        $sql = 'SELECT pas.price, pas.ecotax, pas.weight, pas.default_on, pa.reference, pas.unit_price_impact, 
254
                pas.minimal_quantity, pas.available_date FROM '._DB_PREFIX_.'product_attribute_shop pas 
255
                WHERE pas.id_product_attribute = '.(int) $attribute;
256
        $result = $this->dbConn->fetchRowMany($sql);
257
258
        return $result;
259
    }
260
261
    /**
262
     * @param null|string $id_product_attribute
263
     * @param string $id_product
264
     */
265
    private function getSpecificPrice($id_product_attribute, $id_product)
266
    {
267
        $specific_price = array();
268
        if ($this->getNumberSpecificPrice($id_product_attribute, $id_product) > 0) {
269
            $result = $this->getSpecificPriceData($id_product_attribute, $id_product, date('Y-m-d H:i:s'));
270
            $specific_price['price'] = $result['price'];
271
            $specific_price['id_product_attribute'] = $result['id_product_attribute'];
272
            $specific_price['reduction_percent'] = (int) 100 * $result['reduction'];
273
            $specific_price['reduction_price'] = 0;
274
            $specific_price['reduction_type'] = $result['reduction_type'];
275
276
            return $specific_price;
277
        }
278
    }
279
280
281
    /**
282
     * @param string $now
283
     * @param null|string $id_product_attribute
284
     * @param string $id_product
285
     */
286
    private function getSpecificPriceData($id_product_attribute, $id_product, $now)
287
    {
288
        $sql = 'SELECT * FROM '._DB_PREFIX_.'specific_price
289
                            WWHERE id_product = '.(int) $id_product.'
290
                            AND id_product_attribute IN (0, '.(int) $id_product_attribute.')
291
                            AND (
292
                                   (from = \'0000-00-00 00:00:00\' OR \''.$now.'\' >= from)
293
                            AND
294
                                     (to = \'0000-00-00 00:00:00\' OR \''.$now.'\' <= to)
295
                            ) ';
296
297
        $sql .= ' ORDER BY id_product_attribute DESC, from_quantity DESC, id_specific_price_rule ASC';
298
299
        $result = $this->dbConn->fetchRow($sql);
300
301
        return $result;
302
    }
303
304
    /**
305
     * @param null|string $id_product_attribute
306
     * @param string $id_product
307
     */
308
    private function getNumberSpecificPrice($id_product_attribute, $id_product)
309
    {
310
        $sql = 'SELECT COUNT(*) FROM '._DB_PREFIX_.'specific_price WHERE id_product = '.(int) $id_product.' AND id_product_attribute = 0';
311
        $spec = $this->dbConn->fetchColumn($sql);
312
        if ($spec == 0) {
313
            $sql = 'SELECT COUNT(*) FROM '._DB_PREFIX_.'specific_price WHERE id_product_attribute = '.(int) $id_product_attribute;
314
            $spec = $this->dbConn->fetchColumn($sql);
315
        }
316
317
        return $spec;
318
    }
319
320
    /**
321
     * @param null|string $id_product_attribute
322
     */
323
    private function getComboImage($id_product_attribute)
324
    {
325
        $sql = 'SELECT pai.id_imageas image
326
                        FROM '._DB_PREFIX_.'product_attribute_image pai
327
                        LEFT JOIN '._DB_PREFIX_.'image_lang il ON (il.id_image = pai.id_image)
328
                        LEFT JOIN '._DB_PREFIX_.'image i ON (i.id_image = pai.id_image)
329
                        WHERE pai.id_product_attribute = '.(int) $id_product_attribute.' ORDER by i.position';
330
        $image = $this->dbConn->fetchColumn($sql);
331
        ($image !== false) ? $imager = (int) $image : $imager = -1;
332
        return $imager;
333
    }
334
335
    /**
336
     * @param string $category
337
     */
338
    private function getProducts($category)
339
    {
340
        $product_ids = $this->getProductIDs($category);
341
        $products = $this->getProduct($product_ids);
342
343
        return $products;
344
    }
345
346
    /**
347
     * @param string $category
348
     */
349
    private function getProductIDs($category)
350
    {
351
        $sql = 'SELECT DISTINCT p.id_product
352
                from '._DB_PREFIX_.'product as p
353
                LEFT JOIN '._DB_PREFIX_.'image AS i ON i.id_product = p.id_product 
354
                LEFT JOIN '._DB_PREFIX_.'product_lang as pl ON pl.id_product = p.id_product
355
                WHERE p.active = 1
356
                AND p.id_category_default = '.(int) $category.'
357
                GROUP BY p.id_product';
358
        $pcats = $this->dbConn->fetchRowMany($sql);
359
        $ids = '';
360
        if (is_array($pcats) && (!empty($pcats))) {
361
        foreach ($pcats as $row) {
362
            $ids .= $row['id_product'].',';
363
            }
364
        }
365
366
        $ids = rtrim($ids, ',');
367
368
        return $ids;
369
    }
370
371
    /**
372
     * @param string $ids
373
     */
374
    private function getProduct($ids)
375
    {
376
        $sql = 'SELECT p.id_product, p.id_supplier, p.ean13, p.upc, p.price, p.wholesale_price, p.on_sale, p.quantity, p.id_category_default,
377
                    p.show_price, p.available_for_order, p.minimal_quantity, p.customizable,
378
                    p.out_of_stock, pl.link_rewrite, pl.name, i.id_image, il.legend
379
                    FROM '._DB_PREFIX_.'product as p                 
380
                    LEFT JOIN '._DB_PREFIX_.'image AS i ON i.id_product = p.id_product 
381
                    LEFT JOIN '._DB_PREFIX_.'image_lang as il ON i.id_image = il.id_image
382
                    WHERE p.id_product IN ('.$ids.')
383
                    AND i.cover = 1
384
                    AND p.active = 1
385
                    GROUP BY p.id_product
386
                    ORDER BY p.price ASC';
387
388
        $result = $this->dbConn->fetchRowMany($sql);
389
390
        if (is_array($result))
391
        {
392
         foreach($result as $key => $value)
393
        {
394
            $result['cat_id'] = $value['id_category_default'];
395
            $result['orderprice'] = $this->getOrderPrice($value['id_product']);
396
            $result['category_default'] = $this->getProductCat($value['id_category_default']);
397
        }
398
        return $result;
399
        }
400
    }
401
402
    private function getOrderPrice($product) {
403
        $sql = 'SELECT ps.price from '._DB_PREFIX_.'product_shop as ps WHERE ps.id_product = '.(int) $product;
404
        $result = $this->dbConn->fetchColumn($sql);
405
        return $result;
406
    }
407
408
    private function getProductName($product) {
409
        $sql = 'SELECT pl.name from '._DB_PREFIX_.'product_lang as pl WHERE pl.id_product = '.(int) $product;
410
        $result = $this->dbConn->fetchColumn($sql);
411
        return $result;
412
    }
413
414
    private function getProductCat($category) {
415
        $sql = 'SELECT cl.name from '._DB_PREFIX_.'category_lang as cl WHERE cl.id_category = '.(int) $category;
416
        $result = $this->dbConn->fetchColumn($sql);
417
        return $result;
418
    }
419
420
421
    public function onClose(ConnectionInterface $conn)
422
    {
423
        // The connection is closed, remove it, as we can no longer send it messages
424
        $this->clients->detach($conn);
425
        Analog::log('Connection '.$conn->resourceId.' has disconnected');
0 ignored issues
show
Bug introduced by
Accessing resourceId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
426
    }
427
428
    public function onError(ConnectionInterface $conn, \Exception $e)
429
    {
430
        echo "An error has occurred: {$e->getMessage()}\n";
431
        Analog::log('Error: '.$e->getMessage().'');
432
        $conn->close();
433
    }
434
}
435