Completed
Push — master ( c237d6...e36866 )
by Lawrence
09:21 queued 01:36
created

Prestashop::getProductData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 11
rs 9.4285
cc 2
eloc 9
nc 2
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 = getCartData($data);
45
                                    break;
46
                    case 'prod': $result = getProductData($data);
47
                                    break;
48
                    case 'comb': $result = getCombinationData($data);
49
                                    break;
50
                    default:        break;
51
                }
52
                if (!empty($result)) {
53
                $client->send(json_encode($result));
54
                }
55
            }
56
        }
57
    }
58
59
    private function getCombinationData($data) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
60
        $product = substr($data, 0, strpos($data, ','));
61
                $vars = explode(',', $data);
62
                Analog::log("Product variables: $data");
63
                $choices = array_slice($vars, 1);
64
                $id_product_attribute = $this->getAttribute($product, $choices);
65
                Analog::log("Product combination: $id_product_attribute");
66
                $combo_groups = $this->getCombination($id_product_attribute);
67
                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...
68
                    foreach ($combo_groups as $k => $row) {
69
                        $combinations = $this->buildAttributes($id_product_attribute, $row);
70
                    }
71
                }
72
        return $combinations;
0 ignored issues
show
Bug introduced by
The variable $combinations does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
73
    }
74
75
    private function getProductData($data) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
76
                $category = (int) substr($data, 0, strpos($data, ','));
77
                if ($category != 0) {
78
                    $products = $this->getProducts($category);
79
                } else {
80
                    $product = substr($data, strpos($data, ',') + 1);
81
                    $products = $this->getProducts($product);
82
                }
83
                Analog::log("Product variables: $data");
84
        return $products;
85
    }
86
87
    private function getCartData($data) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
88
        $cart = substr($data, 0, strpos($data, ','));
89
        $cust = substr($data, strpos($data, ',') + 1);
90
91
        Analog::log("Cart & customer variables: $data");
92
        $otherCarts = $this->processFindCarts($cart, $cust);
93
        return $otherCarts;
94
    }
95
96
97
/**
98
 * @param string $cart
99
 * @param string $cust
100
 */
101
    private function processFindCarts($cart, $cust)
102
    {
103
        $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
104
                LEFT JOIN  '._DB_PREFIX_.'cart_product as pcp on pcp.id_cart = pc.id_cart
105
                WHERE pc.id_cart NOT IN (SELECT po.id_cart FROM  '._DB_PREFIX_.'orders as po)
106
                AND pcp.id_product IS NOT NULL
107
                AND pc.id_customer = '.(int) $cust.'
108
                AND pc.id_cart != '.(int) $cart.'
109
                ORDER BY pc.date_upd DESC
110
                LIMIT 10';
111
        if ($results = $this->dbConn->fetchRowMany($sql)) {
112
            foreach ($results as &$row) {
113
                $row['token'] = md5(_COOKIE_KEY_.'recover_cart_'.$row['id']);
114
            }
115
116
            return $results;
117
        } else {
118
            return;
119
        }
120
    }
121
122
123
    /**
124
     * @param string $product
125
     */
126
    private function getAttribute($product, $choices)
127
    {
128
        $sql = 'SELECT pac.id_product_attribute from '._DB_PREFIX_.'product_attribute_combination as pac
129
                LEFT JOIN  '._DB_PREFIX_.'product_attribute as ppa on ppa.id_product_attribute = pac.id_product_attribute
130
                LEFT JOIN '._DB_PREFIX_.'attribute a ON (a.id_attribute = pac.id_attribute)
131
                WHERE ppa.id_product = '.$product;
132
        foreach ($choices as $value) {
133
            $sql .= ' AND pac.id_product_attribute IN
134
                    ( SELECT pac.id_product_attribute from '._DB_PREFIX_.'product_attribute_combination as pac
135
                    WHERE pac.id_attribute = '.(int) $value.')  ';
136
        }
137
        $sql .= ' GROUP BY pac.id_product_attribute';
138
        $id_product_attribute = $this->dbConn->fetchColumn($sql);
139
140
        return $id_product_attribute;
141
    }
