Completed
Push — master ( faed62...341552 )
by Lawrence
02:03
created

Prestashop   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 386
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 18
Bugs 8 Features 0
Metric Value
wmc 50
c 18
b 8
f 0
lcom 1
cbo 4
dl 0
loc 386
rs 8.6206

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