Issues (2002)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

code/model/address/OrderAddress.php (37 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
4
/**
5
 * @description: each order has an address: a Shipping and a Billing address
6
 * This is a base-class for both.
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 OrderAddress extends DataObject implements EditableEcommerceObject
15
{
16
    /**
17
     * standard SS static definition.
18
     */
19
    private static $singular_name = 'Order Address';
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...
20
    public function i18n_singular_name()
21
    {
22
        return _t('OrderAddress.ORDERADDRESS', 'Order Address');
23
    }
24
25
    /**
26
     * standard SS static definition.
27
     */
28
    private static $plural_name = 'Order Addresses';
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...
29
    public function i18n_plural_name()
30
    {
31
        return _t('OrderAddress.ORDERADDRESSES', 'Order Addresses');
32
    }
33
34
    /**
35
     * standard SS static definition.
36
     */
37
    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...
38
        'FullName' => 'Text',
39
        'FullString' => 'Text',
40
        'JSONData' => 'Text',
41
    );
42
43
    /**
44
     * returns the id of the MAIN country field for template manipulation.
45
     * Main means the one that is used as the primary one (e.g. for tax purposes).
46
     *
47
     * @see EcommerceConfig::get("OrderAddress", "use_shipping_address_for_main_region_and_country")
48
     *
49
     * @return string
50
     */
51
    public static function get_country_field_ID()
52
    {
53
        if (EcommerceConfig::get('OrderAddress', 'use_shipping_address_for_main_region_and_country')) {
54
            return 'ShippingCountry';
55
        } else {
56
            return 'Country';
57
        }
58
    }
59
60
    /**
61
     * returns the id of the MAIN region field for template manipulation.
62
     * Main means the one that is used as the primary one (e.g. for tax purposes).
63
     *
64
     * @return string
65
     */
66
    public static function get_region_field_ID()
67
    {
68
        if (EcommerceConfig::get('OrderAddress', 'use_shipping_address_for_main_region_and_country')) {
69
            return 'ShippingRegion';
70
        } else {
71
            return 'Region';
72
        }
73
    }
74
75
    /**
76
     * There might be times when a modifier needs to make an address field read-only.
77
     * In that case, this is done here.
78
     *
79
     * @var array
80
     */
81
    protected $readOnlyFields = array();
82
83
    /**
84
     * sets a field to readonly state
85
     * we use this when modifiers have been set that require a field to be a certain value
86
     * for example - a PostalCode field maybe set in the modifier.
87
     *
88
     * @param string $fieldName
89
     */
90
    public function addReadOnlyField($fieldName)
91
    {
92
        $this->readOnlyFields[$fieldName] = $fieldName;
93
    }
94
95
    /**
96
     * removes a field from the readonly state.
97
     *
98
     * @param string $fieldName
99
     */
100
    public function removeReadOnlyField($fieldName)
101
    {
102
        unset($this->readOnlyFields[$fieldName]);
103
    }
104
105
    /**
106
     * link to edit the record.
107
     *
108
     * @param string | Null $action - e.g. edit
109
     *
110
     * @return string
0 ignored issues
show
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...
111
     */
112
    public function CMSEditLink($action = null)
113
    {
114
        return CMSEditLinkAPI::find_edit_link_for_object($this, $action);
115
    }
116
117
    /**
118
     * save edit status for speed's sake.
119
     *
120
     * @var bool
121
     */
122
    protected $_canEdit = null;
123
124
    /**
125
     * save view status for speed's sake.
126
     *
127
     * @var bool
128
     */
129
    protected $_canView = null;
130
131
    public function canCreate($member = null)
132
    {
133
        if (! $member) {
134
            $member = Member::currentUser();
135
        }
136
        $extended = $this->extendedCan(__FUNCTION__, $member);
137
        if ($extended !== null) {
138
            return $extended;
139
        }
140
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
141
            return true;
142
        }
143
144
        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 canCreate()). 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...
145
    }
