Completed
Push — master ( a6eefc...133a7e )
by Lawrence
02:08
created

Prestashop   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 398
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 14
Bugs 7 Features 0
Metric Value
wmc 45
c 14
b 7
f 0
lcom 1
cbo 4
dl 0
loc 398
rs 8.3673

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A onOpen() 0 7 1
C onMessage() 0 22 7
B getCombinationData() 0 16 5
A getSpecificPrice() 0 16 2
A getFinalPrice() 0 10 2
A getAvailableDate() 0 8 2
A getCombinationSpecificPrice() 0 10 2
B getCombination() 0 27 1
A getSpecificPriceData() 0 17 1
A getNumberSpecificPrice() 0 11 2
A getComboImage() 0 14 2
A getProducts() 0 7 1
A getProductIDs() 0 21 4
A getProduct() 0 22 1
A onClose() 0 6 1
A onError() 0 6 1
A getProductData() 0 11 2
A getCartData() 0 8 1
A processFindCarts() 0 20 3
A getAttribute() 0 16 2
B buildAttributes() 0 26 1

How to fix   Complexity   

Complex Class

Complex classes like Prestashop 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 Prestashop, and based on these observations, apply Extract Interface, too.

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($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
        } else {
128
            return;
129
        }
130
    }
131
132
133
    /**
134
     * @param string $product
135
     */
136
    private function getAttribute($product, $choices)
137
    {
138
        $sql = 'SELECT pac.id_product_attribute from '._DB_PREFIX_.'product_attribute_combination as pac
139
                LEFT JOIN  '._DB_PREFIX_.'product_attribute as ppa on ppa.id_product_attribute = pac.id_product_attribute
140
                LEFT JOIN '._DB_PREFIX_.'attribute a ON (a.id_attribute = pac.id_attribute)
141
                WHERE ppa.id_product = '.$product;
142
        foreach ($choices as $value) {
143
            $sql .= ' AND pac.id_product_attribute IN
144
                    ( SELECT pac.id_product_attribute from '._DB_PREFIX_.'product_attribute_combination as pac
145
                    WHERE pac.id_attribute = '.(int) $value.')  ';
146
        }
147
        $sql .= ' GROUP BY pac.id_product_attribute';
148
        $id_product_attribute = $this->dbConn->fetchColumn($sql);
149
150
        return $id_product_attribute;
151
    }
152
153
    /**
154
     * @param null|string $id_product_attribute
155
     */
156
    private function buildAttributes($combinations, $id_product_attribute, $row)
157
    {
158
        $combinationSet = array();
159
        $specific_price = null;
160
161
        $combinations['id_product'] = (int) $row['id_product'];
162
        $combinations['name'] = $row['product_name'];
163
        $combinations['attributes_values'][$row['id_attribute_group']] = $row['attribute_name'];
164
        $combinations['attributes'][] = (int) $row['id_attribute'];
165
        $combinations['base_price'] = (float) $row['base_price'];
166
        $combinations['price'] = (float) $row['price'];
167
168
        list ($combinationSet[(int) $row['id_product_attribute']], $combinations['specific_price']) = $this->getCombinationSpecificPrice($combinationSet, $row, $id_product_attribute);
169
170
        $combinations['ecotax'] = (float) $row['ecotax'];
171
        $combinations['weight'] = (float) $row['weight'];
172
        $combinations['quantity'] = (int) $row['quantity'];
173
        $combinations['reference'] = $row['reference'];
174
        $combinations['unit_impact'] = $row['unit_price_impact'];
175
        $combinations['minimal_quantity'] = $row['minimal_quantity'];
176
        $combinations['available_date'] = $this->getAvailableDate($row);
177
        $combinations['image'] = $this->getComboImage($id_product_attribute);
178
        $combinations['final_price'] = $this->getFinalPrice($row, $specific_price);
179
180
        return $combinations;
181
    }
182
183
    private function getFinalPrice($row, $specific_price)
184
    {
185
        if ($specific_price['price'] != 0) {
186
            $final_price = (((float) $row['base_price'] + (float) $row['price']) * (((int) 100 - $specific_price['reduction_percent']) / 100));
187
        } else {
188
            $final_price = (float) $row['base_price'] + (float) $row['price'];
189
        }
190
191
        return $final_price;
192
    }
193
194
195
    private function getAvailableDate($row) {
196
        if ($row['available_date'] != '0000-00-00') {
197
            return $row['available_date'];
198
        } else {
199
            return '';
200
        }
201
202
    }
203
204
    /**
205
     * @param null|string $id_product_attribute
206
     */
