Completed
Push — master ( 613b2e...a2195f )
by Nicolaas
03:28
created

EcommerceCountry::i18n_singular_name()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
eloc 2
nc 1
nop 0
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
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...
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
Unused Code introduced by
The property $api_access 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...
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
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...
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
Unused Code introduced by
The property $has_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...
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 $indexes = array(
0 ignored issues
show
Unused Code introduced by
The property $indexes 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...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
54
        'Code' => true,
55
        'DoNotAllowSales' => true,
56
    );
57
58
    /**
59
     * standard SS variable.
60
     *
61
     * @var array
62
     */
63
    private static $summary_fields = array(
0 ignored issues
show
Unused Code introduced by
The property $summary_fields 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...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
64
        'Code' => 'Code',
65
        'Name' => 'Name',
66
        'AllowSalesNice' => 'Allow Sales',
67
    );
68
69
    /**
70
     * standard SS variable.
71
     *
72
     * @var array
73
     */
74
    private static $casting = array(
0 ignored issues
show
Unused Code introduced by
The property $casting 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...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
75
        'AllowSales' => 'Boolean',
76
        'AllowSalesNice' => 'Varchar',
77
    );
78
79
    /**
80
     * STANDARD SILVERSTRIPE STUFF.
81
     *
82
     * @todo: how to translate this?
83
     **/
84
    private static $searchable_fields = array(
0 ignored issues
show
Unused Code introduced by
The property $searchable_fields 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...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
85
        'Code' => 'PartialMatchFilter',
86
        'Name' => 'PartialMatchFilter',
87
        'DoNotAllowSales' => array(
88
            'title' => 'Sales are prohibited',
89
        ),
90
    );
91
92
    /**
93
     * Standard SS Variable.
94
     *
95
     * @var string
96
     **/
97
    private static $default_sort = '"DoNotAllowSales" ASC, "Name" ASC';
0 ignored issues
show
Unused Code introduced by
The property $default_sort 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...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
98
99
    /**
100
     * Standard SS Variable.
101
     *
102
     * @var string
103
     **/
104
    private static $singular_name = 'Country';
0 ignored issues
show
Unused Code introduced by
The property $singular_name 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...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
105
    public function i18n_singular_name()
106
    {
107
        return _t('EcommerceCountry.COUNTRY', 'Country');
108
    }
109
110
    /**
111
     * Standard SS Variable.
112
     *
113
     * @var string
114
     **/
115
    private static $plural_name = 'Countries';
0 ignored issues
show
Unused Code introduced by
The property $plural_name 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...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
116
    public function i18n_plural_name()
117
    {
118
        return _t('EcommerceCountry.COUNTRIES', 'Countries');
119
    }
120
121
    /**
122
     * Standard SS variable.
123
     *
124
     * @var string
125
     */
126
    private static $description = 'A country.';
0 ignored issues
show
Unused Code introduced by
The property $description 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...
127
128
    /**
129
     * Standard SS Method.
130
     *
131
     * @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...
132
     *
133
     * @var bool
134
     */
135
    public function canCreate($member = null)
136
    {
137
        if (! $member) {
138
            $member = Member::currentUser();
139
        }
140
        $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...
141
        if ($extended !== null) {
142
            return $extended;
143
        }
144
145
        return false;
146
    }
147
148
    /**
149
     * Standard SS Method.
150
     *
151
     * @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...
152
     *
153
     * @var bool
154
     */
155
    public function canView($member = null)
156
    {
157
        if (! $member) {
158
            $member = Member::currentUser();
159
        }
160
        $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...
161
        if ($extended !== null) {
162
            return $extended;
163
        }
164
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
165
            return true;
166
        }
167
168
        return parent::canEdit($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by \Member::currentUser() on line 158 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...
169
    }
170
171
    /**
172
     * Standard SS Method.
173
     *
174
     * @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...
175
     *
176
     * @var bool
177
     */
178
    public function canEdit($member = null)
179
    {
180
        if (! $member) {
181
            $member = Member::currentUser();
182
        }
183
        $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...
184
        if ($extended !== null) {
185
            return $extended;
186
        }
187
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
188
            return true;
189
        }
190
191
        return parent::canEdit($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by \Member::currentUser() on line 181 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...
192
    }
193
194
    /**
195
     * Standard SS method.
196
     *
197
     * @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...
198
     *
199
     * @return bool
200
     */
201
    public function canDelete($member = null)
