Completed
Push — master ( ba6a58...cefb5c )
by
unknown
03:33
created

EcommerceCountry::allow_sales()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
dl 0
loc 20
rs 9.2
c 0
b 0
f 0
eloc 13
nc 4
nop 1
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 = DataObject::get_one(
342
                'EcommerceCountry',
343
                array('Code' => $code)
344
            );
345
            if ($obj) {
346
                return $obj->Name;
347
            }
348
            return $code;
349
        } else {
350
            return _t('Ecommerce.COUNTRY_NOT_FOUND', '[COUNTRY NOT FOUND]');
351
        }
352
    }
353
354
    /**
355
     * Memory for the order's country.
356
     *
357
     * @var array
358
     */
359
    private static $_country_cache = array();
360
361
    /**
362
     * @param int (optional) $orderID
363
     */
364
    public static function reset_get_country_cache($orderID = 0)
365
    {
366
        $orderID = ShoppingCart::current_order_id($orderID);
367
        unset(self::$_country_cache[$orderID]);
368
    }
369
370
    /**
371
     * @param int (optional) $orderID
372
     */
373
    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...
374
    {
375
        $orderID = ShoppingCart::current_order_id($orderID);
376
377
        return isset(self::$_country_cache[$orderID]) ? self::$_country_cache[$orderID] : null;
378
    }
379
380
    /**
381
     * @param string         $countryCode
382
     * @param int (optional) $orderID
383
     */
384
    public static function set_country_cache($countryCode, $orderID = 0)
385
    {
386
        $orderID = ShoppingCart::current_order_id($orderID);
387
        self::$_country_cache[$orderID] = $countryCode;
388
    }
389
390
    /**
391
     * This function works out the most likely country for the current order.
392
     *
393
     * @param bool (optional) $recalculate
394
     * @param int (optional)  $orderID
395
     *
396
     * @return string - Country Code - e.g. NZ
397
     **/
398
    public static function get_country($recalculate = false, $orderID = 0)
399
    {
400
        $orderID = ShoppingCart::current_order_id($orderID);
401
        $countryCode = self::get_country_cache($orderID);
402
        if ($countryCode === null || $recalculate) {
403
            $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...
404
            //1. fixed country is first
405
            $countryCode = self::get_fixed_country_code();
406
            if (!$countryCode) {
407
                //2. check order / shipping address / ip address
408
                //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...
409
                if ($o = ShoppingCart::current_order()) {
410
                    $countryCode = $o->getCountry();
411
                //3 ... if there is no shopping cart, then we still want it from IP
412
                } else {
413
                    $countryCode = self::get_country_from_ip();
414
                }
415
                //4 check default country set in GEO IP....
416
                if (!$countryCode) {
417
                    $countryCode = self::get_country_default();
418
                }
419
            }
420
            self::set_country_cache($countryCode, $orderID);
421
        }
422
423
        return $countryCode;
424
    }
425
426
    /**
427
     * A bling guess at the best country!
428
     *
429
     * @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...
430
     */
431
    public static function get_country_default()
432
    {
433
        $countryCode = EcommerceConfig::get('EcommerceCountry', 'default_country_code');
434
        //5. take the FIRST country from the get_allowed_country_codes
435
        if (!$countryCode) {
436
            $countryArray = self::list_of_allowed_entries_for_dropdown();
437
            if (is_array($countryArray) && count($countryArray)) {
438
                foreach ($countryArray as $countryCode => $countryName) {
439
                    //we stop at the first one... as we have no idea which one is the best.
440
                    break;
441
                }
442
            }
443
        }
444
445
        return $countryCode;
446
    }
447
448
    /**
449
     * This function works out the most likely country for the current order
450
     * and returns the Country Object, if any.
451
     *
452
     * @param bool    (optional)   $recalculate
453
     * @param string  (optional)   $countryCode
454
     *
455
     * @return EcommerceCountry | Null
456
     **/
457
    public static function get_country_object($recalculate = false, $countryCode = null)
458
    {
459
        if (! $countryCode) {
460
            $countryCode = self::get_country($recalculate);
461
        }
462
463
        return DataObject::get_one(
464
            'EcommerceCountry',
465
            array('Code' => $countryCode)
466
        );
467
    }
468
469
    /**
470
     * maps like this
471
     *     NZ => 1
472
     *     AU => 2
473
     *     etc...
474
     * @var array
475
     */
476
    private static $_code_to_id_map = array();
