Completed
Push — master ( 394be6...291a36 )
by Nicolaas
04:43 queued 02:28
created

canPurchaseByCountry()   D

Complexity

Conditions 23
Paths 267

Size

Total Lines 58
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 58
rs 4.7063
c 0
b 0
f 0
cc 23
eloc 38
nc 267
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, 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 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...
105
     */
106
    public function canPurchaseByCountry(Member $member = null, $checkPrice = true, $countryCode = '')
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
            $excluded = $this->owner->getManyManyComponents('ExcludedCountries', "\"Code\" = '$countryCode'")->Count();
121
            if ($excluded) {
122
                if ($this->debug) {debug::log('excluded country');}
123
124
                //no!
125
                return false;
126
            }
127
            elseif ($this->owner->AllCountries) {
128
                //is there a valid price ???
129
                if ($this->debug) {debug::log('All countries applies - updated  ... new price = '.floatval($this->owner->updateCalculatedPrice()));}
130
                $canSell = true;
131
            } elseif ($countryCode == EcommerceConfig::get('EcommerceCountry', 'default_country_code')) {
132
                if ($this->debug) {debug::log('we are in the default country! exiting now ... ');}
133
                $canSell = true;
134
            } elseif($this->IncludedCountries()->count()) {
0 ignored issues
show
Bug introduced by
The method IncludedCountries() does not seem to exist on object<CountryPrice_BuyableExtension>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
135
                $included = $this->owner->getManyManyComponents('IncludedCountries', "\"Code\" = '$countryCode'")->Count();
136
                if ($included) {
137
                    if ($this->debug) {debug::log('In included countries');}
138
                    //null basically means - ignore ...
139
                    $canSell = true;
140
                } else {
141
142
                    return false;
143
                }
144
            }
145
            if ($this->debug) {debug::log('the product is for '.($canSell ? '' : 'NOT ').'sale in principal... ');}
146
            if (
147
                $canSell &&
148
                $this->owner instanceof Product &&
149
                $this->owner->hasMethod('hasVariations') &&
150
                $this->owner->hasVariations()->count()
151
            ) {
152
                if ($this->debug) {debug::log('check variations ... ');}
153
                //check variations ...
154
                return $this->owner->Variations()->First()->canPurchaseByCountry($member, $checkPrice);
155
            }
156
157
            //is there a valid price ???
158
            $countryPrice = $this->owner->getCalculatedPrice();
159
            if ($this->debug) {debug::log('nothing applies, but we have a country price... '.$countryPrice);}
160
161
            return floatval($countryPrice) > 0 ? null : false;
162
        }
163
    }
164
165
    /**
166
     *
167
     * @return DataList
168
     */
169
    public function AllCountryPricesForBuyable()
170
    {
171
        $filterArray = array("ObjectClass" => ClassInfo::subclassesFor($this->ownerBaseClass), "ObjectID" => $this->owner->ID);
172
        return CountryPrice::get()
173
            ->filter($filterArray);
174
    }
175
176
    /**
177
     * returns all the prices for a particular country and/or currency
178
     * for the object
179
     * @param string (optional) $country
180
     * @param string (optional) $currency
181
     * @return DataList
182
     */
183
    public function CountryPricesForCountryAndCurrency($countryCode = null, $currency = null)
184
    {
185
        $countryObject = CountryPrice_EcommerceCountry::get_real_country($countryCode);
186
        $allCountryPricesForBuyable = $this->AllCountryPricesForBuyable();
187
        if ($countryObject) {
188
            $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...
189
        }
190
        if ($currency) {
191
            $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...
192
        }
193
        $allCountryPricesForBuyable = $allCountryPricesForBuyable->filter($filterArray);
194
        return $allCountryPricesForBuyable;
195
    }
196
197
    private static $_buyable_price = array();
