EcommerceCountry   F
last analyzed

Complexity

Total Complexity 112

Size/Duplication

Total Lines 786
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 16

Importance

Changes 0
Metric Value
dl 0
loc 786
rs 1.814
c 0
b 0
f 0
wmc 112
lcom 2
cbo 16

37 Methods

Rating   Name   Duplication   Size   Complexity  
A i18n_singular_name() 0 4 1
A i18n_plural_name() 0 4 1
A canCreate() 0 16 4
A canView() 0 15 4
A canEdit() 0 15 4
B canDelete() 0 22 6
A get_country_from_ip() 0 10 2
A get_ip() 0 10 2
C get_country_dropdown() 0 32 12
A get_fixed_country_code() 0 9 3
A countryCode2name() 0 4 1
A find_title() 0 20 4
A reset_get_country_cache() 0 5 1
A get_country_cache() 0 6 2
A set_country_cache() 0 5 1
B get_country() 0 31 9
A get_country_default() 0 16 5
A get_country_from_mixed_var() 0 18 6
A get_country_object() 0 11 2
A get_country_id() 0 20 4
A reset_allow_sales_cache() 0 4 1
A allow_sales() 0 20 4
A get_default_array() 0 18 5
A getCMSFields() 0 19 1
A CMSEditLink() 0 4 1
A requireDefaultRecords() 0 8 3
A onBeforeWrite() 0 6 1
A get_for_current_order_only_show_countries() 0 4 1
A set_for_current_order_only_show_countries() 0 9 2
A get_for_current_order_do_not_show_countries() 0 4 1
A set_for_current_order_do_not_show_countries() 0 5 1
B list_of_allowed_entries_for_dropdown() 0 25 10
A code_allowed() 0 4 1
A AllowSales() 0 4 1
A getAllowSales() 0 8 2
A AllowSalesNice() 0 4 1
A getAllowSalesNice() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like EcommerceCountry often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EcommerceCountry, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @description: This class helps you to manage countries within the context of e-commerce.
5
 * For example: To what countries can be sold.
6
 * /dev/build/?resetecommercecountries=1 will reset the list of countries...
7
 *
8
 *
9
 * @authors: Nicolaas [at] Sunny Side Up .co.nz
10
 * @package: ecommerce
11
 * @sub-package: address
12
 * @inspiration: Silverstripe Ltd, Jeremy
13
 **/
14
class EcommerceCountry extends DataObject implements EditableEcommerceObject
15
{
16
    /**
17
     * what variables are accessible through  http://mysite.com/api/ecommerce/v1/EcommerceCountry/.
18
     *
19
     * @var array
20
     */
21
    private static $api_access = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
22
        'view' => array(
23
            'Code',
24
            'Name',
25
        )
26
     );
27
28
    /**
29
     * Standard SS Variable.
30
     *
31
     * @var array
32
     **/
33
    private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
34
        'Code' => 'Varchar(20)',
35
        'Name' => 'Varchar(200)',
36
        'DoNotAllowSales' => 'Boolean',
37
    );
38
39
    /**
40
     * Standard SS Variable.
41
     *
42
     * @var array
43
     **/
44
    private static $has_many = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
45
        'Regions' => 'EcommerceRegion',
46
    );
47
48
    /**
49
     * standard SS variable.
50
     *
51
     * @var array
52
     */
53
    private static $summary_fields = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
54
        'Code' => 'Code',
55
        'Name' => 'Name',
56
        'AllowSalesNice' => 'Allow Sales',
57
    );
58
59
    /**
60
     * standard SS variable.
61
     *
62
     * @var array
63
     */
64
    private static $casting = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
65
        'AllowSales' => 'Boolean',
66
        'AllowSalesNice' => 'Varchar',
67
    );
68
69
    /**
70
     * STANDARD SILVERSTRIPE STUFF.
71
     *
72
     * @todo: how to translate this?
73
     **/
74
    private static $searchable_fields = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
75
        'Code' => 'PartialMatchFilter',
76
        'Name' => 'PartialMatchFilter',