202
    {
203
        if (! $member) {
204
            $member = Member::currentUser();
205
        }
206
        $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...
207
        if ($extended !== null) {
208
            return $extended;
209
        }
210
        return false;
211
        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...
212
            return false;
213
        }
214
        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...
215
            return false;
216
        }
217
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
218
            return true;
219
        }
220
221
        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...
222
    }
223
224
    /**
225
     * returns the country based on the Visitor Country Provider.
226
     * this is some sort of IP recogniser system (e.g. Geoip Class).
227
     *
228
     * @return string (country code)
229
     **/
230
    public static function get_country_from_ip()
231
    {
232
        $visitorCountryProviderClassName = EcommerceConfig::get('EcommerceCountry', 'visitor_country_provider');
233
        if (!$visitorCountryProviderClassName) {
234
            $visitorCountryProviderClassName = 'EcommerceCountry_VisitorCountryProvider';
235
        }
236
        $visitorCountryProvider = new $visitorCountryProviderClassName();
237
238
        return $visitorCountryProvider->getCountry();
239
    }
240
241
    /**
242
     * returns the country based on the Visitor Country Provider.
243
     * this is some sort of IP recogniser system (e.g. Geoip Class).
244
     *
245
     * @return string (country code)
246
     **/
247
    public static function get_ip()
248
    {
249
        $visitorCountryProviderClassName = EcommerceConfig::get('EcommerceCountry', 'visitor_country_provider');
250
        if (!$visitorCountryProviderClassName) {
251
            $visitorCountryProviderClassName = 'EcommerceCountry_VisitorCountryProvider';
252
        }
253
        $visitorCountryProvider = new $visitorCountryProviderClassName();
254
255
        return $visitorCountryProvider->getIP();
256
    }
257
258
    private static $_countries_from_db_cache = array();
259
260
    /**
261
     *               e.g.
262
     *               "NZ" => "New Zealand"
263
     * @return array
264
     */
265
    public static function get_country_dropdown($showAllCountries = true, $addEmptyString = false, $useIDNotCode = false)
266
    {
267
        $key = ($showAllCountries ? "all" : "notall");
268
        if (isset(self::$_countries_from_db_cache[$key])) {
269
            $array = self::$_countries_from_db_cache[$key];
270
        } else {
271
            $array = array();
272
            $objects = null;
273
            if (class_exists('Geoip') && $showAllCountries && ! $useIDNotCode) {
274
                $array = Geoip::getCountryDropDown();
275
            } elseif ($showAllCountries) {
276
                $objects = EcommerceCountry::get();
277
            } else {
278
                $objects = EcommerceCountry::get()->filter(array('DoNotAllowSales' => 0));
279
            }
280
            if ($objects && $objects->count()) {
281
                if ($useIDNotCode) {
282
                    $idField = 'ID';
283
                } else {
284
                    $idField = 'Code';
285
                }
286
                $array = $objects->map($idField, 'Name')->toArray();
287
            }
288
            self::$_countries_from_db_cache[$key] = $array;
289
        }
290
        if (count($array)) {
291
            if ($addEmptyString) {
292
                $array = array("", " -- please select -- ") + $array;
293
            }
294
        }
295
        return $array;
296
    }
297
298
    /**
299
     * This function exists as a shortcut.
300
     * If there is only ONE allowed country code
301
     * then a lot of checking of countries can be avoided.
302
     *
303
     * @return string | null - countrycode
304
     **/
305
    public static function get_fixed_country_code()
306
    {
307
        $a = EcommerceConfig::get('EcommerceCountry', 'allowed_country_codes');
308
        if (is_array($a) && count($a) == 1) {
309
            return array_shift($a);
310
        }
311
312
        return null;
313
    }
314
315
    /**
316
     * @alias for EcommerceCountry::find_title
317
     *
318
     * @param string $code
319
     *                     We have this as this is the same as Geoip
320
     *
321
     * @return string
322
     */
323
    public static function countryCode2name($code)
324
    {
325
        return self::find_title($code);
326
    }
327
328
    /**
329
     * returns the country name from a code.
330
     *
331
     * @return string
332
     **/
333
    public static function find_title($code)
334
    {
335
        $code = strtoupper($code);
336
        $options = self::get_country_dropdown($showAllCountries = true);
337
        // check if code was provided, and is found in the country array
338
        if (isset($options[$code])) {
339
            return $options[$code];
340
        } elseif ($code) {
341
            $obj = EcommerceCountry::get()->filter(array('Code' => $code))->first();
342
            if ($obj) {
343
                return $obj->Name;
344
            }
345
            return $code;
346
        } else {
347
            return _t('Ecommerce.COUNTRY_NOT_FOUND', '[COUNTRY NOT FOUND]');
348
        }
349
    }