146
147
    /**
148
     * Standard SS method
149
     * This is an important method.
150
     *
151
     * @param Member $member
0 ignored issues
show
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
     * @return bool
0 ignored issues
show
Should the return type not be boolean|string|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...
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
$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 (!$this->exists()) {
165
            return $this->canCreate($member);
166
        }
167
        if ($this->_canView === null) {
168
            $this->_canView = false;
169
            if ($this->Order()) {
0 ignored issues
show
Documentation Bug introduced by
The method Order does not exist on object<OrderAddress>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
170
                if ($this->Order()->exists()) {
0 ignored issues
show
Documentation Bug introduced by
The method Order does not exist on object<OrderAddress>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
171
                    if ($this->Order()->canView($member)) {
0 ignored issues
show
Documentation Bug introduced by
The method Order does not exist on object<OrderAddress>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
172
                        $this->_canView = true;
173
                    }
174
                }
175
            }
176
        }
177
178
        return $this->_canView;
179
    }
180
181
    /**
182
     * Standard SS method
183
     * This is an important method.
184
     *
185
     * @param Member $member
0 ignored issues
show
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...
186
     *
187
     * @return bool
0 ignored issues
show
Should the return type not be boolean|string|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...
188
     **/
189
    public function canEdit($member = null)
190
    {
191
        if (! $member) {
192
            $member = Member::currentUser();
193
        }
194
        $extended = $this->extendedCan(__FUNCTION__, $member);
0 ignored issues
show
$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...
195
        if ($extended !== null) {
196
            return $extended;
197
        }
198
        if (! $this->exists()) {
199
            return $this->canCreate($member);
200
        }
201
        if ($this->_canEdit === null) {
202
            $this->_canEdit = false;
203
            if ($this->Order()) {
0 ignored issues
show
Documentation Bug introduced by
The method Order does not exist on object<OrderAddress>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
204
                if ($this->Order()->exists()) {
0 ignored issues
show
Documentation Bug introduced by
The method Order does not exist on object<OrderAddress>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
205
                    if ($this->Order()->canEdit($member)) {
0 ignored issues
show
Documentation Bug introduced by
The method Order does not exist on object<OrderAddress>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
206
                        if (! $this->Order()->IsSubmitted()) {
0 ignored issues
show
Documentation Bug introduced by
The method Order does not exist on object<OrderAddress>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
207
                            $this->_canEdit = true;
208
                        }
209
                    }
210
                }
211
            }
212
        }
213
214
        return $this->_canEdit;
215
    }
216
217
    public function canDelete($member = null)
218
    {
219
        if (! $member) {
220
            $member = Member::currentUser();
221
        }
222
        $extended = $this->extendedCan(__FUNCTION__, $member);
223
        if ($extended !== null) {
224
            return $extended;
225
        }
226
227
        return false;
228
    }
229
230
    /**
231
     * Determine which properties on the DataObject are
232
     * searchable, and map them to their default {@link FormField}
233
     * representations. Used for scaffolding a searchform for {@link ModelAdmin}.
234
     *
235
     * Some additional logic is included for switching field labels, based on
236
     * how generic or specific the field type is.
237
     *
238
     * Used by {@link SearchContext}.
239
     *
240
     * @param array $_params
0 ignored issues
show
Should the type for parameter $_params not be array|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...
241
     *                       'fieldClasses': Associative array of field names as keys and FormField classes as values
242
     *                       'restrictFields': Numeric array of a field name whitelist
243
     *
244
     * @return FieldList
245
     */
246
    public function scaffoldSearchFields($_params = null)
247
    {
248
        $fields = parent::scaffoldSearchFields($_params);
0 ignored issues
show
$fields 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...
249
        $fields = parent::scaffoldSearchFields();
250
        $fields->replaceField('OrderID', new NumericField('OrderID', 'Order Number'));
251
252
        return $fields;
253
    }
254
255
    /**
256
     * @return FieldList
257
     */
258
    protected function getEcommerceFields()
259
    {
260
        return new FieldList();
261
    }
