Shippo::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 16
nc 1
nop 12

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * @package Shippo
5
 * @author Iurii Makukh <[email protected]>
6
 * @copyright Copyright (c) 2015, Iurii Makukh
7
 * @license https://www.gnu.org/licenses/gpl.html GNU/GPLv3
8
 */
9
10
namespace gplcart\modules\shippo\models;
11
12
use Exception;
13
use gplcart\core\helpers\Session;
14
use gplcart\core\models\Address;
15
use gplcart\core\models\Convertor;
16
use gplcart\core\models\Currency;
17
use gplcart\core\models\Price;
18
use gplcart\core\models\Shipping;
19
use gplcart\core\models\CountryState;
20
use gplcart\core\models\Store;
21
use gplcart\core\models\Translation;
22
use gplcart\core\models\User;
23
use gplcart\core\Module;
24
25
/**
26
 * Manages basic behaviors and data related to Shippo module
27
 */
28
class Shippo
29
{
30
31
    /**
32
     * Module class instance
33
     * @var \gplcart\core\Module $module
34
     */
35
    protected $module;
36
37
    /**
38
     * Api model instance
39
     * @var \gplcart\modules\shippo\models\Api $api
40
     */
41
    protected $api;
42
43
    /**
44
     * Translation UI model instance
45
     * @var \gplcart\core\models\Translation $translation
46
     */
47
    protected $translation;
48
49
    /**
50
     * User model class instance
51
     * @var \gplcart\core\models\User $user
52
     */
53
    protected $user;
54
55
    /**
56
     * Price model class instance
57
     * @var \gplcart\core\models\Price $price
58
     */
59
    protected $price;
60
61
    /**
62
     * Shipping model class instance
63
     * @var \gplcart\core\models\Shipping $shipping
64
     */
65
    protected $shipping;
66
67
    /**
68
     * State model class instance
69
     * @var \gplcart\core\models\CountryState $state
70
     */
71
    protected $state;
72
73
    /**
74
     * Currency model class instance
75
     * @var \gplcart\core\models\Currency $currency
76
     */
77
    protected $currency;
78
79
    /**
80
     * Address model class instance
81
     * @var \gplcart\core\models\Address $address
82
     */
83
    protected $address;
84
85
    /**
86
     * Store model instance
87
     * @var \gplcart\core\models\Store $store
88
     */
89
    protected $store;
90
91
    /**
92
     * Convertor model class instance
93
     * @var \gplcart\core\models\Convertor $convertor
94
     */
95
    protected $convertor;
96
97
    /**
98
     * Session class instance
99
     * @var \gplcart\core\helpers\Session $session
100
     */
101
    protected $session;
102
103
    /**
104
     * An array of Shippo module settings
105
     * @var array
106
     */
107
    protected $settings = array();
108
109
    /**
110
     * Shippo constructor.
111
     * @param Module $module
112
     * @param Api $api
113
     * @param Translation $translation
114
     * @param User $user
115
     * @param Price $price
116
     * @param Currency $currency
117
     * @param Address $address
118
     * @param Store $store
119
     * @param CountryState $state
120
     * @param Shipping $shipping
121
     * @param Session $session
122
     * @param Convertor $convertor
123
     */
124
    public function __construct(Module $module, Api $api, Translation $translation, User $user, Price $price,
125
                                Currency $currency, Address $address, Store $store, CountryState $state, Shipping $shipping,
126
                                Session $session, Convertor $convertor)
127
    {
128
        $this->api = $api;
129
        $this->user = $user;
130
        $this->price = $price;
131
        $this->state = $state;
132
        $this->store = $store;
133
        $this->session = $session;
134
        $this->address = $address;
135
        $this->currency = $currency;
136
        $this->shipping = $shipping;
137
        $this->convertor = $convertor;
138
        $this->translation = $translation;
139
140
        $this->module = $module;
141
        $this->settings = $this->module->getSettings('shippo');
142
    }
143
144
    /**
145
     * Returns an array of carrier names keyed by id
146
     * @return array
147
     */
148
    public function getCarrierNames()
149
    {
150
        return gplcart_config_get(__DIR__ . '/../config/carriers.php');
151
    }
152
153
    /**
154
     * Returns an array of service names keyed by id
155
     * @return array
156
     */
157
    public function getServiceNames()
158
    {
159
        return gplcart_config_get(__DIR__ . '/../config/services.php');
160
    }
161
162
    /**
163
     * Calculates shipping rates and sets available shipping methods on checkout page
164
     * @param array $data
165
     */
166
    public function calculate(array &$data)
167
    {
168
        $address = $this->getSourceAddress($data);
169
        $rates = $this->getRates($address, $data['cart'], $data['order']);
170
        $this->setShippingMethodsCheckout($data, $rates);
171
    }
172
173
    /**
174
     * Validates order shipping rates before an order is created
175
     * @param array $order
176
     * @param array $options
177
     * @param array $result
178
     * @return null|bool
179
     */
180
    public function validate(array &$order, $options, array &$result)
181
    {
182
        $method = $this->shipping->get($order['shipping']);
183
184
        if (empty($method['module']) || $method['module'] !== 'shippo') {
185
            return null;
186
        }
187
188
        $error_result = array(
189
            'severity' => 'danger',
190
            'redirect' => '', // Stay on the same page
191
            'message' => $this->translation->text('Please recalculate shipping rates')
192
        );
193
194
        // Forbid further processing if shipping component has not been set
195
        if (!isset($order['data']['components']['shipping']['price']) && empty($options['admin'])) {
196
            $result = $error_result;
197
            return false;
198
        }
199
200
        $rates = $this->getRates($order['shipping_address'], $order['cart'], $order);
201
202
        $this->setShippingMethod($method, $rates, $order['currency']);
203
204
        // Forbid further processing and redirect back if shipping rates don't match
205
        // Validate only "normal" submits.
206
        // In admin mode shipping prices can be adjusted by administrator
207
        if (empty($options['admin'])
208
            && isset($method['price'])
209
            && $method['price'] != $order['data']['components']['shipping']['price']) {
210
            $result = $error_result;
211
            return false;
212
        }
213
214
        // Save Shippo request in the order data to get later labels etc.
215
        $order['data']['shippo'] = $method['data'];
216
        return true;
217
    }
218
219
    /**
220
     * Returns an array of cached rates
221
     * @param array|integer $address
222
     * @param array $cart
223
     * @param array $order
224
     * @return array
225
     */
226
    public function getRates($address, array $cart, array $order)
227
    {
228
        if (!is_array($address)) {
229
            $address = $this->address->get($address);
230
        }
231
232
        $to_address = $this->getShippoAddress($address);
233
234
        if (empty($to_address)) {
235
            return array();
236
        }
237
238
        $session_limit = 10;
239
        $session_rates = $this->session->get('shippo', array());
240
241
        if (count($session_rates) > $session_limit) {
242
            $this->session->delete('shippo');
243
        }
244
245
        $cache_id = $this->getCacheKey($this->settings['sender'], $to_address);
246
247
        if (!empty($session_rates[$cache_id])) {
248
            return $session_rates[$cache_id];
249
        }
250
251
        try {
252
253
            if ($this->api->isValidAddress($to_address) !== true) {
254
                return $this->getDefaultRates();
255
            }
256
257
            $parcel = $this->getParcel($cart, $order);
258
            $response = $this->api->getRates($this->settings['sender'], $to_address, $parcel);
259
260
        } catch (Exception $ex) {
261
            trigger_error($ex->getMessage());
262
            return array();
263
        }
264
265
        if (empty($response)) {
266
            return $this->getDefaultRates();
267
        }
268
269
        $session_rates[$cache_id] = $response;
270
        $this->session->set('shippo', $session_rates);
271
272
        return $response;
273
    }
274
275
    /**
276
     * Returns a unique cache key for a combination of recipient and sender addresses
277
     * @param array $from
278
     * @param array $to
279
     * @return string
280
     */
281
    protected function getCacheKey(array $from, $to)
282
    {
283
        ksort($to);
284
        ksort($from);
285
286
        return md5(json_encode(array($from, $to)));
287
    }
288
289
    /**
290
     * Sets shipping methods available for the order shipping address
291
     * @param array $data
292
     * @param array $rates
293
     */
294
    protected function setShippingMethodsCheckout(array &$data, array $rates)
295
    {
296
        foreach ($data['shipping_methods'] as &$method) {
297
298
            if (empty($method['module']) || $method['module'] !== 'shippo') {
299
                continue;
300
            }
301
302
            if (!$this->setShippingMethod($method, $rates, $data['order']['currency'])) {
303
                unset($data['shipping_methods'][$method['id']]);
304
            }
305
        }
306
307
        // Show cheapest items first
308
        gplcart_array_sort($data['shipping_methods'], 'price');
309
    }
310
311
    /**
312
     * Adjust price and label for the given shipping method
313
     * @param array $method
314
     * @param array $rates
315
     * @param string $currency
316
     * @return boolean
317
     */
318
    protected function setShippingMethod(array &$method, array $rates, $currency)
319
    {
320
        $service_id = $this->getShippoServiceId($method['id']);
321
322
        if (empty($rates[$service_id])) {
323
            return false;
324
        }
325
326
        $converted = $this->currency->convert($rates[$service_id]['amount_local'], $rates[$service_id]['currency_local'], $currency);
327
        $price = $this->price->format($converted, $currency, false, true);
328
329
        $method['title'] .= " - $price";
330
331
        if (isset($rates[$service_id]['days'])) {
332
            $method['description'] = $this->translation->text('Estimated delivery time: @num day(s)', array(
333
                '@num' => $rates[$service_id]['days']));
334
        }
335
336
        $method['data'] = $rates[$service_id];
337
        $method['price'] = $this->price->amount($converted, $currency);
338
339
        return true;
340
    }
341
342
    /**
343
     * Returns the default rates if unabled to calculate via Shippo API
344
     * @return array
345
     */
346
    protected function getDefaultRates()
347
    {
348
        if (empty($this->settings['default']['method'])) {
349
            return array();
350
        }
351
352
        $service_id = $this->getShippoServiceId($this->settings['default']['method']);
353
354
        return array(
355
            $service_id => array(
356
                'currency' => 'USD',
357
                'amount' => $this->settings['default']['price']
358
            )
359
        );
360
    }
361
362
    /**
363
     * Converts the system method ID into Shippo's service ID
364
     * @param string $system_method_id
365
     * @return string
366
     */
367
    protected function getShippoServiceId($system_method_id)
368
    {
369
        return str_replace('shippo_', '', $system_method_id);
370
    }
371
372
    /**
373
     * Converts an address into Shippo's format
374
     * @param array $data
375
     * @return array
376
     */
377
    protected function getShippoAddress(array $data)
378
    {
379
        if (empty($data)) {
380
            return array();
381
        }
382
383
        $name = array();
384
385
        if (isset($data['first_name'])) {
386
            $name[] = $data['first_name'];
387
        }
388
        if (isset($data['middle_name'])) {
389
            $name[] = $data['middle_name'];
390
        }
391
        if (isset($data['last_name'])) {
392
            $name[] = $data['last_name'];
393
        }
394
395
        $city = '';
396
397
        if (isset($data['city_name'])) {
398
            $city = $data['city_name'];
399
        } else if (isset($data['city_id']) && !is_numeric($data['city_id'])) {
400
            $city = $data['city_id'];
401
        }
402
403
        $state = '';
404
405
        if (isset($data['state_name'])) {
406
            $state = $data['state_name'];
407
        } else if (isset($data['state_id'])) {
408
            $state_data = $this->state->get($data['state_id']);
409
            $state = isset($state_data['name']) ? $state_data['name'] : '';
410
        }
411
412
        if (!empty($data['country'])) {
413
            $country = $data['country'];
414
        } else {
415
            $store = $this->store->getDefault(true);
416
            $country = $store['data']['country'];
417
        }
418
419
        return array(
420
            'city' => $city,
421
            'state' => $state,
422
            'country' => $country,
423
            'name' => implode(' ', $name),
424
            'email' => $this->user->getSession('email'),
425
            'phone' => isset($data['phone']) ? $data['phone'] : '',
426
            'zip' => isset($data['postcode']) ? $data['postcode'] : '',
427
            'company' => isset($data['company']) ? $data['company'] : '',
428
            'street1' => isset($data['address_1']) ? $data['address_1'] : '',
429
            'street2' => isset($data['address_2']) ? $data['address_2'] : '',
430
        );
431
    }
432
433
    /**
434
     * Returns an array of parcel data in Shippo's format
435
     * @param array $cart
436
     * @param array $order
437
     * @return array
438
     */
439
    protected function getParcel(array $cart, array $order)
440
    {
441
        $dimensions = $this->getDimensions($cart, $order);
442
        $dimensions += $this->settings['default'];
443
444
        return array(
445
            'width' => $dimensions['width'],
446
            'height' => $dimensions['height'],
447
            'length' => $dimensions['length'],
448
            'weight' => $dimensions['weight'],
449
            'mass_unit' => $order['weight_unit'],
450
            'distance_unit' => $order['size_unit']
451
        );
452
    }
453
454
    /**
455
     * Returns total dimensions of all products in the order
456
     * @param array $cart
457
     * @param array $order
458
     * @return array
459
     */
460
    protected function getDimensions(array $cart, array $order)
461
    {
462
        $width = $height = $length = $weight = array();
463
464
        foreach ($cart['items'] as $item) {
465
            $product = $item['product'];
466
            $width[] = (float) $this->convertor->convert($product['width'], $product['size_unit'], $order['size_unit']);
467
            $height[] = (float) $this->convertor->convert($product['height'], $product['size_unit'], $order['size_unit']);
468
            $length[] = (float) $this->convertor->convert($product['length'], $product['size_unit'], $order['size_unit']);
469
            $weight[] = (float) $this->convertor->convert($product['weight'], $product['weight_unit'], $order['weight_unit']);
470
        }
471
472
        $result = array(
473
            'height' => max($height),
474
            'length' => max($length),
475
            'width' => array_sum($width),
476
            'weight' => array_sum($weight),
477
        );
478
479
        if (count(array_filter($result)) != count($result)) {
480
            return array();
481
        }
482
483
        return $result;
484
    }
485
486
    /**
487
     * Sets a source shipping address
488
     * @param array $data
489
     * @return array
490
     */
491
    protected function getSourceAddress(array $data)
492
    {
493
        if (!empty($data['order']['shipping_address'])) {
494
            $address_id = $data['order']['shipping_address'];
495
            return $this->address->get($address_id);
496
        }
497
498
        if (!empty($data['show_shipping_address_form']) && !empty($data['address']['shipping'])) {
499
            return $data['address']['shipping'];
500
        }
501
502
        return array();
503
    }
504
505
}
506