198
199
    /***
200
     *
201
     * updates the calculated price to the local price...
202
     * if there is no price then we return 0
203
     * if the default price can be used then we use NULL (i.e. ignore it!)
204
     * @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...
205
     * @return Float | null (ignore this value and use original value)
206
     */
207
    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...
208
    {
209
        $countryCode = '';
210
        $countryObject = CountryPrice_EcommerceCountry::get_real_country();
211
        if ($countryObject) {
212
            $countryCode = $countryObject->Code;
213
        }
214
        if ($countryCode == '' || $countryCode == EcommerceConfig::get('EcommerceCountry', 'default_country_code')) {
215
            if ($this->debug) {
216
                debug::log('No country code or default country code: '.$countryCode);
217
            }
218
219
            return null;
220
        }
221
        $key = $this->owner->ClassName."___".$this->owner->ID.'____'.$countryCode;
222
        if (! isset(self::$_buyable_price[$key])) {
223
            //basics
224
            $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...
225
            $currencyCode = null;
226
227
            if ($countryCode) {
228
                $order = ShoppingCart::current_order();
229
                CountryPrice_OrderDOD::localise_order();
230
                $currency = $order->CurrencyUsed();
231
                if ($currency) {
232
                    $currencyCode = strtoupper($currency->Code);
233
                    //1. exact price for country
234
                    if ($currencyCode) {
235
                        $prices = $this->owner->CountryPricesForCountryAndCurrency(
236
                            $countryCode,
237
                            $currencyCode
238
                        );
239 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...
240
                            self::$_buyable_price[$key] = $prices->First()->Price;
241
                            return self::$_buyable_price[$key];
242
                        } elseif ($prices) {
243
                            if ($this->debug) {
244
                                debug::log('MAIN COUNTRY: There is an error number of prices: '.$prices->count());
245
                            }
246
                        } else {
247
                            if ($this->debug) {
248
                                debug::log('MAIN COUNTRY: There is no country price: ');
249
                            }
250
                        }
251
                    } else {
252
                        if ($this->debug) {
253
                            debug::log('MAIN COUNTRY: There is no currency code '.$currencyCode.'');
254
                        }
255
                    }
256
                } else {
257
                    if ($this->debug) {
258
                        debug::log('MAIN COUNTRY: there is no currency');
259
                    }
260
                }
261
                if (Config::inst()->get('CountryPrice_BuyableExtension', 'allow_usage_of_distributor_backup_country_pricing')) {
262
                    //there is a specific country price ...
263
                    //check for distributor primary country price
264
                    // if it is the same currency, then use that one ...
265
                    $distributorCountry = CountryPrice_EcommerceCountry::get_distributor_primary_country($countryCode);
266
                    if ($distributorCurrency = $distributorCountry->EcommerceCurrency()) {
267
                        if ($distributorCurrency->ID == $currency->ID) {
268
                            $distributorCurrencyCode = strtoupper($distributorCurrency->Code);
269
                            $distributorCountryCode = $distributorCountry->Code;
270
                            if ($distributorCurrencyCode && $distributorCountryCode) {
271
                                $prices = $this->owner->CountryPricesForCountryAndCurrency(
272
                                    $distributorCountryCode,
273
                                    $distributorCurrencyCode
274
                                );
275 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...
276
                                    self::$_buyable_price[$key] = $prices->First()->Price;
277
278
                                    return self::$_buyable_price[$key];
279
                                } elseif ($prices) {
280
                                    if ($this->debug) {
281
                                        debug::log('BACKUP COUNTRY: There is an error number of prices: '.$prices->count());
282
                                    }
283
                                } else {
284
                                    if ($this->debug) {
285
                                        debug::log('BACKUP COUNTRY: There is no country price: ');
286
                                    }
287
                                }
288
                            } else {
289
                                if ($this->debug) {
290
                                    debug::log('BACKUP COUNTRY: We are missing the distributor currency code ('.$distributorCurrencyCode.') or the distributor country code ('.$distributorCountryCode.')');
291
                                }
292
                            }
293
                        } else {
294
                            if ($this->debug) {
295
                                debug::log('BACKUP COUNTRY: The distributor currency ID ('.$distributorCurrency->ID.') is not the same as the order currency ID ('.$currency->ID.').');
296
                            }
297
                        }
298
                    }
299
                } else {
300
                    if ($this->debug) {
301
                        debug::log('We do not allow backup country pricing');
302
                    }
303
                }
304
            } else {
305
                if ($this->debug) {
306
                    debug::log('There is not Country Code ');
307
                }
308
            }
