Shippo::getShippoAddress()   F
last analyzed

Complexity

Conditions 17
Paths 6145

Size

Total Lines 55
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 55
rs 3.4889
c 0
b 0
f 0
cc 17
eloc 37
nc 6145
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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