Completed
Branch master (366423)
by Lawrence
02:40 queued 40s
created

Prestashop   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 388
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 8
Bugs 5 Features 0
Metric Value
wmc 45
c 8
b 5
f 0
lcom 1
cbo 4
dl 0
loc 388
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 getProductData() 0 11 2
A getCartData() 0 8 1
A processFindCarts() 0 20 3
A getAttribute() 0 16 2
B buildAttributes() 0 27 1
A getFinalPrice() 0 10 2
A getAvailableDate() 0 8 2
A getCombinationSpecificPrice() 0 10 2
B getCombination() 0 27 1
A getSpecificPrice() 0 16 2
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

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