77
        'DoNotAllowSales' => array(
78
            'title' => 'Sales are prohibited',
79
        ),
80
    );
81
82
    /**
83
     * Standard SS Variable.
84
     *
85
     * @var array
86
     **/
87
    private static $indexes = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
88
        'DoNotAllowSales' => true,
89
        'Name' => true,
90
        'Code' => true
91
    );
92
93
    /**
94
     * Standard SS Variable.
95
     *
96
     * @var string
97
     **/
98
    private static $default_sort = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
99
        'DoNotAllowSales' => 'ASC',
100
        'Name' => 'ASC',
101
        'ID' => 'ASC'
102
    ];
103
104
    /**
105
     * Standard SS Variable.
106
     *
107
     * @var string
108
     **/
109
    private static $singular_name = 'Country';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
110
    public function i18n_singular_name()
111
    {
112
        return _t('EcommerceCountry.COUNTRY', 'Country');
113
    }
114
115
    /**
116
     * Standard SS Variable.
117
     *
118
     * @var string
119
     **/
120
    private static $plural_name = 'Countries';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
121
    public function i18n_plural_name()
122
    {
123
        return _t('EcommerceCountry.COUNTRIES', 'Countries');
124
    }
125
126
    /**
127
     * Standard SS variable.
128
     *
129
     * @var string
130
     */
131
    private static $description = 'A country.';
132
133
    /**
134
     * Standard SS Method.
135
     *
136
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be Member|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...
137
     *
138
     * @var bool
139
     */
140
    public function canCreate($member = null)
141
    {
142
        $can = false;
143
        if (! $member) {
144
            $member = Member::currentUser();
145
        }
146
        if (EcommerceCountry::get()->count() < 220) {
147
            $can = parent::canCreate($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by \Member::currentUser() on line 144 can also be of type object<DataObject>; however, DataObject::canCreate() does only seem to accept object<Member>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
148
        }
149
        $extended = $this->extendedCan(__FUNCTION__, $member);
0 ignored issues
show
Documentation introduced by
$member is of type object<DataObject>|null, but the function expects a object<Member>|integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
150
        if ($extended !== null) {
151
            return $extended;
152
        }
153
154
        return $can;
155
    }
156
157
    /**
158
     * Standard SS Method.
159
     *
160
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be Member|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...
161
     *
162
     * @var bool
163
     */
164
    public function canView($member = null)
165
    {
166
        if (! $member) {
167
            $member = Member::currentUser();
168
        }
169
        $extended = $this->extendedCan(__FUNCTION__, $member);
0 ignored issues
show
Documentation introduced by
$member is of type object<DataObject>|null, but the function expects a object<Member>|integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
170
        if ($extended !== null) {
171
            return $extended;
172
        }
173
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
174
            return true;
175
        }
176
177
        return parent::canEdit($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by \Member::currentUser() on line 167 can also be of type object<DataObject>; however, DataObject::canEdit() does only seem to accept object<Member>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Comprehensibility Bug introduced by
It seems like you call parent on a different method (canEdit() instead of canView()). Are you sure this is correct? If so, you might want to change this to $this->canEdit().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
178
    }
179
180
    /**
181
     * Standard SS Method.
182
     *
183
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be Member|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...
184
     *
185
     * @var bool
186
     */
187
    public function canEdit($member = null)
188
    {
189
        if (! $member) {
190
            $member = Member::currentUser();
191
        }
192
        $extended = $this->extendedCan(__FUNCTION__, $member);
0 ignored issues
show
Documentation introduced by
$member is of type object<DataObject>|null, but the function expects a object<Member>|integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
193
        if ($extended !== null) {
194
            return $extended;
195
        }
196
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
197
            return true;
198
        }
199
200
        return parent::canEdit($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by \Member::currentUser() on line 190 can also be of type object<DataObject>; however, DataObject::canEdit() does only seem to accept object<Member>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
201
    }
202
203
    /**
204
     * Standard SS method.
205
     *
206
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be Member|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...
207
     *
208
     * @return bool
209
     */
210
    public function canDelete($member = null)
211
    {
212
        if (! $member) {
213
            $member = Member::currentUser();
214
        }
215
        $extended = $this->extendedCan(__FUNCTION__, $member);
0 ignored issues
show
Documentation introduced by
$member is of type object<DataObject>|null, but the function expects a object<Member>|integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
216
        if ($extended !== null) {
217
            return $extended;
218
        }
219
        return false;
220
        if (ShippingAddress::get()->filter(array('ShippingCountry' => $this->Code))->count()) {
0 ignored issues
show
Unused Code introduced by
if (\ShippingAddress::ge...) { return false; } does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
Documentation introduced by
The property Code does not exist on object<EcommerceCountry>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
221
            return false;
222
        }
223
        if (BillingAddress::get()->filter(array('Country' => $this->Code))->count()) {
0 ignored issues
show
Documentation introduced by
The property Code does not exist on object<EcommerceCountry>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
224
            return false;
225
        }
226
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
227
            return true;
228
        }
229
230
        return parent::canEdit($member);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (canEdit() instead of canDelete()). Are you sure this is correct? If so, you might want to change this to $this->canEdit().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
231
    }
