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

OrderFormAddress::__construct()   F

Complexity

Conditions 33
Paths > 20000

Size

Total Lines 245
Code Lines 137

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 33
dl 0
loc 245
rs 2
c 0
b 0
f 0
eloc 137
nc 2193408
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This class is the form for editing the Order Addresses.
5
 * It is also used to link the order to a member.
6
 *
7
 * @authors: Nicolaas [at] Sunny Side Up .co.nz
8
 * @package: ecommerce
9
 * @sub-package: forms
10
 * @inspiration: Silverstripe Ltd, Jeremy
11
 **/
12
class OrderFormAddress extends Form
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...
13
{
14
    /**
15
     * @var bool
16
     */
17
    protected $debug = false;
18
19
    /**
20
     * @var array
21
     */
22
    protected $debugArray = array();
23
24
    /**
25
     * the member attached to the order
26
     * this is not always the same as the loggedInMember.
27
     *
28
     * @var object (Member)
29
     */
30
    protected $orderMember = null;
31
32
    /**
33
     * the logged in member, if any
34
     * this is not always the same as the orderMember.
35
     *
36
     * @var object (Member)
37
     */
38
    protected $loggedInMember = null;
39
40
    /**
41
     * ID of the member that has just been created.
42
     *
43
     * @var int
44
     */
45
    protected $newlyCreatedMemberID = 0;
46
47
    /**
48
     * ID of the member that has just been created.
49
     *
50
     * @var Order
51
     */
52
    protected $order = null;
53
54
    /**
55
     * @param Controller
56
     * @param string
57
     */
58
    public function __construct(Controller $controller, $name)
59
    {
60
61
        //set basics
62
        $requiredFields = array();
63
64
        //requirements
65
        Requirements::javascript('ecommerce/javascript/EcomOrderFormAddress.js'); // LEAVE HERE - NOT EASY TO INCLUDE VIA TEMPLATE
66
        if (EcommerceConfig::get('OrderAddress', 'use_separate_shipping_address')) {
67
            Requirements::javascript('ecommerce/javascript/EcomOrderFormShipping.js'); // LEAVE HERE - NOT EASY TO INCLUDE VIA TEMPLATE
68
        }
69
70
        //  ________________ 1) Order + Member + Address fields
71
72
73
        // define field lists ...
74
        $addressFieldsMember = FieldList::create();
75
        $addressFieldsBilling = FieldList::create();
76
        $addressFieldsShipping = null;
77
        $useShippingAddressField = null;
78
        $shippingAddressFirst = EcommerceConfig::get('OrderFormAddress', 'shipping_address_first');
79
80
        //find member
81
        $this->order = ShoppingCart::current_order();
82
        $this->orderMember = $this->order->CreateOrReturnExistingMember(false);
83
        $this->loggedInMember = Member::currentUser();
84
85
        //strange security situation...
86
        if ($this->orderMember->exists() && $this->loggedInMember) {
87
            if ($this->orderMember->ID != $this->loggedInMember->ID) {
88
                if (!$this->loggedInMember->IsShopAdmin()) {
89
                    $this->loggedInMember->logOut();
90
                }
91
            }
92
        }
93
94
        //member fields
95
        if ($this->orderMember) {
96
            $memberFields = $this->orderMember->getEcommerceFields();
97
            $requiredFields = array_merge($requiredFields, $this->orderMember->getEcommerceRequiredFields());
98
            $addressFieldsMember->merge($memberFields);
99
        }
100
101
        //billing address field
102
        $billingAddress = $this->order->CreateOrReturnExistingAddress('BillingAddress');
103
        $billingAddressFields = $billingAddress->getFields($this->orderMember);
104
        $addressFieldsBilling->merge($billingAddressFields);
105
106
        $requiredFields = array_merge($requiredFields, $billingAddress->getRequiredFields());
107
108
        //HACK: move phone to member fields ..
109
        if ($addressFieldsMember) {
110
            if ($addressFieldsBilling) {
111
                if ($phoneField = $addressFieldsBilling->dataFieldByName('Phone')) {
112
                    $addressFieldsBilling->removeByName('Phone');
113
                    $addressFieldsMember->insertAfter('Email', $phoneField);
114
                }
115
            }
116
        }
117
118
119
        //shipping address field
120
121
        if (EcommerceConfig::get('OrderAddress', 'use_separate_shipping_address')) {
122
            //add the important CHECKBOX
123
            $useShippingAddressField = FieldList::create(
124
                HeaderField::create(
125
                    'HasShippingAddressHeader',
126
                    _t('OrderFormAddress.HAS_SHIPPING_ADDRESS_HEADER', 'Delivery Option'),
127
                    3
128
                )
129
            );
130
            if ($shippingAddressFirst) {
131
                $useShippingAddressField->push(
132
                    CheckboxField::create(
133
                        'DoNotUseShippingAddress',
134
                        _t('OrderForm.DO_NOT_USESHIPPINGADDRESS', 'Shipping and Billing Address are the same'),
135
                        1
136
                    )
137
                );
138
                $useShippingAddressField->push(
139
                    HiddenField::create('UseShippingAddress', 'UseShippingAddress', 0)
140
                );
141
            } else {
142
                $useShippingAddressField->push(
143
                    CheckboxField::create(
144
                        'UseShippingAddress',
145
                        _t('OrderForm.USESHIPPINGADDRESS', 'Use separate shipping address')
146
                    )
147
                );
148
            }
149
150
            $addressFieldsShipping = FieldList::create();
151
152
            //$addressFieldsShipping->merge($useShippingAddressField);
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% 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...
153
            //now we can add the shipping fields
154
            $shippingAddress = $this->order->CreateOrReturnExistingAddress('ShippingAddress');
155
            $shippingAddressFields = $shippingAddress->getFields($this->orderMember);
156
            $requiredFields = array_merge($requiredFields, $shippingAddress->getRequiredFields());
157
            $addressFieldsShipping->merge($shippingAddressFields);
158
        }
159
160
        //create holder
161
        $allLeftFields = CompositeField::create();
162
        $allLeftFields->addExtraClass('leftOrder');
163
164
        //member fields holder
165
        $leftFieldsMember = CompositeField::create($addressFieldsMember);
166
        $leftFieldsMember->addExtraClass('leftOrderMember');
167
168
        //creating shipping fields holder
169
        $leftFieldsShipping = CompositeField::create($addressFieldsShipping);
170
        $leftFieldsShipping->addExtraClass('leftOrderShipping');
171
172
        //creating billing fields holder
173
        $leftFieldsBilling = CompositeField::create($addressFieldsBilling);
174
        $leftFieldsBilling->addExtraClass('leftOrderBilling');
175
176
        //adding member fields ...
177
        $allLeftFields->push($leftFieldsMember);
178
        if ($useShippingAddressField) {
179
            $leftFieldsShippingOptions = CompositeField::create($useShippingAddressField);
180
            $leftFieldsShippingOptions->addExtraClass('leftOrderShippingOptions');
181
            $allLeftFields->push($leftFieldsShippingOptions);
182
        }
183
        if ($shippingAddressFirst) {
184
            if ($addressFieldsShipping) {
185
                $allLeftFields->push($leftFieldsShipping);
186
            }
187
            $allLeftFields->push($leftFieldsBilling);
188
        } else {
189
            $allLeftFields->push($leftFieldsBilling);
190
            if ($addressFieldsShipping) {
191
                $allLeftFields->push($leftFieldsShipping);
192
            }
193
        }
194
195
        //  ________________  2) Log in / vs Create Account fields - RIGHT-HAND-SIDE fields
196
197
        $rightFields = CompositeField::create();
198
        $rightFields->addExtraClass('rightOrder');
199
        //to do: simplify
200
        if (EcommerceConfig::get('EcommerceRole', 'allow_customers_to_setup_accounts')) {
201
            if ($this->orderDoesNotHaveFullyOperationalMember()) {
202
                //general header
203
                if (!$this->loggedInMember) {
204
                    $rightFields->push(
205
                        //TODO: check EXACT link!!!
206
                        new LiteralField('MemberInfo', '<p class="message good">'._t('OrderForm.MEMBERINFO', 'If you already have an account then please').' <a href="Security/login/?BackURL=/'.urlencode(implode('/', $controller->getURLParams())).'">'._t('OrderForm.LOGIN', 'log in').'</a>.</p>')
207
                    );
208
                }
209
            } else {
210
                if ($this->loggedInMember) {
211
                    $rightFields->push(
212
                        new LiteralField(
213
                            'LoginNote',
214
                            '<p class="message good">'._t('Account.LOGGEDIN', 'You are logged in as ').
215
                            Convert::raw2xml($this->loggedInMember->FirstName).' '.
216
                            Convert::raw2xml($this->loggedInMember->Surname).
217
                            ' ('.Convert::raw2xml($this->loggedInMember->Email).').'.
218
                            ' <a href="/Security/logout/">'.
219
                            _t('Account.LOGOUTNOW', 'Log out?').
220
                            '</a>'.
221
                            '</p>'
222
                        )
223
                    );
224
                }
225
            }
226
            if ($this->orderMember->exists()) {
227
                if ($this->loggedInMember) {
228
                    if ($this->loggedInMember->ID !=  $this->orderMember->ID) {
229
                        $rightFields->push(
230
                            new LiteralField(
231
                                'OrderAddedTo',
232
                                '<p class="message good">'.
233
                                _t('Account.ORDERADDEDTO', 'Order will be added to ').
234
                                Convert::raw2xml($this->orderMember->FirstName).' '.
235
                                Convert::raw2xml($this->orderMember->Surname).' ('.
236
                                Convert::raw2xml($this->orderMember->Email).
237
                                ').</p>'
238
                            )
239
                        );
240
                    }
241
                }
242
            }
243
        }
244
245
        //  ________________  5) Put all the fields in one FieldList
246
247
        $fields = FieldList::create($rightFields, $allLeftFields);
248
249
        //  ________________  6) Actions and required fields creation + Final Form construction
250
251
        $nextButton = new FormAction('saveAddress', _t('OrderForm.NEXT', 'Next'));
252
        $nextButton->addExtraClass('next');
253
        $actions = FieldList::create($nextButton);
254
255
        $validator = OrderFormAddress_Validator::create($requiredFields);
256
257
        parent::__construct($controller, $name, $fields, $actions, $validator);
258
        $this->setAttribute('autocomplete', 'off');
259
        //extensions need to be set after __construct
260
        //extension point
261
        $this->extend('updateFields', $fields);
262
        $this->setFields($fields);
263
        $this->extend('updateActions', $actions);
264
        $this->setActions($actions);
265
        $this->extend('updateValidator', $validator);
266
        $this->setValidator($validator);
267
268
        //this needs to come after the extension calls
269
        foreach ($validator->getRequired() as $requiredField) {
270
            $field = $fields->dataFieldByName($requiredField);
271
            if ($field) {
272
                $field->addExtraClass('required');
273
            }
274
        }
275
276
        //  ________________  7)  Load saved data
277
278
        //we do this first so that Billing and Shipping Address can override this...
279
        if ($this->orderMember) {
280
            $this->loadDataFrom($this->orderMember);
281
        }
282
283
        if ($this->order) {
284
            $this->loadDataFrom($this->order);
285
            if ($billingAddress) {
286
                $this->loadDataFrom($billingAddress);
287
            }
288
            if (EcommerceConfig::get('OrderAddress', 'use_separate_shipping_address')) {
289
                if ($shippingAddress) {
290
                    $this->loadDataFrom($shippingAddress);
0 ignored issues
show
Bug introduced by
The variable $shippingAddress does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
291
                }
292
            }
293
        }
294
295
        //allow updating via decoration
296
        $oldData = Session::get("FormInfo.{$this->FormName()}.data");
297
        if ($oldData && (is_array($oldData) || is_object($oldData))) {
298
            $this->loadDataFrom($oldData);
0 ignored issues
show
Bug introduced by
It seems like $oldData can also be of type object; however, Form::loadDataFrom() does only seem to accept array|object<DataObject>, 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...
299
        }
300
301
        $this->extend('updateOrderFormAddress', $this);
302
    }
303
304
    /**
305
     * Is there a member that is fully operational?
306
     * - saved
307
     * - has password.
308
     *
309
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|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...
310
     */
311
    protected function orderHasFullyOperationalMember()
312
    {
313
        //orderMember is Created in __CONSTRUCT
314
        if ($this->orderMember) {
315
            if ($this->orderMember->exists()) {
316
                if ($this->orderMember->Password) {
317
                    return true;
318
                }
319
            }
320
        }
321
    }
322
323
    /**
324
     * Opposite of orderHasFullyOperationalMember method.
325
     *
326
     * @return bool
327
     */
328
    protected function orderDoesNotHaveFullyOperationalMember()
329
    {
330
        return $this->orderHasFullyOperationalMember() ? false : true;
331
    }
332
333
    /**
334
     * Process the items in the shopping cart from session,
335
     * creating a new {@link Order} record, and updating the
336
     * customer's details {@link Member} record.
337
     *
338
     * {@link Payment} instance is created, linked to the order,
339
     * and payment is processed {@link Payment::processPayment()}
340
     *
341
     * @param array       $data    Form request data submitted from OrderForm
342
     * @param Form        $form    Form object for this action
343
     * @param HTTPRequest $request Request object for this action
0 ignored issues
show
Documentation introduced by
Should the type for parameter $request not be SS_HTTPRequest?

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...
344
     */
345
    public function saveAddress(array $data, Form $form, SS_HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
346
    {
347
        Session::set('BillingEcommerceGeocodingFieldValue', empty($data['BillingEcommerceGeocodingField']) ? null : $data['BillingEcommerceGeocodingField']);
348
        Session::set('ShippingEcommerceGeocodingFieldValue', empty($data['ShippingEcommerceGeocodingField']) ? null : $data['ShippingEcommerceGeocodingField']);
349
350
        $data = Convert::raw2sql($data);
351
        //check for cart items
352
        if (!$this->order) {
353
            $form->sessionMessage(_t('OrderForm.ORDERNOTFOUND', 'Your order could not be found.'), 'bad');
354
            $this->controller->redirectBack();
355
356
            return false;
357
        }
358
        if ($this->order && ($this->order->TotalItems($recalculate = true) < 1)) {
359
            // WE DO NOT NEED THE THING BELOW BECAUSE IT IS ALREADY IN THE TEMPLATE AND IT CAN LEAD TO SHOWING ORDER WITH ITEMS AND MESSAGE
360
            $form->sessionMessage(_t('OrderForm.NOITEMSINCART', 'Please add some items to your cart.'), 'bad');
361
            $this->controller->redirectBack();
362
363
            return false;
364
        }
365
366
        //----------- START BY SAVING INTO ORDER
367
        $form->saveInto($this->order);
368
        //----------- --------------------------------
369
370
        //MEMBER
371
        $this->orderMember = $this->createOrFindMember($data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by \Convert::raw2sql($data) on line 350 can also be of type string; however, OrderFormAddress::createOrFindMember() does only seem to accept array, 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...
372
        if ($this->debug) {
373
            debug::log('debug array from OrderFormAddress:'.implode("\r\n<hr />", $this->debugArray));
374
        }
375
376
        if ($this->orderMember && is_object($this->orderMember)) {
377
            if ($this->memberShouldBeSaved($data)) {
0 ignored issues
show
Bug introduced by
It seems like $data defined by \Convert::raw2sql($data) on line 350 can also be of type string; however, OrderFormAddress::memberShouldBeSaved() does only seem to accept array, 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...
378
                $form->saveInto($this->orderMember);
379
                $password = $this->validPasswordHasBeenEntered($data);
380
                if ($password) {
381
                    $this->orderMember->changePassword($password);
0 ignored issues
show
Bug introduced by
It seems like $password defined by $this->validPasswordHasBeenEntered($data) on line 379 can also be of type array; however, Member::changePassword() does only seem to accept string, 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...
382
                }
383
                $this->orderMember->write();
384
            }
385
            if ($this->memberShouldBeLoggedIn($data)) {
0 ignored issues
show
Bug introduced by
It seems like $data defined by \Convert::raw2sql($data) on line 350 can also be of type string; however, OrderFormAddress::memberShouldBeLoggedIn() does only seem to accept array, 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...
386
                $this->orderMember->LogIn();
387
            }
388
            //this causes ERRORS ....
389
            $this->order->MemberID = $this->orderMember->ID;
0 ignored issues
show
Documentation introduced by
The property MemberID does not exist on object<Order>. 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...
390
            Session::set('Ecommerce_Member_For_Order', $this->orderMember->ID);
391
        }
392
393
        //BILLING ADDRESS
394
        if ($billingAddress = $this->order->CreateOrReturnExistingAddress('BillingAddress')) {
395
            $form->saveInto($billingAddress);
396
            // NOTE: write should return the new ID of the object
397
            $this->order->BillingAddressID = $billingAddress->write();
0 ignored issues
show
Documentation introduced by
The property BillingAddressID does not exist on object<Order>. 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...
398
        }
399
400
        // SHIPPING ADDRESS
401
        if (isset($data['UseShippingAddress'])) {
402
            if ($data['UseShippingAddress']) {
403
                if ($shippingAddress = $this->order->CreateOrReturnExistingAddress('ShippingAddress')) {
404
                    $form->saveInto($shippingAddress);
405
                    // NOTE: write should return the new ID of the object
406
                    $this->order->ShippingAddressID = $shippingAddress->write();
0 ignored issues
show
Documentation introduced by
The property ShippingAddressID does not exist on object<Order>. 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...
407
                }
408
            }
409
        }
410
411
        $this->extend('saveAddressExtension', $data, $form, $order, $this->orderMember);
412
413
        //SAVE ORDER
414
        $this->order->write();
415
416
        //----------------- CLEAR OLD DATA ------------------------------
417
        $this->clearSessionData(); //clears the stored session form data that might have been needed if validation failed
418
        //-----------------------------------------------
419
420
        $nextStepLink = CheckoutPage::find_next_step_link('orderformaddress');
421
        $this->controller->redirect($nextStepLink);
422
423
        return true;
424
    }
425
426
    /**
427
     * saves the form into session.
428
     *
429
     * @param array $data - data from form.
0 ignored issues
show
Bug introduced by
There is no parameter named $data. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
430
     */
431
    public function saveDataToSession()
432
    {
433
        $data = $this->getData();
434
        unset($data['AccountInfo']);
435
        unset($data['LoginDetails']);
436
        unset($data['LoggedInAsNote']);
437
        unset($data['PasswordCheck1']);
438
        unset($data['PasswordCheck2']);
439
        Session::set("FormInfo.{$this->FormName()}.data", $data);
0 ignored issues
show
Documentation introduced by
$data is of type array, but the function expects a string.

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...
440
    }
441
442
    /**
443
     * clear the form data (after the form has been submitted and processed).
444
     */
445
    public function clearSessionData()
446
    {
447
        $this->clearMessage();
448
        Session::set("FormInfo.{$this->FormName()}.data", null);
449
    }
450
451
    /**
452
     * Works out the most likely member for the order after submission of the form.
453
     * It returns a member if appropriate.
454
     * 1. does the order already have a member that is not a shop-admin - if so - DONE.
455
     * 2. shop allows creation of member? - if NOT return NULL
456
     * A. is the logged in member the shop admin placing an order on behalf of someone else?
457
     * A1. is the email entered different from the admin email?
458
     * A2. attach to other member as new one or existing one.
459
     * 3. can the entered data be used? - if
460
     * 4. is there no member logged in yet? - If there is one return null, member is already linked to order.
461
     * 5. find member from data entered (even if not logged in)
462
     * 6. At this stage, if we dont have a member, we will create one!
463
     * 7. We do one last check to see if we are allowed to create one.
464
     *
465
     * @param array - form data - should include $data[uniqueField....] - e.g. $data["Email"]
466
     *
467
     * @return Member | Null
468
     **/
469
    protected function createOrFindMember(array $data)
470
    {
471
        //get the best available from order.
472
        $this->orderMember = $this->order->CreateOrReturnExistingMember(false);
473
        $orderPlacedByShopAdmin = ($this->loggedInMember && $this->loggedInMember->IsShopAdmin()) ? true : false;
474
        //1. does the order already have a member
475
        if ($this->orderMember->exists() && !$orderPlacedByShopAdmin) {
476
            if ($this->debug) {
477
                $this->debugArray[] = '1. the order already has a member';
478
            }
479
        } else {
480
            //special shop admin situation:
481
            if ($orderPlacedByShopAdmin) {
482
                if ($this->debug) {
483
                    $this->debugArray[] = 'A1. shop admin places order ';
484
                }
485
                //2. does email match shopadmin email
486
                if ($newEmail = $this->enteredEmailAddressDoesNotMatchLoggedInUser($data)) {
487
                    $this->orderMember = null;
488
                    if ($this->debug) {
489
                        $this->debugArray[] = 'A2. email does not match shopadmin email - reset orderMember';
490
                    }
491
                    $this->orderMember = $this->anotherExistingMemberWithSameUniqueFieldValue($data);
492
                    if ($this->orderMember) {
493
                        if ($this->debug) {
494
                            $this->debugArray[] = 'A3. the other member already exists';
495
                        }
496
                    } elseif ($this->memberShouldBeCreated($data)) {
497
                        if ($this->debug) {
498
                            $this->debugArray[] = 'A4. No other member found - creating new one';
499
                        }
500
                        $this->orderMember = Member::create();
501
                        $this->orderMember->Email = Convert::raw2sql($newEmail);
502
                        $this->orderMember->write($forceCreation = true);
503
                        $this->newlyCreatedMemberID = $this->orderMember->ID;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->orderMember->ID can also be of type double. However, the property $newlyCreatedMemberID is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
504
                    }
505
                }
506
            } else {
507
                if ($this->debug) {
508
                    $this->debugArray[] = '2. shop allows creation of member';
509
                }
510
                $this->orderMember = null;
511
512
                //3. can the entered data be used?
513
                //member that will be added does not exist somewhere else.
514
                if ($this->uniqueMemberFieldCanBeUsed($data)) {
515
                    if ($this->debug) {
516
                        $this->debugArray[] = '3. can the entered data be used?';
517
                    }
518
                    // 4. is there no member logged in yet?
519
                    //no logged in member
520
                    if (!$this->loggedInMember) {
521
                        if ($this->debug) {
522
                            $this->debugArray[] = '4. is there no member logged in yet?';
523
                        }
524
                        //5. find member from data entered (even if not logged in)
525
                        //another member with the same email?
526
527
                        if ($this->debug) {
528
                            $this->debugArray[] = '5. find member from data entered (even if not logged in)';
529
                        }
530
                        $this->orderMember = $this->anotherExistingMemberWithSameUniqueFieldValue($data);
531
532
                        //6. At this stage, if we dont have a member, we will create one!
533
                        //in case we still dont have a member AND we should create a member for every customer, then we do this below...
534
                        if (!$this->orderMember) {
535
                            if ($this->debug) {
536
                                $this->debugArray[] = '6. No other member found';
537
                            }
538
                            // 7. We do one last check to see if we are allowed to create one
539
                            //are we allowed to create a member?
540
                            if ($this->memberShouldBeCreated($data)) {
541
                                if ($this->debug) {
542
                                    $this->debugArray[] = '7. We do one last check to see if we are allowed to create one. CREATE NEW MEMBER';
543
                                }
544
                                $this->orderMember = $this->order->CreateOrReturnExistingMember(false);
545
                                $this->orderMember->write($forceCreation = true);
546
                                //this is safe because it is memberShouldBeCreated ...
547
                                $this->newlyCreatedMemberID = $this->orderMember->ID;
548
                            }
549
                        }
550
                    }
551
                }
552
            }
553
        }
554
555
        return $this->orderMember;
556
    }
557
558
    /**
559
     * Should a new member be created?
560
     *
561
     * @Todo: explain why password needs to be more than three characters...
562
     * @todo: create class that checks if password is good enough
563
     *
564
     * @param array - form data - should include $data[uniqueField....] - e.g. $data["Email"]
565
     *
566
     * @return bool
567
     **/
568
    protected function memberShouldBeCreated(array $data)
569
    {
570
        //shop admin and
571
        //data entered does not match shop admin and
572
        //data entered does not match existing member...
573
        //TRUE!
574
        if ($this->loggedInMember && $this->loggedInMember->IsShopAdmin()) {
575
            if ($this->enteredEmailAddressDoesNotMatchLoggedInUser($data)) {
576
                if ($this->anotherExistingMemberWithSameUniqueFieldValue($data)) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return !$this->anotherEx...niqueFieldValue($data);.
Loading history...
577
                    return false;
578
                } else {
579
                    return true;
580
                }
581
            }
582
        }
583
        // already logged in or already created...
584
        // FALSE!
585
        elseif ($this->loggedInMember || $this->newlyCreatedMemberID) {
586
            return false;
587
        }
588
        // no other user exists with the email...
589
        // TRUE!
590
        else {
591
            if ($this->anotherExistingMemberWithSameUniqueFieldValue($data)) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return !$this->anotherEx...niqueFieldValue($data);.
Loading history...
592
                return false;
593
            } else {
594
                return true;
595
            }
596
        }
597
        //defaults to FALSE...
598
        return false;
599
    }
600
601
    /**
602
     * @param array - form data - should include $data[uniqueField....] - e.g. $data["Email"]
603
     *
604
     * @return bool
605
     **/
606
    protected function memberShouldBeSaved(array $data)
607
    {
608
609
        //new members always need to be saved
610
        $newMember = (
611
            $this->memberShouldBeCreated($data) ||
612
            $this->newlyCreatedMemberID
613
        ) ? true : false;
614
615
        // existing logged in members need to be saved if they are updateable
616
        // AND do not match someone else...
617
        $updateableMember = (
618
            $this->loggedInMember &&
619
            !$this->anotherExistingMemberWithSameUniqueFieldValue($data) &&
620
            EcommerceConfig::get('EcommerceRole', 'automatically_update_member_details')
621
        ) ? true : false;
622
623
        // logged in member is shop admin and members are updateable...
624
        $memberIsShopAdmin = (
625
            $this->loggedInMember &&
626
            $this->loggedInMember->IsShopAdmin() &&
627
            EcommerceConfig::get('EcommerceRole', 'automatically_update_member_details')
628
        ) ? true : false;
629
        if ($newMember || $updateableMember || $memberIsShopAdmin) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $newMember || $up... || $memberIsShopAdmin;.
Loading history...
630
            return true;
631
        }
632
633
        return false;
634
    }