262
263
    /**
264
     * put together a textfield for a postal code field.
265
     *
266
     * @param string $name - name of the field
267
     *
268
     * @return TextField
269
     **/
270
    protected function getPostalCodeField($name)
271
    {
272
        $field = new TextField($name, _t('OrderAddress.POSTALCODE', 'Postal Code'));
273
        $postalCodeURL = EcommerceDBConfig::current_ecommerce_db_config()->PostalCodeURL;
0 ignored issues
show
The property PostalCodeURL does not exist on object<EcommerceDBConfig>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
274
        $postalCodeLabel = EcommerceDBConfig::current_ecommerce_db_config()->PostalCodeLabel;
0 ignored issues
show
The property PostalCodeLabel does not exist on object<EcommerceDBConfig>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
275
        if ($postalCodeURL && $postalCodeLabel) {
276
            $prefix = EcommerceConfig::get('OrderAddress', 'field_class_and_id_prefix');
277
            $field->setRightTitle('<a href="'.$postalCodeURL.'" id="'.$prefix.$name.'Link" class="'.$prefix.'postalCodeLink">'.$postalCodeLabel.'</a>');
278
        }
279
280
        return $field;
281
    }
282
283
    /**
284
     * put together a dropdown for the region field.
285
     *
286
     * @param string $name - name of the field
287
     *
288
     * @return DropdownField
289
     **/
290
    protected function getRegionField($name, $freeTextName = '')
291
    {
292
        if (EcommerceRegion::show()) {
293
            $nameWithoutID = str_replace('ID', '', $name);
294
            $title = _t('OrderAddress.'.strtoupper($nameWithoutID), 'Region / Province / State');
295
            $regionsForDropdown = EcommerceRegion::list_of_allowed_entries_for_dropdown();
296
            $count = count($regionsForDropdown);
297
            if ($count < 1) {
298
                if (!$freeTextName) {
299
                    $freeTextName = $nameWithoutID.'Code';
300
                }
301
                $regionField = new TextField($freeTextName, $title);
302
            } else {
303
                $regionField = new DropdownField($name, $title, $regionsForDropdown);
304
                if ($count < 2) {
305
                    //readonly shows as number (ID), rather than title
306
                    //$regionField = $regionField->performReadonlyTransformation();
307
                } else {
308
                    $regionField->setEmptyString(_t('OrderAdress.PLEASE_SELECT_REGION', '--- Select Region ---'));
309
                }
310
            }
311
        } else {
312
            //adding region field here as hidden field to make the code easier below...
313
            $regionField = new HiddenField($name, '', 0);
314
        }
315
        $prefix = EcommerceConfig::get('OrderAddress', 'field_class_and_id_prefix');
316
        $regionField->addExtraClass($prefix.'ajaxRegionField');
317
318
        return $regionField;
319
    }
320
321
    /**
322
     * put together a dropdown for the country field.
323
     *
324
     * @param string $name - name of the field
325
     *
326
     * @return DropdownField
327
     **/
328
    protected function getCountryField($name)
329
    {
330
        $countriesForDropdown = EcommerceCountry::list_of_allowed_entries_for_dropdown();
331
        $title = _t('OrderAddress.'.strtoupper($name), 'Country');
332
        $order = $this->Order();
0 ignored issues
show
Documentation Bug introduced by
The method Order does not exist on object<OrderAddress>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
333
334
        $countryCode = null;
335
        if ($order && $order->exists()) {
336
            //if it is the billing country field and we use a shipping address then ignore Order Country
337
            if ($order->UseShippingAddress && ($this instanceof BillingAddress)) {
338
                //do nothing
339
            } else {
340
                $countryCode = EcommerceCountry::get_country(false, $this->OrderID);
0 ignored issues
show
The property OrderID does not exist on object<OrderAddress>. 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...
341
            }
342
        }
343
        $countryField = new DropdownField($name, $title, $countriesForDropdown, $countryCode);
344
        $countryField->setRightTitle(_t('OrderAddress.'.strtoupper($name).'_RIGHT', ''));
345
        if (count($countriesForDropdown) < 2) {
346
            $countryField = $countryField->performReadonlyTransformation();
347
            if (count($countriesForDropdown) < 1) {
348
                $countryField = new HiddenField($name, '', 'not available');
349
            }
350
        }
351
        $prefix = EcommerceConfig::get('OrderAddress', 'field_class_and_id_prefix');
352
        $countryField->addExtraClass($prefix.'ajaxCountryField');
353
        //important, otherwise loadData will override the default value....
354
        if ($countryCode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $countryCode of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
355
            $this->$name = $countryCode;
356
        }
357
358
        return $countryField;
359
    }