232
233
    /**
234
     * returns the country based on the Visitor Country Provider.
235
     * this is some sort of IP recogniser system (e.g. Geoip Class).
236
     *
237
     * @return string (country code)
238
     **/
239
    public static function get_country_from_ip()
240
    {
241
        $visitorCountryProviderClassName = EcommerceConfig::get('EcommerceCountry', 'visitor_country_provider');
242
        if (!$visitorCountryProviderClassName) {
243
            $visitorCountryProviderClassName = 'EcommerceCountry_VisitorCountryProvider';
244
        }
245
        $visitorCountryProvider = new $visitorCountryProviderClassName();
246
247
        return $visitorCountryProvider->getCountry();
248
    }
249
250
    /**
251
     * returns the country based on the Visitor Country Provider.
252
     * this is some sort of IP recogniser system (e.g. Geoip Class).
253
     *
254
     * @return string (country code)
255
     **/
256
    public static function get_ip()
257
    {
258
        $visitorCountryProviderClassName = EcommerceConfig::get('EcommerceCountry', 'visitor_country_provider');
259
        if (!$visitorCountryProviderClassName) {
260
            $visitorCountryProviderClassName = 'EcommerceCountry_VisitorCountryProvider';
261
        }
262
        $visitorCountryProvider = new $visitorCountryProviderClassName();
263
264
        return $visitorCountryProvider->getIP();
265
    }
266
267
    private static $_countries_from_db_cache = array();
268
269
    /**
270
     *               e.g.
271
     *               "NZ" => "New Zealand"
272
     * @return array
273
     */
274
    public static function get_country_dropdown($showAllCountries = true, $addEmptyString = false, $useIDNotCode = false)
275
    {
276
        $key = ($showAllCountries ? "all" : "notall");
277
        if (isset(self::$_countries_from_db_cache[$key])) {
278
            $array = self::$_countries_from_db_cache[$key];
279
        } else {
280
            $array = array();
281
            $objects = null;
282
            if (class_exists('Geoip') && $showAllCountries && ! $useIDNotCode) {
283
                $array = Geoip::getCountryDropDown();
284
            } elseif ($showAllCountries) {
285
                $objects = EcommerceCountry::get();
286
            } else {
287
                $objects = EcommerceCountry::get()->filter(array('DoNotAllowSales' => 0));
288
            }
289
            if ($objects && $objects->count()) {
290
                if ($useIDNotCode) {
291
                    $idField = 'ID';
292
                } else {
293
                    $idField = 'Code';
294
                }
295
                $array = $objects->map($idField, 'Name')->toArray();
296
            }
297
            self::$_countries_from_db_cache[$key] = $array;
298
        }
299
        if (count($array)) {
300
            if ($addEmptyString) {
301
                $array = array("", " -- please select -- ") + $array;
302
            }
303
        }
304
        return $array;
305
    }