142
143
    /**
144
     * @param null|string $id_product_attribute
145
     */
146
    private function buildAttributes($id_product_attribute, $row)
147
    {
148
        $combinations = array();
149
        $combinations[$row['id_product_attribute']]['id_product'] = (int) $row['id_product'];
150
        $combinations[$row['id_product_attribute']]['name'] = $row['product_name'];
151
        $combinations[$row['id_product_attribute']]['attributes_values'][$row['id_attribute_group']] = $row['attribute_name'];
152
        $combinations[$row['id_product_attribute']]['attributes'][] = (int) $row['id_attribute'];
153
        $combinations[$row['id_product_attribute']]['base_price'] = (float) $row['base_price'];
154
        $combinations[$row['id_product_attribute']]['price'] = (float) $row['price'];
155
156
                // Call getPriceStatic in order to set $combination_specific_price
157
                if (!isset($combinationSet[(int) $row['id_product_attribute']])) {
0 ignored issues
show
Bug introduced by
The variable $combinationSet seems only to be defined at a later point. As such the call to isset() seems to always evaluate to false.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
158
                    $specific_price = $this->getSpecificPrice($id_product_attribute, $row['id_product']);
159
                    $combinationSet[(int) $row['id_product_attribute']] = true;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$combinationSet was never initialized. Although not strictly required by PHP, it is generally a good practice to add $combinationSet = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
160
                    $combinations[$row['id_product_attribute']]['specific_price'] = $specific_price;
161
                }
162
        $combinations[$row['id_product_attribute']]['ecotax'] = (float) $row['ecotax'];
163
        $combinations[$row['id_product_attribute']]['weight'] = (float) $row['weight'];
164
        $combinations[$row['id_product_attribute']]['quantity'] = (int) $row['quantity'];
165
        $combinations[$row['id_product_attribute']]['reference'] = $row['reference'];
166
        $combinations[$row['id_product_attribute']]['unit_impact'] = $row['unit_price_impact'];
167
        $combinations[$row['id_product_attribute']]['minimal_quantity'] = $row['minimal_quantity'];
168
        $combinations[$row['id_product_attribute']]['available_date'] = '';
169
        if ($row['available_date'] != '0000-00-00') {
170
            $combinations[$row['id_product_attribute']]['available_date'] = $row['available_date'];
171
        }
172
        $combinations[$row['id_product_attribute']]['image'] = $this->getComboImage($id_product_attribute);
173
        $combinations[$row['id_product_attribute']]['final_price'] = $this->getFinalPrice($row, $specfic_price);
0 ignored issues
show
Bug introduced by
The variable $specfic_price does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
174
175
        return $combinations;
176
    }
177
178
    private function getFinalPrice($row, $specific_price)
179
    {
180
        if ($specific_price['price'] != 0) {
181
            $final_price = (((float) $row['base_price'] + (float) $row['price']) * (((int) 100 - $specific_price['reduction_percent']) / 100));
182
        } else {
183
            $final_price = (float) $row['base_price'] + (float) $row['price'];
184
        }
185
186
        return $final_price;
187
    }
188
189
    /**
190
     * @param null|string $id_product_attribute
191
     */
192
    private function getCombination($id_product_attribute)