477
    /**
478
     * returns the ID of the country or 0.
479
     *
480
     * @param string (optional)   $countryCode
481
     * @param bool   (optional)   $recalculate
482
     *
483
     * @return int
484
     **/
485
    public static function get_country_id($countryCode = null, $recalculate = false)
486
    {
487
        if (!$countryCode) {
488
            $countryCode = self::get_country($recalculate);
489
        }
490
        if (isset(self::$_code_to_id_map[$countryCode])) {
491
            return self::$_code_to_id_map[$countryCode];
492
        }
493
        self::$_code_to_id_map[$countryCode] = 0;
494
        $country = DataObject::get_one(
495
            'EcommerceCountry',
496
            array('Code' => $countryCode)
497
        );
498
        if ($country) {
499
            self::$_code_to_id_map[$countryCode] = $country->ID;
500
            return $country->ID;
501
        }
502
503
        return 0;
504
    }
505
506
    /**
507
     * Memory for allow country to check.
508
     *
509
     * @var null | Boolean
510
     */
511
    private static $_allow_sales_cache = array();
512
513
    public static function reset_allow_sales_cache()
514
    {
515
        self::$_allow_sales_cache = null;
516
    }
517
518
    /**
519
     * Checks if we are allowed to sell to the current country.
520
     *
521
     * @param int (optional) $orderID
522
     *
523
     * @return bool
524
     */
525
    public static function allow_sales($orderID = 0)
526
    {
527
        $orderID = ShoppingCart::current_order_id($orderID);
528
        if (!isset(self::$_allow_sales_cache[$orderID])) {
529
            self::$_allow_sales_cache[$orderID] = true;
530
            $countryCode = self::get_country(false, $orderID);
531
            if ($countryCode) {
532
                $countries = EcommerceCountry::get()
533
                    ->filter(array(
534
                        'DoNotAllowSales' => 1,
535
                        'Code' => $countryCode,
536
                    ));
537
                if ($countries->count()) {
538
                    self::$_allow_sales_cache[$orderID] = false;
539
                }
540
            }
541
        }
542
543
        return self::$_allow_sales_cache[$orderID];
544
    }
545
546
    /**
547
     * returns an array of Codes => Names of all countries that can be used.
548
     * Use "list_of_allowed_entries_for_dropdown" to get the list.
549
     *
550
     * @todo add caching
551
     *
552
     * @return array
553
     **/
554
    protected static function get_default_array()
555
    {
556
        $defaultArray = array();
557
        $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...
558
        if ($code = self::get_fixed_country_code()) {
559
            $defaultArray[$code] = self::find_title($code);
560
561
            return $defaultArray;
562
        }
563
        $countries = EcommerceCountry::get()->exclude(array('DoNotAllowSales' => 1));
564
        if ($countries && $countries->count()) {
565
            foreach ($countries as $country) {
566
                $defaultArray[$country->Code] = $country->Name;
567
            }
568
        }
569
570
        return $defaultArray;
571
    }
572
573
    public function getCMSFields()