306
307
    /**
308
     * This function exists as a shortcut.
309
     * If there is only ONE allowed country code
310
     * then a lot of checking of countries can be avoided.
311
     *
312
     * @return string | null - countrycode
313
     **/
314
    public static function get_fixed_country_code()
315
    {
316
        $a = EcommerceConfig::get('EcommerceCountry', 'allowed_country_codes');
317
        if (is_array($a) && count($a) == 1) {
318
            return array_shift($a);
319
        }
320
321
        return null;
322
    }
323
324
    /**
325
     * @alias for EcommerceCountry::find_title
326
     *
327
     * @param string $code
328
     *                     We have this as this is the same as Geoip
329
     *
330
     * @return string
331
     */
332
    public static function countryCode2name($code)
333
    {
334
        return self::find_title($code);
335
    }
336
337
    /**
338
     * returns the country name from a code.
339
     *
340
     * @return string
341
     **/
342
    public static function find_title($code)
343
    {
344
        $code = strtoupper($code);
345
        $options = self::get_country_dropdown($showAllCountries = true);
346
        // check if code was provided, and is found in the country array
347
        if (isset($options[$code])) {
348
            return $options[$code];
349
        } elseif ($code) {
350
            $obj = DataObject::get_one(
351
                'EcommerceCountry',
352
                array('Code' => $code)
353
            );
354
            if ($obj) {
355
                return $obj->Name;
356
            }
357
            return $code;
358
        } else {
359
            return _t('Ecommerce.COUNTRY_NOT_FOUND', '[COUNTRY NOT FOUND]');
360
        }
361
    }
362
363
    /**
364
     * Memory for the order's country.
365
     *
366
     * @var array
367
     */
368
    private static $_country_cache = array();
369
370
    /**
371
     * @param int (optional) $orderID
372
     */
373
    public static function reset_get_country_cache($orderID = 0)
374
    {
375
        $orderID = ShoppingCart::current_order_id($orderID);
376
        unset(self::$_country_cache[$orderID]);
377
    }
378
379
    /**
380
     * @param int (optional) $orderID
381
     */
382
    public static function get_country_cache($orderID = 0)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
383
    {
384
        $orderID = ShoppingCart::current_order_id($orderID);
385
386
        return isset(self::$_country_cache[$orderID]) ? self::$_country_cache[$orderID] : null;
387
    }
388
389
    /**
390
     * @param string         $countryCode
391
     * @param int (optional) $orderID
392
     */
393
    public static function set_country_cache($countryCode, $orderID = 0)
394
    {
395
        $orderID = ShoppingCart::current_order_id($orderID);
396
        self::$_country_cache[$orderID] = $countryCode;
397
    }
398
399
    /**
400
     * This function works out the most likely country for the current order.
401
     *
402
     * @param bool (optional) $recalculate
403
     * @param int (optional)  $orderID
404
     *
405
     * @return string - Country Code - e.g. NZ
406
     **/
407
    public static function get_country($recalculate = false, $orderID = 0)
408
    {
409
        //get order ID
410
        $orderID = ShoppingCart::current_order_id($orderID);
411
        $countryCode = self::get_country_cache($orderID);
412
        if ($countryCode === null || $recalculate) {
413
            $countryCode = '';
0 ignored issues
show
Unused Code introduced by
$countryCode 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...
414
            //1. fixed country is first
415
            $countryCode = self::get_fixed_country_code();
416
            if (! $countryCode) {
417
                //2. check order / shipping address / ip address
418
                //include $countryCode = self::get_country_from_ip();
419
                $o = ShoppingCart::current_order();
420
                if ($orderID && $orderID !== $o->ID) {
421
                    $o = DataObject::get_one('Order', ['ID' => $orderID]);
422
                }
423
                if ($o && $o->exists()) {
424
                    $countryCode = $o->getCountry();
425
                //3 ... if there is no shopping cart, then we still want it from IP
426
                } else {
427
                    $countryCode = self::get_country_from_ip();
428
                }
429
                //4 check default country set in GEO IP....
430
                if (! $countryCode) {
431
                    $countryCode = self::get_country_default();
432
                }
433
            }
434
            self::set_country_cache($countryCode, $orderID);
435
        }
436
        return $countryCode;
437
    }