193
    {
194
        $sql = 'SELECT ag.id_attribute_group, ag.is_color_group, agl.name AS group_name, agl.public_name AS public_group_name,
195
                    a.id_attribute, al.name AS attribute_name, a.color AS attribute_color, pas.id_product_attribute,
196
                    IFNULL(stock.quantity, 0) as quantity, pas.price, pas.ecotax, pas.weight,
197
                    pas.default_on, pa.reference, pas.unit_price_impact,
198
                    pas.minimal_quantity, pas.available_date, ag.group_type
199
                FROM '._DB_PREFIX_.'product_attribute pa
200
                 INNER JOIN '._DB_PREFIX_.'product_attribute_shop pas
201
                    ON (pas.id_product_attribute = pa.id_product_attribute AND pas.id_shop = 1)
202
                 LEFT JOIN '._DB_PREFIX_.'stock_available stock
203
                        ON (stock.id_product = pa.id_product AND stock.id_product_attribute = IFNULL(pa.id_product_attribute, 0) AND stock.id_shop = 1  )
204
                LEFT JOIN '._DB_PREFIX_.'product_attribute_combination pac ON (pac.id_product_attribute = pa.id_product_attribute)
205
                LEFT JOIN '._DB_PREFIX_.'attribute a ON (a.id_attribute = pac.id_attribute)
206
                LEFT JOIN '._DB_PREFIX_.'attribute_group ag ON (ag.id_attribute_group = a.id_attribute_group)
207
                LEFT JOIN '._DB_PREFIX_.'attribute_lang al ON (a.id_attribute = al.id_attribute)
208
                LEFT JOIN '._DB_PREFIX_.'attribute_group_lang agl ON (ag.id_attribute_group = agl.id_attribute_group)
209
                 INNER JOIN '._DB_PREFIX_.'attribute_shop attribute_shop
210
                    ON (attribute_shop.id_attribute = a.id_attribute AND attribute_shop.id_shop = 1)
211
                WHERE pas.id_product_attribute= '.(int) $id_product_attribute.'
212
                GROUP BY id_attribute_group, id_product_attribute
213
                ORDER BY ag.position ASC, a.position ASC, agl.name ASC';
214
215
        $combo_groups = $this->dbConn->fetchRowMany($sql);
216
217
        return $combo_groups;
218
    }
219
220
    private function getSpecificPrice($id_product_attribute, $id_product)