574
    {
575
        $fields = parent::getCMSFields();
576
        $fields->addFieldToTab('Root.Main', new LiteralField(
577
            'Add Add Countries',
578
            '
579
                <h3>Short-Cuts</h3>
580
                <h6>
581
                    <a href="/dev/tasks/EcommerceTaskCountryAndRegion_DisallowAllCountries" target="_blank">'._t('EcommerceCountry.DISALLOW_ALL', 'disallow sales to all countries').'</a> |||
582
                    <a href="/dev/tasks/EcommerceTaskCountryAndRegion_AllowAllCountries" target="_blank">'._t('EcommerceCountry.ALLOW_ALL', 'allow sales to all countries').'</a>
583
                </h6>
584
            ')
585
        );
586
587
        return $fields;
588
    }
589
590
591
592
    /**
593
     * link to edit the record.
594
     *
595
     * @param string | Null $action - e.g. edit
596
     *
597
     * @return string
598
     */
599
    public function CMSEditLink($action = null)
600
    {
601
        return CMSEditLinkAPI::find_edit_link_for_object($this, $action);
602
    }
603
604
    /**
605
     * standard SS method.
606
     */
607
    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...
608
    {
609
        parent::requireDefaultRecords();
610
        if ((! EcommerceCountry::get()->count()) || isset($_REQUEST['resetecommercecountries'])) {
611
            $task = new EcommerceTaskCountryAndRegion();
612
            $task->run(null);
613
        }
614
    }
615
616
    /**
617
     * standard SS method
618
     * cleans up codes.
619
     */
620
    public function onBeforeWrite()
621
    {
622
        parent::onBeforeWrite();
623
        $filter = EcommerceCodeFilter::create();
624
        $filter->checkCode($this);
625
    }
626
627
    //DYNAMIC LIMITATIONS
628
629
    /**
630
     * these variables and methods allow to "dynamically limit the countries available,
631
     * based on, for example: ordermodifiers, item selection, etc....
632
     * for example, if a person chooses delivery within Australasia (with modifier) -
633
     * then you can limit the countries available to "Australasian" countries.
634
     */
635
636
    /**
637
     * List of countries that should be shown.
638
     *
639
     * @param array $a: should be country codes e.g. array("NZ", "NP", "AU");
640
     *
641
     * @var array
642
     */
643
    private static $for_current_order_only_show_countries = array();
644
    public static function get_for_current_order_only_show_countries()
645
    {
646
        return self::$for_current_order_only_show_countries;
647
    }
648
    public static function set_for_current_order_only_show_countries(array $a)
649
    {
650
        if (count(self::$for_current_order_only_show_countries)) {
651
            //we INTERSECT here so that only countries allowed by all forces (modifiers) are added.
652
                self::$for_current_order_only_show_countries = array_intersect($a, self::$for_current_order_only_show_countries);
653
        } else {
654
            self::$for_current_order_only_show_countries = $a;
655
        }
656
    }
657
658
    /**
659
     * List of countries that should NOT be shown.
660
     *
661
     * @param array $a: should be country codes e.g. array("NZ", "NP", "AU");
662
     *
663
     * @var array
664
     */
665
    private static $for_current_order_do_not_show_countries = array();
666
    public static function get_for_current_order_do_not_show_countries()
667
    {
668
        return self::$for_current_order_do_not_show_countries;
669
    }
670
    public static function set_for_current_order_do_not_show_countries(array $a)
671
    {
672
        //We MERGE here because several modifiers may limit the countries
673
            self::$for_current_order_do_not_show_countries = array_merge($a, self::$for_current_order_do_not_show_countries);
674
    }
675
676
    /**
677
     * @var array
678
     */
679
    private static $list_of_allowed_entries_for_dropdown_array = array();
680
681
    /**
682
     * takes the defaultArray and limits it with "only show" and "do not show" value, relevant for the current order.
683
     *
684
     * @return array (Code, Title)
685
     **/
686
    public static function list_of_allowed_entries_for_dropdown()
687
    {
688
        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...
689
            $defaultArray = self::get_default_array();
690
            $onlyShow = self::$for_current_order_only_show_countries;
691
            $doNotShow = self::$for_current_order_do_not_show_countries;
692
            if (is_array($onlyShow) && count($onlyShow)) {
693
                foreach ($defaultArray as $key => $value) {
694
                    if (!in_array($key, $onlyShow)) {
695
                        unset($defaultArray[$key]);
696
                    }
697
                }
698
            }
699
            if (is_array($doNotShow) && count($doNotShow)) {
700
                foreach ($doNotShow as $code) {
701
                    if (isset($defaultArray[$code])) {
702
                        unset($defaultArray[$code]);
703
                    }
704
                }
705
            }
706
            self::$list_of_allowed_entries_for_dropdown_array = $defaultArray;
707
        }
708
709
        return self::$list_of_allowed_entries_for_dropdown_array;
710
    }
711
712
    /**
713
     * checks if a code is allowed.
714
     *
715
     * @param string $code - e.g. NZ, NSW, or CO
716
     *
717
     * @return bool
718
     **/
719
    public static function code_allowed($code)
720
    {
721
        return array_key_exists($code, self::list_of_allowed_entries_for_dropdown());
722
    }
723
724
    /**
725
     * Casted variable to show if sales are allowed to this country.
726
     *
727
     * @return bool
728
     */
729
    public function AllowSales()
730
    {
731
        return $this->getAllowSales();
732
    }
733
    public function getAllowSales()
734
    {
735
        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...
736
            return false;
737
        } else {
738
            return true;
739
        }
740
    }
741
742
    /**
743
     * Casted variable to show if sales are allowed to this country.
744
     *
745
     * @return string
746
     */
747
    public function AllowSalesNice()
748
    {
749
        return $this->getAllowSalesNice();
750
    }
751
    public function getAllowSalesNice()
752
    {
753
        if ($this->AllowSales()) {
754
            return _t('EcommerceCountry.YES', 'Yes');
755
        } else {
756
            return _t('EcommerceCountry.NO', 'No');
757
        }
758
    }
759
}
760