350
351
    /**
352
     * Memory for the order's country.
353
     *
354
     * @var array
355
     */
356
    private static $_country_cache = array();
357
358
    /**
359
     * @param int (optional) $orderID
360
     */
361
    public static function reset_get_country_cache($orderID = 0)
362
    {
363
        $orderID = ShoppingCart::current_order_id($orderID);
364
        unset(self::$_country_cache[$orderID]);
365
    }
366
367
    /**
368
     * @param int (optional) $orderID
369
     */
370
    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...
371
    {
372
        $orderID = ShoppingCart::current_order_id($orderID);
373
374
        return isset(self::$_country_cache[$orderID]) ? self::$_country_cache[$orderID] : null;
375
    }
376
377
    /**
378
     * @param string         $countryCode
379
     * @param int (optional) $orderID
380
     */
381
    public static function set_country_cache($countryCode, $orderID = 0)
382
    {
383
        $orderID = ShoppingCart::current_order_id($orderID);
384
        self::$_country_cache[$orderID] = $countryCode;
385
    }
386
387
    /**
388
     * This function works out the most likely country for the current order.
389
     *
390
     * @param bool (optional) $recalculate
391
     * @param int (optional)  $orderID
392
     *
393
     * @return string - Country Code - e.g. NZ
394
     **/
395
    public static function get_country($recalculate = false, $orderID = 0)
396
    {
397
        $orderID = ShoppingCart::current_order_id($orderID);
398
        $countryCode = self::get_country_cache($orderID);
399
        if ($countryCode === null || $recalculate) {
400
            $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...
401
            //1. fixed country is first
402
            $countryCode = self::get_fixed_country_code();
403
            if (!$countryCode) {
404
                //2. check order / shipping address / ip address
405
                //include $countryCode = self::get_country_from_ip();
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
406
                if ($o = ShoppingCart::current_order()) {
407
                    $countryCode = $o->getCountry();
408
                //3 ... if there is no shopping cart, then we still want it from IP
409
                } else {
410
                    $countryCode = self::get_country_from_ip();
411
                }
412
                //4 check default country set in GEO IP....
413
                if (!$countryCode) {
414
                    $countryCode = self::get_country_default();
415
                }
416
            }
417
            self::set_country_cache($countryCode, $orderID);
418
        }
419
420
        return $countryCode;
421
    }
422
423
    /**
424
     * A bling guess at the best country!
425
     *
426
     * @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...
427
     */
428
    public static function get_country_default()
429
    {
430
        $countryCode = EcommerceConfig::get('EcommerceCountry', 'default_country_code');
431
        //5. take the FIRST country from the get_allowed_country_codes
432
        if (!$countryCode) {
433
            $countryArray = self::list_of_allowed_entries_for_dropdown();
434
            if (is_array($countryArray) && count($countryArray)) {
435
                foreach ($countryArray as $countryCode => $countryName) {
436
                    //we stop at the first one... as we have no idea which one is the best.
437
                    break;
438
                }
439
            }
440
        }
441
442
        return $countryCode;
443
    }
444
445
    /**
446
     * This function works out the most likely country for the current order
447
     * and returns the Country Object, if any.
448
     *
449
     * @param bool    (optional)   $recalculate
450
     * @param string  (optional)   $countryCode
451
     *
452
     * @return EcommerceCountry | Null
0 ignored issues
show
Documentation introduced by
Should the return type not be EcommerceCountry|null?

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...
453
     **/
454
    public static function get_country_object($recalculate = false, $countryCode = null)
455
    {
456
        if (! $countryCode) {
457
            $countryCode = self::get_country($recalculate);
458
        }
459
460
        return EcommerceCountry::get()->filter(array('Code' => $countryCode))->First();
461
    }
462
463
    /**
464
     * maps like this
465
     *     NZ => 1
466
     *     AU => 2
467
     *     etc...
468
     * @var array
469
     */
470
    private static $_code_to_id_map = array();
471
    /**
472
     * returns the ID of the country or 0.
473
     *
474
     * @param string (optional)   $countryCode
475
     * @param bool   (optional)   $recalculate
476
     *
477
     * @return int
478
     **/
479
    public static function get_country_id($countryCode = null, $recalculate = false)