221
    {
222
        if ($this->getNumberSpecificPrice($id_product_attribute, $id_product) > 0) {
223
            $result = $this->getSpecificPriceData($id_product_attribute, $id_product, date('Y-m-d H:i:s'));
224
            $specific_price['price'] = $result['price'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$specific_price was never initialized. Although not strictly required by PHP, it is generally a good practice to add $specific_price = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
225
            $specific_price['id_product_attribute'] = $result['id_product_attribute'];
226
            $specific_price['reduction_percent'] = (int) 100 * $result['reduction'];
227
            $specific_price['reduction_price'] = 0;
228
            $specific_price['reduction_type'] = $result['reduction_type'];
229
230
            return $specific_price;
231
        } else {
232
            return;
233
        }
234
    }
235
236
237
    /**
238
     * @param string $now
239
     */
240
    private function getSpecificPriceData($id_product_attribute, $id_product, $now)
241
    {
242
        $sql = 'SELECT * FROM '._DB_PREFIX_.'specific_price
243
                            WWHERE id_product = '.(int) $id_product.'
244
                            AND id_product_attribute IN (0, '.(int) $id_product_attribute.')
245
                            AND (
246
                                   (from = \'0000-00-00 00:00:00\' OR \''.$now.'\' >= from)
247
                            AND
248
                                     (to = \'0000-00-00 00:00:00\' OR \''.$now.'\' <= to)
249
                            ) ';
250
251
        $sql .= ' ORDER BY id_product_attribute DESC, from_quantity DESC, id_specific_price_rule ASC';
252
253
        $result = $this->dbConn->fetchRow($sql);
254
255
        return $result;
256
    }
257
258
    private function getNumberSpecificPrice($id_product_attribute, $id_product)
259
    {
260
        $sql = 'SELECT COUNT(*) FROM '._DB_PREFIX_.'specific_price WHERE id_product = '.(int) $id_product.' AND id_product_attribute = 0';
261
        $spec = $this->dbConn->fetchColumn($sql);
262
        if ($spec == 0) {
263
            $sql = 'SELECT COUNT(*) FROM '._DB_PREFIX_.'specific_price WHERE id_product_attribute = '.(int) $id_product_attribute;
264
            $spec = $this->dbConn->fetchColumn($sql);
265
        }
266
267
        return $spec;
268
    }
269
270
    private function getComboImage($id_product_attribute)
271
    {
272
        $sql = 'SELECT pai.id_imageas image
273
                        FROM '._DB_PREFIX_.'product_attribute_image pai
274
                        LEFT JOIN '._DB_PREFIX_.'image_lang il ON (il.id_image = pai.id_image)
275
                        LEFT JOIN '._DB_PREFIX_.'image i ON (i.id_image = pai.id_image)
276
                        WHERE pai.id_product_attribute = '.(int) $id_product_attribute.' ORDER by i.position';
277
        $image = $this->dbConn->fetchColumn($sql);
278
        if ($image !== false) {
279
            return (int) $image;
280
        } else {
281
            return -1;
282
        }
283
    }
284
285
286
    private function getProducts($category)
287
    {
288
        $product_ids = $this->getProductIDs($category);
289
        $products = $this->getProduct($product_ids);
290
291
        return $products;
292
    }
293
294
    private function getProductIDs($category)
295
    {
296
        $sql = 'SELECT DISTINCT p.id_product
297
                from '._DB_PREFIX_.'product as p
298
                LEFT JOIN '._DB_PREFIX_.'image AS i ON i.id_product = p.id_product 
299
                LEFT JOIN '._DB_PREFIX_.'product_lang as pl ON pl.id_product = p.id_product
300
                WHERE p.active = 1
301
                AND p.id_category_default = '.(int) $category.'
302
                GROUP BY p.id_product';
303
        $pcats = $this->dbConn->fetchRowMany($sql);
304
        $ids = '';
305
        if (is_array($pcats) && (!empty($pcats))) {
306
        foreach ($pcats as $row) {
307
            $ids .= $row['id_product'].',';
308
            }
309
        }
310
311
        $ids = rtrim($ids, ',');
312
313
        return $ids;
314
    }
315
316
    /**
317
     * @param string $ids
318
     */
319
    private function getProduct($ids)
320
    {
321
        $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,
322
                    p.out_of_stock, pl.link_rewrite, pl.name, i.id_image, il.legend,
323
                    cl.name AS category_default,  cl.id_category AS cat_id,
324
                    ps.price AS orderprice
325
                    FROM '._DB_PREFIX_.'product as p                 
326
                    LEFT JOIN '._DB_PREFIX_.'image AS i ON i.id_product = p.id_product 
327
                    LEFT JOIN '._DB_PREFIX_.'product_shop as ps ON ps.id_product = p.id_product
328
                    LEFT JOIN '._DB_PREFIX_.'product_lang as pl ON pl.id_product = p.id_product
329
                    LEFT JOIN '._DB_PREFIX_.'image_lang as il ON i.id_image = il.id_image
330
                    LEFT JOIN '._DB_PREFIX_.'category_lang cl ON p.id_category_default = cl.id_category
331
                    WHERE p.id_product IN ('.$ids.')
332
                    AND i.cover = 1
333
                    AND p.active = 1
334
                    GROUP BY p.id_product
335
                    ORDER BY p.price ASC';
336
337
        $result = $this->dbConn->fetchRowMany($sql);
338
339
        return $result;
340
    }
341
342
343
    public function onClose(ConnectionInterface $conn)
344
    {
345
        // The connection is closed, remove it, as we can no longer send it messages
346
        $this->clients->detach($conn);
347
        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...
348
    }
349
350
    public function onError(ConnectionInterface $conn, \Exception $e)
351
    {
352
        echo "An error has occurred: {$e->getMessage()}\n";
353
        Analog::log('Error: '.$e->getMessage().'');
354
        $conn->close();
355
    }
356
}
357