635
636
    /**
637
     * returns TRUE if
638
     * - the member is not logged in
639
     * - the member is new AND
640
     * - the password is valid.
641
     *
642
     * @param array - form data - should include $data[uniqueField....] - e.g. $data["Email"]
643
     *
644
     * @return bool
645
     **/
646
    protected function memberShouldBeLoggedIn(array $data)
647
    {
648
        if (!$this->loggedInMember) {
649
            if ($this->newlyCreatedMemberID && $this->validPasswordHasBeenEntered($data)) {
650
                return true;
651
            }
652
        }
653
654
        return false;
655
    }
656
657
    /**
658
     * returns TRUE if
659
     * - there is no existing member with the same value in the unique field
660
     * - OR the member is not logged in.
661
     * - OR the member is a Shop Admin (we assume they are placing an order on behalf of someone else).
662
     * returns FALSE if
663
     * - the unique field already exists in another member
664
     * - AND the member being "tested" is already logged in...
665
     * in that case the logged in member tries to take on another identity.
666
     * If you are not logged BUT the the unique field is used by an existing member then we can still
667
     * use the field - we just CAN NOT log in the member.
668
     * This method needs to be public because it is used by the OrderForm_Validator (see below).
669
     *
670
     * @param array - form data - should include $data[uniqueField....] - e.g. $data["Email"]
671
     *
672
     * @return bool
673
     **/