480
    {
481
        if (!$countryCode) {
482
            $countryCode = self::get_country($recalculate);
483
        }
484
        if (isset(self::$_code_to_id_map[$countryCode])) {
485
            return self::$_code_to_id_map[$countryCode];
486
        }
487
        self::$_code_to_id_map[$countryCode] = 0;
488
        $country = EcommerceCountry::get()
489
            ->filter(array('Code' => $countryCode))
490
            ->first();
491
        if ($country) {
492
            self::$_code_to_id_map[$countryCode] = $country->ID;
493
            return $country->ID;
494
        }
495
496
        return 0;
497
    }
498
499
    /**
500
     * Memory for allow country to check.
501
     *
502
     * @var null | Boolean
503
     */
504
    private static $_allow_sales_cache = array();
505
506
    public static function reset_allow_sales_cache()
507
    {
508
        self::$_allow_sales_cache = null;
509
    }
510
511
    /**
512
     * Checks if we are allowed to sell to the current country.
513
     *
514
     * @param int (optional) $orderID
515
     *
516
     * @return bool
517
     */
518
    public static function allow_sales($orderID = 0)
519
    {
520
        $orderID = ShoppingCart::current_order_id($orderID);
521
        if (!isset(self::$_allow_sales_cache[$orderID])) {
522
            self::$_allow_sales_cache[$orderID] = true;
523
            $countryCode = self::get_country(false, $orderID);
524
            if ($countryCode) {
525
                $countries = EcommerceCountry::get()
526
                    ->filter(array(
527
                        'DoNotAllowSales' => 1,
528
                        'Code' => $countryCode,
529
                    ));
530
                if ($countries->count()) {
531
                    self::$_allow_sales_cache[$orderID] = false;
532
                }
533
            }
534
        }
535
536
        return self::$_allow_sales_cache[$orderID];
537
    }
538
539
    /**
540
     * returns an array of Codes => Names of all countries that can be used.
541
     * Use "list_of_allowed_entries_for_dropdown" to get the list.
542
     *
543
     * @todo add caching
544
     *
545
     * @return array
546
     **/
547
    protected static function get_default_array()
548
    {
549
        $defaultArray = array();
550
        $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...
551
        if ($code = self::get_fixed_country_code()) {
552
            $defaultArray[$code] = self::find_title($code);
553
554
            return $defaultArray;
555
        }
556
        $countries = EcommerceCountry::get()->exclude(array('DoNotAllowSales' => 1));
557
        if ($countries && $countries->count()) {
558
            foreach ($countries as $country) {
559
                $defaultArray[$country->Code] = $country->Name;
560
            }
561
        }
562
563
        return $defaultArray;
564
    }
565
566
    public function getCMSFields()