360
361
    /**
362
     * makes selected fields into read only using the $this->readOnlyFields array.
363
     *
364
     * @param FieldList | Composite $fields
365
     *
366
     * @return FieldList
367
     */
368
    protected function makeSelectedFieldsReadOnly($fields)
369
    {
370
        $this->extend('augmentMakeSelectedFieldsReadOnly', $fields);
371
        if (is_array($this->readOnlyFields) && count($this->readOnlyFields)) {
372
            foreach ($this->readOnlyFields as $readOnlyField) {
373
                if ($oldField = $fields->fieldByName($readOnlyField)) {
374
                    $fields->replaceField($readOnlyField, $oldField->performReadonlyTransformation());
375
                }
376
            }
377
        }
378
379
        return $fields;
380
    }
381
382
    /**
383
     * Saves region - both shipping and billing fields are saved here for convenience sake (only one actually gets saved)
384
     * NOTE: do not call this method SetCountry as this has a special meaning! *.
385
     *
386
     * @param int -  RegionID
387
     **/
388
    public function SetRegionFields($regionID)
389
    {
390
        $regionField = $this->fieldPrefix().'RegionID';
391
        $this->$regionField = $regionID;
392
        $this->write();
393
    }
394
395
    /**
396
     * Saves country - both shipping and billing fields are saved here for convenience sake (only one actually gets saved)
397
     * NOTE: do not call this method SetCountry as this has a special meaning!
398
     *
399
     * @param string - CountryCode - e.g. NZ
400
     */
401
    public function SetCountryFields($countryCode)
402
    {
403
        $countryField = $this->fieldPrefix().'Country';
404
        $this->$countryField = $countryCode;
405
        $this->write();
406
    }
407
408
    /**
409
     * Casted variable
410
     * returns the full name of the person, e.g. "John Smith".
411
     *
412
     * @return string
413
     */
414
    public function getFullName()
415
    {
416
        $fieldNameField = $this->fieldPrefix().'FirstName';
417
        $fieldFirst = $this->$fieldNameField;
418
        $lastNameField = $this->fieldPrefix().'Surname';
419
        $fieldLast = $this->$lastNameField;
420
421
        return $fieldFirst.' '.$fieldLast;
422
    }
423
    public function FullName()
424
    {
425
        return $this->getFullName();
426
    }
427
428
    /**
429
     * Casted variable
430
     * returns the full strng of the record.
431
     *
432
     * @return string
0 ignored issues
show
Should the return type not be HTMLText?

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...
433
     */
434
    public function FullString()
435
    {
436
        return $this->getFullString();
437
    }
438
    public function getFullString()
439
    {
440
        Config::nest();
441
        Config::inst()->update('SSViewer', 'theme_enabled', true);
442
        $html = $this->renderWith('Order_Address'.str_replace('Address', '', $this->ClassName).'FullString');
443
        Config::unnest();
444
445
        return $html;
446
    }
447
448
    /**
449
     * returns a string that can be used to find out if two addresses are the same.
450
     *
451
     * @return string
452
     */
453
    public function comparisonString()