438
439
    /**
440
     * A bling guess at the best country!
441
     *
442
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|string|array|double|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...
443
     */
444
    public static function get_country_default()
445
    {
446
        $countryCode = EcommerceConfig::get('EcommerceCountry', 'default_country_code');
447
        //5. take the FIRST country from the get_allowed_country_codes
448
        if (!$countryCode) {
449
            $countryArray = self::list_of_allowed_entries_for_dropdown();
450
            if (is_array($countryArray) && count($countryArray)) {
451
                foreach ($countryArray as $countryCode => $countryName) {
452
                    //we stop at the first one... as we have no idea which one is the best.
453
                    break;
454
                }
455
            }
456
        }
457
458
        return $countryCode;
459
    }
460
461
    /**
462
     * @param  int|string|EcommerceCountry
463
     *
464
     * @return EcommerceCountry|string|null
465
     */
466
    public static function get_country_from_mixed_var($var, $asCode = false)
467
    {
468
        if (is_string($var)) {
469
            $var = strtoupper($var);
470
            $var = DataObject::get_one('EcommerceCountry', ['Code' => $var]);
471
        } elseif (is_numeric($var) && is_int($var)) {
472
            $var = EcommerceCountry::get()->byID($var);
473
        }
474
        if ($var instanceof EcommerceCountry) {
475
            if ($asCode) {
476
                return $var->Code;
0 ignored issues
show
Documentation introduced by
The property Code does not exist on object<EcommerceCountry>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
477
            } else {
478
                return $var;
479
            }
480
        }
481
482
        return null;
483
    }
484
485
    /**
486
     * This function works out the most likely country for the current order
487
     * and returns the Country Object, if any.
488
     *
489
     * @param bool    (optional)   $recalculate
490
     * @param string  (optional)   $countryCode
491
     *
492
     * @return EcommerceCountry | Null
493
     **/
494
    public static function get_country_object($recalculate = false, $countryCode = null)
495
    {
496
        if (! $countryCode) {
497
            $countryCode = self::get_country($recalculate);
498
        }
499
500
        return DataObject::get_one(
501
            'EcommerceCountry',
502
            array('Code' => $countryCode)
503
        );
504
    }
505
506
    /**
507
     * maps like this
508
     *     NZ => 1
509
     *     AU => 2
510
     *     etc...
511
     * @var array
512
     */
513
    private static $_code_to_id_map = array();
514
    /**
515
     * returns the ID of the country or 0.
516
     *
517
     * @param string (optional)   $countryCode
518
     * @param bool   (optional)   $recalculate
519
     *
520
     * @return int
521
     **/
522
    public static function get_country_id($countryCode = null, $recalculate = false)
523
    {
524
        if (!$countryCode) {
525
            $countryCode = self::get_country($recalculate);
526
        }
527
        if (isset(self::$_code_to_id_map[$countryCode])) {
528
            return self::$_code_to_id_map[$countryCode];
529
        }
530
        self::$_code_to_id_map[$countryCode] = 0;
531
        $country = DataObject::get_one(
532
            'EcommerceCountry',
533
            array('Code' => $countryCode)
534
        );
535
        if ($country) {
536
            self::$_code_to_id_map[$countryCode] = $country->ID;
537
            return $country->ID;
538
        }
539
540
        return 0;
541
    }
542
543
    /**
544
     * Memory for allow country to check.
545
     *
546
     * @var null | Boolean
547
     */
548
    private static $_allow_sales_cache = array();
549
550
    public static function reset_allow_sales_cache()
551
    {
552
        self::$_allow_sales_cache = null;
553
    }
554
555
    /**
556
     * Checks if we are allowed to sell to the current country.
557
     *
558
     * @param int (optional) $orderID
559
     *
560
     * @return bool
561
     */
562
    public static function allow_sales($orderID = 0)