674
    public function uniqueMemberFieldCanBeUsed(array $data)
675
    {
676
        if ($this->loggedInMember && $this->anotherExistingMemberWithSameUniqueFieldValue($data)) {
677
            //there is an exception for shop admins
678
            //who can place an order on behalve of a customer.
679
            if ($this->loggedInMember->IsShopAdmin()) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
680
                //REMOVED PART:
681
                //but NOT when the member placing the Order is the ShopAdmin
682
                //AND there is another member with the same credentials.
683
                //because in that case the ShopAdmin is not placing an order
684
                //on behalf of someone else.
685
                //that is,
686
                //if($this->orderMember->ID == $this->loggedInMember->ID) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% 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...
687
                //	return false;
688
                //}
689
            } else {
690
                return false;
691
            }
692
        }
693
694
        return true;
695
    }
696
697
    /**
698
     * returns existing member if it already exists and it is not the logged-in one.
699
     * Based on the unique field (email)).
700
     *
701
     * @param array - form data - should include $data[uniqueField....] - e.g. $data["Email"]
702
     **/
703
    protected function anotherExistingMemberWithSameUniqueFieldValue(array $data)
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...
704
    {
705
        $uniqueFieldName = Member::get_unique_identifier_field();
0 ignored issues
show
Deprecated Code introduced by
The method Member::get_unique_identifier_field() has been deprecated with message: 4.0 Use the "Member.unique_identifier_field" config setting instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
706
        //The check below covers both Scenario 3 and 4....
707
        if (isset($data[$uniqueFieldName])) {
708
            if ($this->loggedInMember) {
709
                $currentUserID = $this->loggedInMember->ID;
710
            } else {
711
                $currentUserID = 0;
712
            }
713
            $uniqueFieldValue = $data[$uniqueFieldName];
714
            //no need to convert raw2sql as this has already been done.
715
            return Member::get()
716
                ->filter(
717
                    array(
718
                        $uniqueFieldName => $uniqueFieldValue,
719
                    )
720
                )
721
                ->exclude(
722
                    array(
723
                        'ID' => $currentUserID,
724
                    )
725
                )
726
                ->First();
727
        }
728
        user_error('No email data was set, suspicious transaction', E_USER_WARNING);
729
730
        return;
731
    }