454
    {
455
        $comparisonString = '';
456
        $excludedFields = array('ID', 'OrderID');
457
        $fields = $this->stat('db');
458
        $regionFieldName = $this->fieldPrefix().'RegionID';
459
        $fields[$regionFieldName] = $regionFieldName;
460
        if ($fields) {
461
            foreach ($fields as $field => $useless) {
0 ignored issues
show
The expression $fields of type array|integer|double|string|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
462
                if (!in_array($field, $excludedFields)) {
463
                    $comparisonString .= preg_replace('/\s+/', '', $this->$field);
464
                }
465
            }
466
        }
467
468
        return strtolower(trim($comparisonString));
469
    }
470
471
    /**
472
     * returns the field prefix string for shipping addresses.
473
     *
474
     * @return string
0 ignored issues
show
Should the return type not be string|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...
475
     **/
476
    protected function baseClassLinkingToOrder()
477
    {
478
        if (is_a($this, Object::getCustomClass('BillingAddress'))) {
479
            return 'BillingAddress';
480
        } elseif (is_a($this, Object::getCustomClass('ShippingAddress'))) {
481
            return 'ShippingAddress';
482
        }
483
    }
484
485
    /**
486
     * returns the field prefix string for shipping addresses.
487
     *
488
     * @return string
489
     **/
490
    protected function fieldPrefix()
491
    {
492
        if ($this->baseClassLinkingToOrder() == Object::getCustomClass('BillingAddress')) {
493
            return '';
494
        } else {
495
            return 'Shipping';
496
        }
497
    }
498
499
    /**
500
     *@todo: are there times when the Shipping rather than the Billing address should be linked?
501
     * Copies the last address used by the member.
502
     *
503
     * @param object (Member) $member
504
     * @param bool            $write  - should the address be written
505
     *
506
     * @return OrderAddress | ShippingAddress | BillingAddress
507
     **/
508
    public function FillWithLastAddressFromMember(Member $member, $write = false)
509
    {
510
        $excludedFields = array('ID', 'OrderID');
511
        $fieldPrefix = $this->fieldPrefix();
512
        if ($member && $member->exists()) {
513
            $oldAddress = $member->previousOrderAddress($this->baseClassLinkingToOrder(), $this->ID);
514
            if ($oldAddress) {
515
                $fieldNameArray = array_keys($this->Config()->get('db')) + array_keys($this->Config()->get('has_one'));
516
                foreach ($fieldNameArray as $field) {
517
                    if (in_array($field, $excludedFields)) {
518
                        //do nothing
519
                    } elseif ($this->$field) {
520
                        //do nothing
521
                    } elseif (isset($oldAddress->$field)) {
522
                        $this->$field = $oldAddress->$field;
523
                    }
524
                }
525
            }
526
            //copy data from  member
527
            if (is_a($this, Object::getCustomClass('BillingAddress'))) {
528
                $this->Email = $member->Email;
0 ignored issues
show
The property Email does not exist on object<OrderAddress>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
529
            }
530
            $fieldNameArray = array('FirstName' => $fieldPrefix.'FirstName', 'Surname' => $fieldPrefix.'Surname');
531
            foreach ($fieldNameArray as $memberField => $fieldName) {
532
                //NOTE, we always override the Billing Address (which does not have a fieldPrefix)
533
                if (!$this->$fieldName || (is_a($this, Object::getCustomClass('BillingAddress')))) {
534
                    $this->$fieldName = $member->$memberField;
535
                }
536
            }
537
        }
538
        if ($write) {
539
            $this->write();
540
        }
541
542
        return $this;
543
    }
544
545
    /**
546
     * find the member associated with the current Order and address.
547
     *
548
     * @Note: this needs to be public to give DODS (extensions access to this)
549
     * @todo: can wre write $this->Order() instead????
550
     *
551
     * @return DataObject (Member) | Null
0 ignored issues
show
Should the return type not be DataObject|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...
552
     **/
553
    public function getMemberFromOrder()
554
    {
555
        if ($this->exists()) {
556
            if ($order = $this->Order()) {
0 ignored issues
show
Documentation Bug introduced by
The method Order does not exist on object<OrderAddress>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
557
                if ($order->exists()) {
558
                    if ($order->MemberID) {
559
                        return Member::get()->byID($order->MemberID);
560
                    }
561
                }
562
            }
563
        }
564
    }