563
    {
564
        $orderID = ShoppingCart::current_order_id($orderID);
565
        if (!isset(self::$_allow_sales_cache[$orderID])) {
566
            self::$_allow_sales_cache[$orderID] = true;
567
            $countryCode = self::get_country(false, $orderID);
568
            if ($countryCode) {
569
                $countries = EcommerceCountry::get()
570
                    ->filter(array(
571
                        'DoNotAllowSales' => 1,
572
                        'Code' => $countryCode,
573
                    ));
574
                if ($countries->count()) {
575
                    self::$_allow_sales_cache[$orderID] = false;
576
                }
577
            }
578
        }
579
580
        return self::$_allow_sales_cache[$orderID];
581
    }
582
583
    /**
584
     * returns an array of Codes => Names of all countries that can be used.
585
     * Use "list_of_allowed_entries_for_dropdown" to get the list.
586
     *
587
     * @todo add caching
588
     *
589
     * @return array
590
     **/
591
    protected static function get_default_array()
592
    {
593
        $defaultArray = array();
594
        $countries = null;
0 ignored issues
show
Unused Code introduced by
$countries 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...
595
        if ($code = self::get_fixed_country_code()) {
596
            $defaultArray[$code] = self::find_title($code);
597
598
            return $defaultArray;
599
        }
600
        $countries = EcommerceCountry::get()->exclude(array('DoNotAllowSales' => 1));
601
        if ($countries && $countries->count()) {
602
            foreach ($countries as $country) {
603
                $defaultArray[$country->Code] = $country->Name;
604
            }
605
        }
606
607
        return $defaultArray;
608
    }
609
610
    public function getCMSFields()
611
    {
612
        $fields = parent::getCMSFields();
613
        $fields->addFieldToTab(
614
            'Root.Main',
615
            new LiteralField(
616
            'Add Add Countries',
617
            '
618
                <h3>Short-Cuts</h3>
619
                <h6>
620
                    <a href="/dev/tasks/EcommerceTaskCountryAndRegion_DisallowAllCountries" target="_blank">'._t('EcommerceCountry.DISALLOW_ALL', 'disallow sales to all countries').'</a> |||
621
                    <a href="/dev/tasks/EcommerceTaskCountryAndRegion_AllowAllCountries" target="_blank">'._t('EcommerceCountry.ALLOW_ALL', 'allow sales to all countries').'</a>
622
                </h6>
623
            '
624
        )
625
        );
626
627
        return $fields;
628
    }
629
630
631
632
    /**
633
     * link to edit the record.
634
     *
635
     * @param string|null $action - e.g. edit
636
     *
637
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string?

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...
638
     */
639
    public function CMSEditLink($action = null)
640
    {
641
        return CMSEditLinkAPI::find_edit_link_for_object($this, $action);
642
    }
643
644
    /**
645
     * standard SS method.
646
     */
647
    public function requireDefaultRecords()