309
            //order must have a country and a currency
310
            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...
311
                if ($this->debug) {
312
                    debug::log('No currency ('.$currencyCode.') or no country code ('.$countryCode.') for order: ');
313
                }
314
                self::$_buyable_price[$key] = 0;
315
                return self::$_buyable_price[$key];
316
            }
317
            //catch error 2: no country price BUT currency is not default currency ...
318
            if (EcommercePayment::site_currency() != $currencyCode) {
319
                if ($this->debug) {
320
                    debug::log('site currency  ('.EcommercePayment::site_currency().') is not the same order currency ('.$currencyCode.')');
321
                }
322
                self::$_buyable_price[$key] = 0;
323
                return self::$_buyable_price[$key];
324
            }
325
            if ($this->debug) {
326
                debug::log('SETTING '.$key.' to ZERO - NOT FOR SALE');
327
            }
328
            self::$_buyable_price[$key] = 0;
329
330
            return self::$_buyable_price[$key];
331
        }
332
333
        return self::$_buyable_price[$key];
334
    }
335
336
    /**
337
     * delete the related prices
338
     */
339
    public function onBeforeDelete()
340
    {
341
        $prices = $this->AllCountryPricesForBuyable();
342
        if ($prices && $prices->count()) {
343
            foreach ($prices as $price) {
344
                $price->delete();
345
            }
346
        }
347
    }
348
349
    // VARIATION CODE ONLY
350
351
    // 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
352
    protected $isNew = false;
353
354
    public function onBeforeWrite()
355
    {
356
        $this->isNew = $this->owner->ID == 0;
357
    }
358
359
360
    public function onAfterWrite()
361
    {
362
        //only run if these are variations
363
        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...
364
            $product = $this->owner->Product();
365
            if ($product) {
366
                $productPrices = $product->AllCountryPricesForBuyable();
367
                foreach ($productPrices as $productPrice) {
368
                    if ($productPrice->Country) {
369
                        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...
370
                            $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...
371
                            ->filter(
372
                                array(
373
                                    "Country" => $productPrice->Country,
374
                                    "ObjectClass" => $this->owner->ClassName,
375
                                    "ObjectID" => $this->owner->ID
376
                                )
377
                            )
378
                            ->First()
379
                        ) {
380
                            //do nothing
381
                        } else {
382
                            $countryVariationPrice = new CountryPrice(
383
                                array(
384
                                    'Price' => $productPrice->Price,
385
                                    'Country' => $productPrice->Country,
386
                                    'Currency' => $productPrice->Currency,
387
                                    'ObjectClass' => $this->owner->ClassName,
388
                                    'ObjectID' => $this->owner->ID
389
                                )
390
                            );
391
                            $countryVariationPrice->write();
392
                        }
393
                    }
394
                }
395
            }
396
        }
397
    }
398
399
    /**
400
     * as long as we do not give distributors access to the Products
401
     * this is fairly safe.
402
     * @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...
403
     * @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...
404
     */
405
    public function canEdit($member = null)
406
    {
407
        if (! $member) {
408
            $member = Member::currentUser();
409
        }
410
        if ($member) {
411
            $distributor = $member->Distributor();
412
            if ($distributor->exists()) {
413
                return true;
414
            }
415
        }
416
        return false;
417
    }
418
}
419