Passed
Push — 1.0 ( 07b100...83f8df )
by Morven
05:31
created

ShippingCalculator::getWeight()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverCommerce\OrdersAdmin\Tools;
4
5
use SilverStripe\Security\Member;
6
use SilverStripe\ORM\ArrayList;
7
use SilverStripe\SiteConfig\SiteConfig;
8
use SilverStripe\i18n\i18n;
9
use SilverCommerce\OrdersAdmin\Control\ShoppingCart;
0 ignored issues
show
Bug introduced by
The type SilverCommerce\OrdersAdmin\Control\ShoppingCart was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use SilverCommerce\OrdersAdmin\Model\Discount;
11
use SilverCommerce\OrdersAdmin\Model\PostageArea;
12
13
/**
14
 * Shipping calculator is a basic helper class that can be used to query
15
 * the shipping table.
16
 * 
17
 * At the moment we only output shipping areas based on weight/cost/
18
 * items and location. Buit this can now be expanded more easily if
19
 * needed.
20
 * 
21
 * @author ilateral ([email protected])
22
 * @package orders-admin
23
 */
24
class ShippingCalculator
25
{
26
    
27
    /**
28
     * 2 character country code
29
     * 
30
     * @var string
31
     */
32
    private $country_code;
33
    
34
    public function setCountryCode($value)
35
    {
36
        $this->country_code = $value;
37
        return $this;
38
    }
39
40
    public function getCountryCode()
41
    {
42
        return $this->country_code;
43
    }
44
    
45
    /**
46
     * Zip/postal code for the search
47
     * 
48
     * @var string
49
     */
50
    private $zipcode;
51
        
52
    public function setZipCode($value)
53
    {
54
        $this->zipcode = $value;
55
        return $this;
56
    }
57
58
    public function getZipCode()
59
    {
60
        return $this->zipcode;
61
    }
62
    
63
    /**
64
     * The total cost we will be checking the cart against
65
     * 
66
     * @var Float
67
     */
68
    private $cost = 0.0;
69
    
70
    public function setCost($value)
71
    {
72
        $this->cost = $value;
73
        return $this;
74
    }
75
76
    public function getCost()
77
    {
78
        return $this->cost;
79
    }
80
    
81
    /**
82
     * The total weight to check against
83
     * 
84
     * @var Float
85
     */
86
    private $weight = 0;
87
    
88
    public function setWeight($value)
89
    {
90
        $this->weight = $value;
91
        return $this;
92
    }
93
94
    public function getWeight()
95
    {
96
        return $this->weight;
97
    }
98
    
99
    /**
100
     * The total numbers of items to check against
101
     * 
102
     * @var Float
103
     */
104
    private $items = 0;
105
    
106
    public function setItems($value)
107
    {
108
        $this->items = $value;
109
        return $this;
110
    }
111
112
    public function getItems()
113
    {
114
        return $this->items;
115
    }
116
117
    /**
118
     * An {@link Discount} object
119
     * 
120
     * @var Discount
121
     */
122
    private $discount;
123
    
124
    public function setDiscount(Discount $value)
125
    {
126
        $this->discount = $value;
127
        return $this;
128
    }
129
130
    public function getDiscount()
131
    {
132
        return $this->discount;
133
    }
134
    
135
    /**
136
     * Should we also check for wildcards when doing location/
137
     * postcode searches
138
     * 
139
     * @var Boolean
140
     */
141
    private $include_wildcards = true;
142
    
143
    public function setWildcards($value)
144
    {
145
        $this->include_wildcards = $value;
146
        return $this;
147
    }
148
149
    public function getWildcards()
150
    {
151
        return $this->include_wildcards;
152
    }
153
    
154
    
155
    /**
156
     * Simple constructor that sets the country code and zip. If no
157
     * country is set, this class attempts to autodetect.
158
     * 
159
     * @param zipcode string of the zipo/postal code
160
     * @param country_code 2 character country code
161
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment 2 at position 0 could not be parsed: Unknown type name '2' at position 0 in 2.
Loading history...
162
    public function __construct($zipcode, $country_code = null)
163
    {
164
        if ($country_code) {
165
            $this->country_code = $country_code;
166
        } else {
167
            $this->country_code = $this->locale();
168
        }
169
        
170
        if ($zipcode) {
171
            $this->zipcode = $zipcode;
172
        }
173
    }
174
    
175
    /**
176
     * Get the locale of the Member, or if we're not logged in or don't have a locale, use the default one
177
     * @return string
178
     */
179
    protected function locale()
180
    {
181
        if (($member = Member::currentUser()) && $member->Locale) {
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Security\Member::currentUser() has been deprecated: 5.0.0 use Security::getCurrentUser() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

181
        if (($member = /** @scrutinizer ignore-deprecated */ Member::currentUser()) && $member->Locale) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
182
            return $member->Locale;
183
        }
184
            
185
        return i18n::get_locale();
186
    }
187
188
    /**
189
     * Generate a free postage object we can use in our code.
190
     * 
191
     * @todo This is a little hacky, ideally we need to find a cleaner
192
     * way of dealing with postage options that doesn't involve unsaved
193
     * database objects.
194
     * 
195
     * @return PostageArea
196
     */
197
    public static function CreateFreePostageObject()
198
    {
199
        $postage = PostageArea::create();
200
        $postage->ID = -1;
201
        $postage->Title = _t("OrdersAdmin.FreeShipping", "Free Shipping");
202
        $postage->Country = "*";
203
        $postage->ZipCode = "*";
204
        
205
        return $postage;
206
    }
207
    
208
    
209
    /**
210
     * Find relevent postage rates, based on supplied:
211
     * - Country
212
     * - Zip/postal code
213
     * - Weight
214
     * - Cost
215
     * - Number of Items
216
     * 
217
     * This is returned as an ArrayList that can be looped through.
218
     *
219
     * @return ArrayList
220
     */
221
    public function getPostageAreas()
222
    {
223
        $return = ArrayList::create();
224
        $config = SiteConfig::current_site_config();
225
        $discount = $this->getDiscount();
226
        $pc_match = "";
227
228
        if ($this->include_wildcards) {
229
            $filter = array(
230
                "Country:PartialMatch" => array($this->country_code, "*"),
231
            );
232
        } else {
233
            $filter = array(
234
                "Country:PartialMatch" => $this->country_code
235
            );
236
        }
237
238
        // Find any postage areas that match our filter
239
        $postage_areas = $config
240
            ->PostageAreas()
241
            ->filter($filter);
242
243
        // Next perform a check to look for the closest match to the
244
        // current zip/postal code (so we can correctly filter).
245
        foreach ($postage_areas as $item) {
246
            $postal_codes = explode(",", $item->ZipCode);
247
248
            foreach ($postal_codes as $code) {
249
                $code = strtolower($code);
250
                $new_pc_match = substr(strtolower($this->zipcode), 0, strlen($code));
251
252
                if ($code && $code == $new_pc_match && strlen($new_pc_match) > strlen($pc_match)) {
253
                    $pc_match = $new_pc_match;
254
                }
255
            }
256
        }
257
258
        // Now perform a zip/postal code comparision against our list
259
        // and add some 
260
        foreach ($postage_areas as $item) {
261
            $postal_codes = explode(",", $item->ZipCode);
262
263
            foreach ($postal_codes as $code) {
264
                $code = strtolower($code);
265
266
                if ($code && $code == substr(strtolower($this->zipcode), 0, strlen($pc_match))) {
267
                    $return->add($item);
268
                } elseif (!$pc_match && $item->ZipCode == "*" && $this->include_wildcards) {
269
                    $return->add($item);
270
                }
271
            }
272
        }
273
274
        // Check if any discounts are set with free postage
275
        // This is a little hacky at the moment, need to find a nicer
276
        // way to add free shipping.
277
        if ($discount && $discount->Type == "Free Shipping" && ((strpos($discount->Country, $this->country_code) !== false) || $discount->Country == "*")) {
0 ignored issues
show
Bug Best Practice introduced by
The property Country does not exist on SilverCommerce\OrdersAdmin\Model\Discount. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property Type does not exist on SilverCommerce\OrdersAdmin\Model\Discount. Since you implemented __get, consider adding a @property annotation.
Loading history...
introduced by
The condition $discount && $discount->...iscount->Country == '*' can never be true.
Loading history...
278
            $postage = $this->CreateFreePostageObject();
279
            $return->add($postage);
280
        }
281
282
        // Before doing anything else, remove any wildcards (if needed)
283
        $exact_country = false;
284
285
        // Find any countries that are exactly matched 
286
        foreach ($return as $location) {
287
            if ($location->Country != "*") {
288
                $exact_country = true;
289
            }
290
        }
291
292
        // If exactly matched, remove any wildcards
293
        foreach ($return as $location) {
294
            if ($exact_country && $location->Country == "*" && $location->ID != -1) {
295
                $return->remove($location);
296
            }
297
        }
298
299
        // Now we have a list of locations, start checking for additional
300
        // rules an remove if not applicable.
301
        $total_cost = $this->cost;
302
        $total_weight = $this->weight;
303
        $total_items = $this->items;
304
305
        $max_cost = 0;
306
        $max_weight = 0;
307
        $max_items = 0;
308
309
        // First loop through and find items that are invalid
310
        foreach ($return as $location) {
311
            if ($location->Calculation == "Price" && ($total_cost < $location->Unit)) {
312
                $return->remove($location);
313
            }
314
315
            if ($location->Calculation == "Weight" && ($total_weight < $location->Unit)) {
316
                $return->remove($location);
317
            }
318
319
            if ($location->Calculation == "Items" && ($total_items < $location->Unit)) {
320
                $return->remove($location);
321
            }
322
        }
323
324
        // Now find max values based on units
325
        foreach ($return as $location) {
326
            if ($location->Calculation == "Price" && ($location->Unit > $max_cost)) {
327
                $max_cost = $location->Unit;
328
            }
329
330
            if ($location->Calculation == "Weight" && ($location->Unit > $max_weight)) {
331
                $max_weight = $location->Unit;
332
            }
333
334
            if ($location->Calculation == "Items" && ($location->Unit > $max_items)) {
335
                $max_items = $location->Unit;
336
            }
337
        }
338
339
        // Now loop through again and calculate which brackets each
340
        // Location fits in
341
        foreach ($return as $location) {
342
            if ($location->Calculation == "Price" && ($location->Unit < $max_cost)) {
343
                $return->remove($location);
344
            }
345
346
            if ($location->Calculation == "Weight" && ($location->Unit < $max_weight)) {
347
                $return->remove($location);
348
            }
349
350
            if ($location->Calculation == "Items" && ($location->Unit < $max_items)) {
351
                $return->remove($location);
352
            }
353
        }
354
355
        return $return;
356
    }
357
}
358