648
    {
649
        parent::requireDefaultRecords();
650
        if ((! EcommerceCountry::get()->count()) || isset($_REQUEST['resetecommercecountries'])) {
651
            $task = new EcommerceTaskCountryAndRegion();
652
            $task->run(null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<SS_HTTPRequest>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
653
        }
654
    }
655
656
    /**
657
     * standard SS method
658
     * cleans up codes.
659
     */
660
    public function onBeforeWrite()
661
    {
662
        parent::onBeforeWrite();
663
        $filter = EcommerceCodeFilter::create();
664
        $filter->checkCode($this);
665
    }
666
667
    //DYNAMIC LIMITATIONS
668
669
    /**
670
     * these variables and methods allow to "dynamically limit the countries available,
671
     * based on, for example: ordermodifiers, item selection, etc....
672
     * for example, if a person chooses delivery within Australasia (with modifier) -
673
     * then you can limit the countries available to "Australasian" countries.
674
     */
675
676
    /**
677
     * List of countries that should be shown.
678
     *
679
     * @param array $a: should be country codes e.g. array("NZ", "NP", "AU");
680
     *
681
     * @var array
682
     */
683
    private static $for_current_order_only_show_countries = array();
684
    public static function get_for_current_order_only_show_countries()
685
    {
686
        return self::$for_current_order_only_show_countries;
687
    }
688
    public static function set_for_current_order_only_show_countries(array $a)
689
    {
690
        if (count(self::$for_current_order_only_show_countries)) {
691
            //we INTERSECT here so that only countries allowed by all forces (modifiers) are added.
692
            self::$for_current_order_only_show_countries = array_intersect($a, self::$for_current_order_only_show_countries);
693
        } else {
694
            self::$for_current_order_only_show_countries = $a;
695
        }
696
    }
697
698
    /**
699
     * List of countries that should NOT be shown.
700
     *
701
     * @param array $a: should be country codes e.g. array("NZ", "NP", "AU");
702
     *
703
     * @var array
704
     */
705
    private static $for_current_order_do_not_show_countries = array();
706
    public static function get_for_current_order_do_not_show_countries()
707
    {
708
        return self::$for_current_order_do_not_show_countries;
709
    }
710
    public static function set_for_current_order_do_not_show_countries(array $a)
711
    {
712
        //We MERGE here because several modifiers may limit the countries
713
        self::$for_current_order_do_not_show_countries = array_merge($a, self::$for_current_order_do_not_show_countries);
714
    }
715
716
    /**
717
     * @var array
718
     */
719
    private static $list_of_allowed_entries_for_dropdown_array = array();
720
721
    /**
722
     * takes the defaultArray and limits it with "only show" and "do not show" value, relevant for the current order.
723
     *
724
     * @return array (Code, Title)
725
     **/
726
    public static function list_of_allowed_entries_for_dropdown()
727
    {
728
        if (!self::$list_of_allowed_entries_for_dropdown_array) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::$list_of_allowed_entries_for_dropdown_array of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
729
            $defaultArray = self::get_default_array();
730
            $onlyShow = self::$for_current_order_only_show_countries;
731
            $doNotShow = self::$for_current_order_do_not_show_countries;
732
            if (is_array($onlyShow) && count($onlyShow)) {
733
                foreach ($defaultArray as $key => $value) {
734
                    if (!in_array($key, $onlyShow)) {
735
                        unset($defaultArray[$key]);
736
                    }
737
                }
738
            }
739
            if (is_array($doNotShow) && count($doNotShow)) {
740
                foreach ($doNotShow as $code) {
741
                    if (isset($defaultArray[$code])) {
742
                        unset($defaultArray[$code]);
743
                    }
744
                }
745
            }
746
            self::$list_of_allowed_entries_for_dropdown_array = $defaultArray;
747
        }
748
749
        return self::$list_of_allowed_entries_for_dropdown_array;
750
    }
751
752
    /**
753
     * checks if a code is allowed.
754
     *
755
     * @param string $code - e.g. NZ, NSW, or CO
756
     *
757
     * @return bool
758
     **/
759
    public static function code_allowed($code)
760
    {
761
        return array_key_exists($code, self::list_of_allowed_entries_for_dropdown());
762
    }
763
764
    /**
765
     * Casted variable to show if sales are allowed to this country.
766
     *
767
     * @return bool
768
     */
769
    public function AllowSales()
770
    {
771
        return $this->getAllowSales();
772
    }
773
    public function getAllowSales()
774
    {
775
        if ($this->DoNotAllowSales) {
0 ignored issues
show
Documentation introduced by
The property DoNotAllowSales does not exist on object<EcommerceCountry>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Coding Style introduced by
The if-else statement can be simplified to return !$this->DoNotAllowSales;.
Loading history...
776
            return false;
777
        } else {
778
            return true;
779
        }
780
    }
781
782
    /**
783
     * Casted variable to show if sales are allowed to this country.
784
     *
785
     * @return string
786
     */
787
    public function AllowSalesNice()
788
    {
789
        return $this->getAllowSalesNice();
790
    }
791
    public function getAllowSalesNice()
792
    {
793
        if ($this->AllowSales()) {
794
            return _t('EcommerceCountry.YES', 'Yes');
795
        } else {
796
            return _t('EcommerceCountry.NO', 'No');
797
        }
798
    }
799
}
800