565
566
    /**
567
     * make an address obsolete and include all the addresses that are identical.
568
     *
569
     * @param Member $member
0 ignored issues
show
Should the type for parameter $member not be null|Member?

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

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

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

Loading history...
570
     */
571
    public function MakeObsolete(Member $member = null)
572
    {
573
        $addresses = $member->previousOrderAddresses($this->baseClassLinkingToOrder(), $this->ID, $onlyLastRecord = false, $keepDoubles = true);
574
        $comparisonString = $this->comparisonString();
575
        if ($addresses->count()) {
576
            foreach ($addresses as $address) {
577
                if ($address->comparisonString() == $comparisonString) {
578
                    $address->Obsolete = 1;
579
                    $address->write();
580
                }
581
            }
582
        }
583
        $this->Obsolete = 1;
0 ignored issues
show
The property Obsolete does not exist on object<OrderAddress>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
584
        $this->write();
585
    }
586
587
    /**
588
     * standard SS method
589
     * We "hackishly" ensure that the OrderID is set to the right value.
590
     */
591
    public function onAfterWrite()
592
    {
593
        parent::onAfterWrite();
594
        if ($this->exists()) {
595
            $order = DataObject::get_one(
596
                'Order',
597
                array($this->ClassName.'ID' => $this->ID),
598
                $cacheDataObjectGetOne = false
599
            );
600
            if ($order && $order->ID != $this->OrderID) {
0 ignored issues
show
The property OrderID does not exist on object<OrderAddress>. 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...
601
                $this->OrderID = $order->ID;
0 ignored issues
show
The property OrderID does not exist on object<OrderAddress>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
602
                $this->write();
603
            }
604
        }
605
    }
606
607
    /**
608
     * returns the link that can be used to remove (make Obsolete) an address.
609
     *
610
     * @return string
611
     */
612
    public function RemoveLink()
613
    {
614
        return ShoppingCart_Controller::remove_address_link($this->ID, $this->ClassName);
615
    }
616
617
    /**
618
     * converts an address into JSON.
619
     *
620
     * @return string (JSON)
621
     */
622
    public function getJSONData()
623
    {
624
        return $this->JSONData();
625
    }
626
    public function JSONData()
627
    {
628
        $jsArray = array();
629
        if (!isset($fields)) {
0 ignored issues
show
The variable $fields seems only to be defined at a later point. As such the call to isset() seems to always evaluate to false.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
630
            $fields = $this->stat('db');
631
            $regionFieldName = $this->fieldPrefix().'RegionID';
632
            $fields[$regionFieldName] = $regionFieldName;
633
        }
634
        if ($fields) {
635
            foreach ($fields as $name => $field) {
0 ignored issues
show
The expression $fields of type array|integer|double|string|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
636
                $jsArray[$name] = $this->$name;
637
            }
638
        }
639
640
        return Convert::array2json($jsArray);
641
    }
642
643
    /**
644
     * returns the instance of EcommerceDBConfig.
645
     *
646
     * @return EcommerceDBConfig
647
     **/
648
    public function EcomConfig()
649
    {
650
        return EcommerceDBConfig::current_ecommerce_db_config();
651
    }
652
653
    /**
654
     * standard SS Method
655
     * saves the region code.
656
     */
657
    public function onBeforeWrite()
658
    {
659
        parent::onBeforeWrite();
660
        $fieldPrefix = $this->fieldPrefix();
661
        $idField = $fieldPrefix.'RegionID';
662
        if ($this->$idField) {
663
            $region = EcommerceRegion::get()->byID($this->$idField);
664
            if ($region) {
665
                $codeField = $fieldPrefix.'RegionCode';
666
                $this->$codeField = $region->Code;
667
            }
668
        }
669
    }
670
671
    public function debug()
672
    {
673
        return EcommerceTaskDebugCart::debug_object($this);
674
    }
675
}
676