207
    private function getCombinationSpecificPrice($combinationSet, $row, $id_product_attribute) {
208
            // Call getSpecificPrice in order to set $combination_specific_price
209
                if (!isset($combinationSet[(int) $row['id_product_attribute']])) {
210
                    $specific_price = $this->getSpecificPrice($id_product_attribute, $row['id_product']);
211
                    $combinationSet[(int) $row['id_product_attribute']] = true;
212
                    return array($combinationSet, $specific_price);
213
                } else {
214
                    return array(false, null);
215
                }
216
    }
217
218
219
220
    /**
221
     * @param null|string $id_product_attribute
222
     */
223
    private function getCombination($id_product_attribute)
224
    {
225
        $sql = 'SELECT ag.id_attribute_group, ag.is_color_group, agl.name AS group_name, agl.public_name AS public_group_name,
226
                    a.id_attribute, al.name AS attribute_name, a.color AS attribute_color, pas.id_product_attribute,
227
                    IFNULL(stock.quantity, 0) as quantity, pas.price, pas.ecotax, pas.weight,
228
                    pas.default_on, pa.reference, pas.unit_price_impact,
229
                    pas.minimal_quantity, pas.available_date, ag.group_type
230
                FROM '._DB_PREFIX_.'product_attribute pa
231
                 INNER JOIN '._DB_PREFIX_.'product_attribute_shop pas
232
                    ON (pas.id_product_attribute = pa.id_product_attribute AND pas.id_shop = 1)
233
                 LEFT JOIN '._DB_PREFIX_.'stock_available stock
234
                        ON (stock.id_product = pa.id_product AND stock.id_product_attribute = IFNULL(pa.id_product_attribute, 0) AND stock.id_shop = 1  )
235
                LEFT JOIN '._DB_PREFIX_.'product_attribute_combination pac ON (pac.id_product_attribute = pa.id_product_attribute)
236
                LEFT JOIN '._DB_PREFIX_.'attribute a ON (a.id_attribute = pac.id_attribute)
237
                LEFT JOIN '._DB_PREFIX_.'attribute_group ag ON (ag.id_attribute_group = a.id_attribute_group)
238
                LEFT JOIN '._DB_PREFIX_.'attribute_lang al ON (a.id_attribute = al.id_attribute)
239
                LEFT JOIN '._DB_PREFIX_.'attribute_group_lang agl ON (ag.id_attribute_group = agl.id_attribute_group)
240
                 INNER JOIN '._DB_PREFIX_.'attribute_shop attribute_shop
241
                    ON (attribute_shop.id_attribute = a.id_attribute AND attribute_shop.id_shop = 1)
242
                WHERE pas.id_product_attribute= '.(int) $id_product_attribute.'
243
                GROUP BY id_attribute_group, id_product_attribute
244
                ORDER BY ag.position ASC, a.position ASC, agl.name ASC';
245
246
        $combo_groups = $this->dbConn->fetchRowMany($sql);
247
248
        return $combo_groups;
249
    }
250
251
    /**
252
     * @param null|string $id_product_attribute
253
     * @param string $id_product
254
     */
255
    private function getSpecificPrice($id_product_attribute, $id_product)
256
    {
257
        $specific_price = array();
258
        if ($this->getNumberSpecificPrice($id_product_attribute, $id_product) > 0) {
259
            $result = $this->getSpecificPriceData($id_product_attribute, $id_product, date('Y-m-d H:i:s'));
260
            $specific_price['price'] = $result['price'];
261
            $specific_price['id_product_attribute'] = $result['id_product_attribute'];
262
            $specific_price['reduction_percent'] = (int) 100 * $result['reduction'];
263
            $specific_price['reduction_price'] = 0;
264
            $specific_price['reduction_type'] = $result['reduction_type'];
265
266
            return $specific_price;
267
        } else {
268
            return;
269
        }
270
    }
271
272
273
    /**
274
     * @param string $now
275
     * @param null|string $id_product_attribute
276
     * @param string $id_product
277
     */
278
    private function getSpecificPriceData($id_product_attribute, $id_product, $now)