732
733
    /**
734
     * returns the email if
735
     * - user is logged in already
736
     * - user's email in DB does not match email entered.
737
     *
738
     * @param array
739
     *
740
     * @return string | false
741
     */
742
    protected function enteredEmailAddressDoesNotMatchLoggedInUser($data)
743
    {
744
        if ($this->loggedInMember) {
745
            $DBUniqueFieldName = $this->loggedInMember->Email;
746
            if ($DBUniqueFieldName) {
747
                $uniqueFieldName = Member::get_unique_identifier_field();
0 ignored issues
show
Deprecated Code introduced by
The method Member::get_unique_identifier_field() has been deprecated with message: 4.0 Use the "Member.unique_identifier_field" config setting instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
748
                if (isset($data[$uniqueFieldName])) {
749
                    $enteredUniqueFieldName = $data[$uniqueFieldName];
750
                    if ($enteredUniqueFieldName) {
751
                        if ($DBUniqueFieldName != $enteredUniqueFieldName) {
752
                            return $enteredUniqueFieldName;
753
                        }
754
                    }
755
                }
756
            }
757
        }
758
759
        return false;
760
    }
761
762
    /**
763
     * Check if the password is good enough.
764
     *
765
     * @param data (from form)
766
     *
767
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be array|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...
768
     */
769
    protected function validPasswordHasBeenEntered($data)
770
    {
771
        return ShopAccountForm_PasswordValidator::clean_password($data);
772
    }
773
}
774