567
    {
568
        $fields = parent::getCMSFields();
569
        $fields->addFieldToTab('Root.Main', new LiteralField(
570
            'Add Add Countries',
571
            '
572
                <h3>Short-Cuts</h3>
573
                <h6>
574
                    <a href="/dev/tasks/EcommerceTaskCountryAndRegion_DisallowAllCountries" target="_blank">'._t('EcommerceCountry.DISALLOW_ALL', 'disallow sales to all countries').'</a> |||
575
                    <a href="/dev/tasks/EcommerceTaskCountryAndRegion_AllowAllCountries" target="_blank">'._t('EcommerceCountry.ALLOW_ALL', 'allow sales to all countries').'</a>
576
                </h6>
577
            ')
578
        );
579
580
        return $fields;
581
    }
582
583
    /**
584
     * link to edit the record.
585
     *
586
     * @param string | Null $action - e.g. edit
587
     *
588
     * @return string
589
     */
590
    public function CMSEditLink($action = null)
591
    {
592
        return Controller::join_links(
593
            Director::baseURL(),
594
            '/admin/shop/'.$this->ClassName.'/EditForm/field/'.$this->ClassName.'/item/'.$this->ID.'/',
595
            $action
596
        );
597
    }
598
599
    /**
600
     * standard SS method.
601
     */
602
    public function requireDefaultRecords()
0 ignored issues
show
Coding Style introduced by
requireDefaultRecords uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
603
    {
604
        parent::requireDefaultRecords();
605
        if ((! EcommerceCountry::get()->count()) || isset($_REQUEST['resetecommercecountries'])) {
606
            $task = new EcommerceTaskCountryAndRegion();
607
            $task->run(null);
608
        }
609
    }
610
611
    /**
612
     * standard SS method
613
     * cleans up codes.
614
     */
615
    public function onBeforeWrite()
616
    {
617
        parent::onBeforeWrite();
618
        $filter = EcommerceCodeFilter::create();
619
        $filter->checkCode($this);
620
    }
621
622
    //DYNAMIC LIMITATIONS
623
624
    /**
625
     * these variables and methods allow to "dynamically limit the countries available,
626
     * based on, for example: ordermodifiers, item selection, etc....
627
     * for example, if a person chooses delivery within Australasia (with modifier) -
628
     * then you can limit the countries available to "Australasian" countries.
629
     */
630
631
    /**
632
     * List of countries that should be shown.
633
     *
634
     * @param array $a: should be country codes e.g. array("NZ", "NP", "AU");
635
     *
636
     * @var array
637
     */
638
    private static $for_current_order_only_show_countries = array();
639
    public static function get_for_current_order_only_show_countries()
640
    {
641
        return self::$for_current_order_only_show_countries;
642
    }
643
    public static function set_for_current_order_only_show_countries(array $a)
644
    {
645
        if (count(self::$for_current_order_only_show_countries)) {
646
            //we INTERSECT here so that only countries allowed by all forces (modifiers) are added.
647
                self::$for_current_order_only_show_countries = array_intersect($a, self::$for_current_order_only_show_countries);
648
        } else {
649
            self::$for_current_order_only_show_countries = $a;
650
        }
651
    }
652
653
    /**
654
     * List of countries that should NOT be shown.
655
     *
656
     * @param array $a: should be country codes e.g. array("NZ", "NP", "AU");
657
     *
658
     * @var array
659
     */
660
    private static $for_current_order_do_not_show_countries = array();
661
    public static function get_for_current_order_do_not_show_countries()
662
    {
663
        return self::$for_current_order_do_not_show_countries;
664
    }
665
    public static function set_for_current_order_do_not_show_countries(array $a)
666
    {
667
        //We MERGE here because several modifiers may limit the countries
668
            self::$for_current_order_do_not_show_countries = array_merge($a, self::$for_current_order_do_not_show_countries);
669
    }
670
671
    /**
672
     * @var array
673
     */
674
    private static $list_of_allowed_entries_for_dropdown_array = array();
675
676
    /**
677
     * takes the defaultArray and limits it with "only show" and "do not show" value, relevant for the current order.
678
     *
679
     * @return array (Code, Title)
680
     **/
681
    public static function list_of_allowed_entries_for_dropdown()
682
    {
683
        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...
684
            $defaultArray = self::get_default_array();
685
            $onlyShow = self::$for_current_order_only_show_countries;
686
            $doNotShow = self::$for_current_order_do_not_show_countries;
687
            if (is_array($onlyShow) && count($onlyShow)) {
688
                foreach ($defaultArray as $key => $value) {
689
                    if (!in_array($key, $onlyShow)) {
690
                        unset($defaultArray[$key]);
691
                    }
692
                }
693
            }
694
            if (is_array($doNotShow) && count($doNotShow)) {
695
                foreach ($doNotShow as $code) {
696
                    if (isset($defaultArray[$code])) {
697
                        unset($defaultArray[$code]);
698
                    }
699
                }
700
            }
701
            self::$list_of_allowed_entries_for_dropdown_array = $defaultArray;
702
        }
703
704
        return self::$list_of_allowed_entries_for_dropdown_array;
705
    }
706
707
    /**
708
     * checks if a code is allowed.
709
     *
710
     * @param string $code - e.g. NZ, NSW, or CO
711
     *
712
     * @return bool
713
     **/
714
    public static function code_allowed($code)
715
    {
716
        return array_key_exists($code, self::list_of_allowed_entries_for_dropdown());
717
    }
718
719
    /**
720
     * Casted variable to show if sales are allowed to this country.
721
     *
722
     * @return bool
723
     */
724
    public function AllowSales()
725
    {
726
        return $this->getAllowSales();
727
    }
728
    public function getAllowSales()
729
    {
730
        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...
731
            return false;
732
        } else {
733
            return true;
734
        }
735
    }
736
737
    /**
738
     * Casted variable to show if sales are allowed to this country.
739
     *
740
     * @return string
741
     */
742
    public function AllowSalesNice()
743
    {
744
        return $this->getAllowSalesNice();
745
    }
746
    public function getAllowSalesNice()
747
    {
748
        if ($this->AllowSales()) {
749
            return _t('EcommerceCountry.YES', 'Yes');
750
        } else {
751
            return _t('EcommerceCountry.NO', 'No');
752
        }
753
    }
754
}
755