279
    {
280
        $sql = 'SELECT * FROM '._DB_PREFIX_.'specific_price
281
                            WWHERE id_product = '.(int) $id_product.'
282
                            AND id_product_attribute IN (0, '.(int) $id_product_attribute.')
283
                            AND (
284
                                   (from = \'0000-00-00 00:00:00\' OR \''.$now.'\' >= from)
285
                            AND
286
                                     (to = \'0000-00-00 00:00:00\' OR \''.$now.'\' <= to)
287
                            ) ';
288
289
        $sql .= ' ORDER BY id_product_attribute DESC, from_quantity DESC, id_specific_price_rule ASC';
290
291
        $result = $this->dbConn->fetchRow($sql);
292
293
        return $result;
294
    }
295
296
    /**
297
     * @param null|string $id_product_attribute
298
     * @param string $id_product
299
     */
300
    private function getNumberSpecificPrice($id_product_attribute, $id_product)
301
    {
302
        $sql = 'SELECT COUNT(*) FROM '._DB_PREFIX_.'specific_price WHERE id_product = '.(int) $id_product.' AND id_product_attribute = 0';
303
        $spec = $this->dbConn->fetchColumn($sql);
304
        if ($spec == 0) {
305
            $sql = 'SELECT COUNT(*) FROM '._DB_PREFIX_.'specific_price WHERE id_product_attribute = '.(int) $id_product_attribute;
306
            $spec = $this->dbConn->fetchColumn($sql);
307
        }
308
309
        return $spec;
310
    }
311
312
    /**
313
     * @param null|string $id_product_attribute
314
     */
315
    private function getComboImage($id_product_attribute)
316
    {
317
        $sql = 'SELECT pai.id_imageas image
318
                        FROM '._DB_PREFIX_.'product_attribute_image pai
319
                        LEFT JOIN '._DB_PREFIX_.'image_lang il ON (il.id_image = pai.id_image)
320
                        LEFT JOIN '._DB_PREFIX_.'image i ON (i.id_image = pai.id_image)
321
                        WHERE pai.id_product_attribute = '.(int) $id_product_attribute.' ORDER by i.position';
322
        $image = $this->dbConn->fetchColumn($sql);
323
        if ($image !== false) {
324
            return (int) $image;
325
        } else {
326
            return -1;
327
        }
328
    }
329
330
    /**
331
     * @param string $category
332
     */
333
    private function getProducts($category)
334
    {
335
        $product_ids = $this->getProductIDs($category);
336
        $products = $this->getProduct($product_ids);
337
338
        return $products;
339
    }
340
341
    /**
342
     * @param string $category
343
     */
344
    private function getProductIDs($category)
345
    {
346
        $sql = 'SELECT DISTINCT p.id_product
347
                from '._DB_PREFIX_.'product as p
348
                LEFT JOIN '._DB_PREFIX_.'image AS i ON i.id_product = p.id_product 
349
                LEFT JOIN '._DB_PREFIX_.'product_lang as pl ON pl.id_product = p.id_product
350
                WHERE p.active = 1
351
                AND p.id_category_default = '.(int) $category.'
352
                GROUP BY p.id_product';
353
        $pcats = $this->dbConn->fetchRowMany($sql);
354
        $ids = '';
355
        if (is_array($pcats) && (!empty($pcats))) {
356
        foreach ($pcats as $row) {
357
            $ids .= $row['id_product'].',';
358
            }
359
        }
360
361
        $ids = rtrim($ids, ',');
362
363
        return $ids;
364
    }
365
366
    /**
367
     * @param string $ids
368
     */
369
    private function getProduct($ids)
370
    {
371
        $sqler = '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, 
372
                    p.show_price, p.available_for_order, p.minimal_quantity, p.customizable,
373
                    p.out_of_stock, pl.link_rewrite, pl.name, i.id_image, il.legend,
374
                    cl.name AS category_default,  cl.id_category AS cat_id, ps.price AS orderprice
375
                    FROM '._DB_PREFIX_.'product as p                 
376
                    LEFT JOIN '._DB_PREFIX_.'image AS i ON i.id_product = p.id_product 
377
                    LEFT JOIN '._DB_PREFIX_.'product_shop as ps ON ps.id_product = p.id_product
378
                    LEFT JOIN '._DB_PREFIX_.'product_lang as pl ON pl.id_product = p.id_product
379
                    LEFT JOIN '._DB_PREFIX_.'image_lang as il ON i.id_image = il.id_image
380
                    LEFT JOIN '._DB_PREFIX_.'category_lang cl ON p.id_category_default = cl.id_category
381
                    WHERE p.id_product IN ('.$ids.')
382
                    AND i.cover = 1
383
                    AND p.active = 1
384
                    GROUP BY p.id_product
385
                    ORDER BY p.price ASC';
386
387
        $result = $this->dbConn->fetchRowMany($sqler);
388
389
        return $result;
390
    }
391
392
393
    public function onClose(ConnectionInterface $conn)
394
    {
395
        // The connection is closed, remove it, as we can no longer send it messages
396
        $this->clients->detach($conn);
397
        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...
398
    }
399
400
    public function onError(ConnectionInterface $conn, \Exception $e)
401
    {
402
        echo "An error has occurred: {$e->getMessage()}\n";
403
        Analog::log('Error: '.$e->getMessage().'');
404
        $conn->close();
405
    }
406
}
407