Completed
Push — master ( 291a36...004164 )
by Nicolaas
02:23
created

canPurchaseByCountry()   C

Complexity

Conditions 18
Paths 183

Size

Total Lines 57
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 57
rs 5.873
c 0
b 0
f 0
cc 18
eloc 32
nc 183
nop 3

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
 * Adds pricing to Buyables
4
 *
5
 *
6
 */
7
8
class CountryPrice_BuyableExtension extends DataExtension
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
9
{
10
    private static $db = array(
0 ignored issues
show
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
11
        "AllCountries" => "Boolean"
12
    );
13
14
    private static $many_many = array(
0 ignored issues
show
Unused Code introduced by
The property $many_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
15
        "IncludedCountries" => "EcommerceCountry",
16
        "ExcludedCountries" => "EcommerceCountry"
17
    );
18
19
    private static $allow_usage_of_distributor_backup_country_pricing = false;
0 ignored issues
show
Unused Code introduced by
The property $allow_usage_of_distributor_backup_country_pricing is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
20
21
    public function updateCMSFields(FieldList $fields)
22
    {
23
        $excludedCountries = EcommerceCountry::get()
24
            ->filter(array("DoNotAllowSales" => 1, "AlwaysTheSameAsID" => 0));
25
        if ($excludedCountries->count()) {
26
            $excludedCountries = $excludedCountries->map('ID', 'Name')->toArray();
27
        }
28
        $includedCountries = EcommerceCountry::get()
29
            ->filter(array("DoNotAllowSales" => 0, "AlwaysTheSameAsID" => 0));
30
        if ($includedCountries->count()) {
31
            $includedCountries = $includedCountries->map('ID', 'Name')->toArray();
32
        }
33
        if ($this->owner->AllCountries) {
34
            $tabs = new TabSet('Countries',
35
                new Tab(
36
                    'Include',
37
                    new CheckboxField("AllCountries", "All Countries")
38
                )
39
            );
40
        } else {
41
            $tabs = new TabSet('Countries',
42
                $includeTab = new Tab(
43
                    'Include',
44
                    new CheckboxField("AllCountries", "All Countries")
45
                ),
46
                $excludeTab = new Tab(
47
                    'Exclude'
48
                )
49
            );
50
            if (count($excludedCountries)) {
51
                $includeTab->push(
52
                    new LiteralField(
53
                        "ExplanationInclude",
54
                        "<p>Products are not available in the countries listed below.  You can include sales of <i>".$this->owner->Title."</i> to new countries by ticking the box(es) next to any country.</p>"
55
                    )
56
                );
57
                $includeTab->push(
58
                    new CheckboxSetField('IncludedCountries', '', $excludedCountries)
59
                );
60
            }
61
            if (count($includedCountries)) {
62
                $excludeTab->push(
63
                    new LiteralField("ExplanationExclude", "<p>Products are available in all countries listed below.  You can exclude sales of <i>".$this->owner->Title."</i> from these countries by ticking the box next to any of them.</p>")
64
                );
65
                $excludeTab->push(
66
                    new CheckboxSetField('ExcludedCountries', '', $includedCountries)
67
                );
68
            }
69
        }
70
71
72
        if ($this->owner->ID) {
73
            //start cms_object hack
74
            CountryPrice::set_cms_object($this->owner);
75
            //end cms_object hack
76
            $source = $this->owner->AllCountryPricesForBuyable();
77
            $table = new GridField(
78
                'CountryPrices',
79
                'Country Prices',
80
                $source,
81
                GridFieldConfig_RecordEditor::create()
82
            );
83
            $tab = 'Root.Countries.Pricing';
84
            $fields->addFieldsToTab(
85
                $tab,
86
                array(
87
                    NumericField::create('Price', 'Main Price', '', 12),
88
                    HeaderField::create('OtherCountryPricing', "Prices for other countries"),
89
                    $table
90
                )
91
            );
92
        }
93
94
        $fields->addFieldToTab('Root.Countries', $tabs);
95
    }
96
97
    private $debug = false;
98
99
    /**
100
     * This is called from /ecommerce/code/Product
101
     * returning NULL is like returning TRUE OR FALSE, i.e. ignore this.
102
     * @param Member (optional)   $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be null|Member?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
103
     * @param bool (optional)     $checkPrice
104
     * @return false | null
0 ignored issues
show
Documentation introduced by
Should the return type not be null|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
105
     */
106
    public function canPurchaseByCountry(Member $member = null, $checkPrice = true, $countryCode = '')
0 ignored issues
show
Unused Code introduced by
The parameter $member is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $checkPrice is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
107
    {
108
        $countryObject = CountryPrice_EcommerceCountry::get_real_country($countryCode);
109
        if ($countryObject) {
110
            if ($this->debug) {debug::log('found country object: '.$countryObject->Code);}
111
            $countryCode = $countryObject->Code;
112
        }
113
        if ($countryCode == '') {
114
            if ($this->debug) {debug::log('There is no country Code! ');}
115
116
            //we can not decide
117
            return null;
118
        } else {
119
            $canSell = false;
120
121
            //easy  ... overrules all ...
122
            if ($this->owner->AllCountries) {
123
                //is there a valid price ???
124
                if ($this->debug) {debug::log('All countries applies - updated  ... new price = '.floatval($this->owner->updateCalculatedPrice()));}
125
                $canSell = true;
126
            } else {
127
128
129
                //excluded first...
130
                $excluded = $this->owner->getManyManyComponents('ExcludedCountries', "\"Code\" = '$countryCode'")->Count();
131
                if ($excluded) {
132
                    if ($this->debug) {debug::log('excluded country');}
133
134
                    //no!
135
                    return false;
136
                }
137
138
                //default country is included by default ...
139
                if ($countryCode == EcommerceConfig::get('EcommerceCountry', 'default_country_code')) {
140
                    if ($this->debug) {debug::log('we are in the default country! exiting now ... ');}
141
                    $canSell = true;
142
                } elseif($this->owner->IncludedCountries()->count()) {
143
                    $included = $this->owner->getManyManyComponents('IncludedCountries', "\"Code\" = '$countryCode'")->Count();
144
                    if ($included) {
145
                        if ($this->debug) {debug::log('In included countries');}
146
                        //null basically means - ignore ...
147
                        $canSell = true;
148
                    } else {
149
                        //if countries are included and the current country is not included ...
150
                        return false;
151
                    }
152
                }
153
            }
154
            if ($this->debug) {debug::log('the product is '.($canSell ? '' : 'NOT ').' for sale - lets check price ... ');}
155
156
            //is there a valid price ???
157
            $countryPrice = $this->owner->getCalculatedPrice();
158
            if ($this->debug) {debug::log('nothing applies, but we have a country price... '.$countryPrice);}
159
160
            return floatval($countryPrice) > 0 ? null : false;
161
        }
162
    }
163
164
    /**
165
     *
166
     * @return DataList
167
     */
168
    public function AllCountryPricesForBuyable()
169
    {
170
        $filterArray = array("ObjectClass" => ClassInfo::subclassesFor($this->ownerBaseClass), "ObjectID" => $this->owner->ID);
171
        return CountryPrice::get()
172
            ->filter($filterArray);
173
    }
174
175
    /**
176
     * returns all the prices for a particular country and/or currency
177
     * for the object
178
     * @param string (optional) $country
179
     * @param string (optional) $currency
180
     * @return DataList
181
     */
182
    public function CountryPricesForCountryAndCurrency($countryCode = null, $currency = null)
183
    {
184
        $countryObject = CountryPrice_EcommerceCountry::get_real_country($countryCode);
185
        $allCountryPricesForBuyable = $this->AllCountryPricesForBuyable();
186
        if ($countryObject) {
187
            $filterArray["Country"] = $countryObject->Code;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$filterArray was never initialized. Although not strictly required by PHP, it is generally a good practice to add $filterArray = 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...
188
        }
189
        if ($currency) {
190
            $filterArray["Currency"] = $currency;
0 ignored issues
show
Bug introduced by
The variable $filterArray 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...
191
        }
192
        $allCountryPricesForBuyable = $allCountryPricesForBuyable->filter($filterArray);
193
        return $allCountryPricesForBuyable;
194
    }
195
196
    private static $_buyable_price = array();
197
198
    /***
199
     *
200
     * updates the calculated price to the local price...
201
     * if there is no price then we return 0
202
     * if the default price can be used then we use NULL (i.e. ignore it!)
203
     * @param float $price (optional)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $price not be double|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
204
     * @return Float | null (ignore this value and use original value)
205
     */
206
    public function updateBeforeCalculatedPrice($price = null)
0 ignored issues
show
Unused Code introduced by
The parameter $price is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
207
    {
208
        $countryCode = '';
209
        $countryObject = CountryPrice_EcommerceCountry::get_real_country();
210
        if ($countryObject) {
211
            $countryCode = $countryObject->Code;
212
        }
213
        if ($countryCode == '' || $countryCode == EcommerceConfig::get('EcommerceCountry', 'default_country_code')) {
214
            if ($this->debug) {
215
                debug::log('No country code or default country code: '.$countryCode);
216
            }
217
218
            return null;
219
        }
220
        $key = $this->owner->ClassName."___".$this->owner->ID.'____'.$countryCode;
221
        if (! isset(self::$_buyable_price[$key])) {
222
            //basics
223
            $currency = null;
0 ignored issues
show
Unused Code introduced by
$currency is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
224
            $currencyCode = null;
225
226
            if ($countryCode) {
227
                $order = ShoppingCart::current_order();
228
                CountryPrice_OrderDOD::localise_order();
229
                $currency = $order->CurrencyUsed();
230
                if ($currency) {
231
                    $currencyCode = strtoupper($currency->Code);
232
                    //1. exact price for country
233
                    if ($currencyCode) {
234
                        $prices = $this->owner->CountryPricesForCountryAndCurrency(
235
                            $countryCode,
236
                            $currencyCode
237
                        );
238 View Code Duplication
                        if ($prices && $prices->count() == 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
239
                            self::$_buyable_price[$key] = $prices->First()->Price;
240
                            return self::$_buyable_price[$key];
241
                        } elseif ($prices) {
242
                            if ($this->debug) {
243
                                debug::log('MAIN COUNTRY: There is an error number of prices: '.$prices->count());
244
                            }
245
                        } else {
246
                            if ($this->debug) {
247
                                debug::log('MAIN COUNTRY: There is no country price: ');
248
                            }
249
                        }
250
                    } else {
251
                        if ($this->debug) {
252
                            debug::log('MAIN COUNTRY: There is no currency code '.$currencyCode.'');
253
                        }
254
                    }
255
                } else {
256
                    if ($this->debug) {
257
                        debug::log('MAIN COUNTRY: there is no currency');
258
                    }
259
                }
260
                if (Config::inst()->get('CountryPrice_BuyableExtension', 'allow_usage_of_distributor_backup_country_pricing')) {
261
                    //there is a specific country price ...
262
                    //check for distributor primary country price
263
                    // if it is the same currency, then use that one ...
264
                    $distributorCountry = CountryPrice_EcommerceCountry::get_distributor_primary_country($countryCode);
265
                    if ($distributorCurrency = $distributorCountry->EcommerceCurrency()) {
266
                        if ($distributorCurrency->ID == $currency->ID) {
267
                            $distributorCurrencyCode = strtoupper($distributorCurrency->Code);
268
                            $distributorCountryCode = $distributorCountry->Code;
269
                            if ($distributorCurrencyCode && $distributorCountryCode) {
270
                                $prices = $this->owner->CountryPricesForCountryAndCurrency(
271
                                    $distributorCountryCode,
272
                                    $distributorCurrencyCode
273
                                );
274 View Code Duplication
                                if ($prices && $prices->count() == 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
275
                                    self::$_buyable_price[$key] = $prices->First()->Price;
276
277
                                    return self::$_buyable_price[$key];
278
                                } elseif ($prices) {
279
                                    if ($this->debug) {
280
                                        debug::log('BACKUP COUNTRY: There is an error number of prices: '.$prices->count());
281
                                    }
282
                                } else {
283
                                    if ($this->debug) {
284
                                        debug::log('BACKUP COUNTRY: There is no country price: ');
285
                                    }
286
                                }
287
                            } else {
288
                                if ($this->debug) {
289
                                    debug::log('BACKUP COUNTRY: We are missing the distributor currency code ('.$distributorCurrencyCode.') or the distributor country code ('.$distributorCountryCode.')');
290
                                }
291
                            }
292
                        } else {
293
                            if ($this->debug) {
294
                                debug::log('BACKUP COUNTRY: The distributor currency ID ('.$distributorCurrency->ID.') is not the same as the order currency ID ('.$currency->ID.').');
295
                            }
296
                        }
297
                    }
298
                } else {
299
                    if ($this->debug) {
300
                        debug::log('We do not allow backup country pricing');
301
                    }
302
                }
303
            } else {
304
                if ($this->debug) {
305
                    debug::log('There is not Country Code ');
306
                }
307
            }
308
            //order must have a country and a currency
309
            if (! $currencyCode ||  ! $countryCode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $currencyCode of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
310
                if ($this->debug) {
311
                    debug::log('No currency ('.$currencyCode.') or no country code ('.$countryCode.') for order: ');
312
                }
313
                self::$_buyable_price[$key] = 0;
314
                return self::$_buyable_price[$key];
315
            }
316
            //catch error 2: no country price BUT currency is not default currency ...
317
            if (EcommercePayment::site_currency() != $currencyCode) {
318
                if ($this->debug) {
319
                    debug::log('site currency  ('.EcommercePayment::site_currency().') is not the same order currency ('.$currencyCode.')');
320
                }
321
                self::$_buyable_price[$key] = 0;
322
                return self::$_buyable_price[$key];
323
            }
324
            if ($this->debug) {
325
                debug::log('SETTING '.$key.' to ZERO - NOT FOR SALE');
326
            }
327
            self::$_buyable_price[$key] = 0;
328
329
            return self::$_buyable_price[$key];
330
        }
331
332
        return self::$_buyable_price[$key];
333
    }
334
335
    /**
336
     * delete the related prices
337
     */
338
    public function onBeforeDelete()
339
    {
340
        $prices = $this->AllCountryPricesForBuyable();
341
        if ($prices && $prices->count()) {
342
            foreach ($prices as $price) {
343
                $price->delete();
344
            }
345
        }
346
    }
347
348
    // VARIATION CODE ONLY
349
350
    // We us isNew to presave if we should add some country price for the newy created variation based on the "possibly" pre-existing ones of the product
351
    protected $isNew = false;
352
353
    public function onBeforeWrite()
354
    {
355
        $this->isNew = $this->owner->ID == 0;
356
    }
357
358
359
    public function onAfterWrite()
360
    {
361
        //only run if these are variations
362
        if ($this->isNew && $this->owner instanceof ProductVariation) {
0 ignored issues
show
Bug introduced by
The class ProductVariation does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
363
            $product = $this->owner->Product();
364
            if ($product) {
365
                $productPrices = $product->AllCountryPricesForBuyable();
366
                foreach ($productPrices as $productPrice) {
367
                    if ($productPrice->Country) {
368
                        if (
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
369
                            $countryVariationPrice = CountryPrice::get()
0 ignored issues
show
Unused Code introduced by
$countryVariationPrice is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
370
                            ->filter(
371
                                array(
372
                                    "Country" => $productPrice->Country,
373
                                    "ObjectClass" => $this->owner->ClassName,
374
                                    "ObjectID" => $this->owner->ID
375
                                )
376
                            )
377
                            ->First()
378
                        ) {
379
                            //do nothing
380
                        } else {
381
                            $countryVariationPrice = new CountryPrice(
382
                                array(
383
                                    'Price' => $productPrice->Price,
384
                                    'Country' => $productPrice->Country,
385
                                    'Currency' => $productPrice->Currency,
386
                                    'ObjectClass' => $this->owner->ClassName,
387
                                    'ObjectID' => $this->owner->ID
388
                                )
389
                            );
390
                            $countryVariationPrice->write();
391
                        }
392
                    }
393
                }
394
            }
395
        }
396
    }
397
398
    /**
399
     * as long as we do not give distributors access to the Products
400
     * this is fairly safe.
401
     * @param member (optiona) $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be optiona|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
402
     * @return null / bool
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
403
     */
404
    public function canEdit($member = null)
405
    {
406
        if (! $member) {
407
            $member = Member::currentUser();
408
        }
409
        if ($member) {
410
            $distributor = $member->Distributor();
411
            if ($distributor->exists()) {
412
                return true;
413
            }
414
        }
415
        return false;
416
    }
417
}
418