Completed
Push — master ( d3dbf3...06543c )
by Jason
04:09
created

FoxyStripeController::parseOrderCustomer()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 25
ccs 0
cts 14
cp 0
rs 9.4888
c 0
b 0
f 0
cc 5
nc 4
nop 2
crap 30
1
<?php
2
3
namespace Dynamic\FoxyStripe\Controller;
4
5
use Dynamic\FoxyStripe\Model\FoxyCart;
6
use Dynamic\FoxyStripe\Model\FoxyStripeSetting;
7
use Dynamic\FoxyStripe\Model\OptionItem;
8
use Dynamic\FoxyStripe\Model\Order;
9
use Dynamic\FoxyStripe\Model\OrderDetail;
10
use Dynamic\FoxyStripe\Page\ProductPage;
11
use SilverStripe\Security\Member;
12
use SilverStripe\Security\Security;
13
14
class FoxyStripeController extends \PageController
15
{
16
    /**
17
     *
18
     */
19
    const URLSEGMENT = 'foxystripe';
20
    /**
21
     * @var array
22
     */
23
    private static $allowed_actions = array(
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
24
        'index',
25
        'sso',
26
    );
27
28
    /**
29
     * @return string
30
     */
31
    public function getURLSegment()
32
    {
33
        return self::URLSEGMENT;
34
    }
35
36
    /**
37
     * @return string
38
     *
39
     * @throws \SilverStripe\ORM\ValidationException
40
     */
41
    public function index()
42
    {
43
        // handle POST from FoxyCart API transaction
44
        if ((isset($_POST['FoxyData']) or isset($_POST['FoxySubscriptionData']))) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
45
            $FoxyData_encrypted = (isset($_POST['FoxyData'])) ?
46
                urldecode($_POST['FoxyData']) :
47
                urldecode($_POST['FoxySubscriptionData']);
48
            $FoxyData_decrypted = \rc4crypt::decrypt(FoxyCart::getStoreKey(), $FoxyData_encrypted);
0 ignored issues
show
Bug introduced by
It seems like Dynamic\FoxyStripe\Model\FoxyCart::getStoreKey() can also be of type false; however, parameter $pwd of rc4crypt::decrypt() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

48
            $FoxyData_decrypted = \rc4crypt::decrypt(/** @scrutinizer ignore-type */ FoxyCart::getStoreKey(), $FoxyData_encrypted);
Loading history...
49
50
            // parse the response and save the order
51
            self::handleDataFeed($FoxyData_encrypted, $FoxyData_decrypted);
0 ignored issues
show
Bug Best Practice introduced by
The method Dynamic\FoxyStripe\Contr...oller::handleDataFeed() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

51
            self::/** @scrutinizer ignore-call */ 
52
                  handleDataFeed($FoxyData_encrypted, $FoxyData_decrypted);
Loading history...
52
53
            // extend to allow for additional integrations with Datafeed
54
            $this->extend('addIntegrations', $FoxyData_encrypted);
55
56
            return 'foxy';
57
        } else {
58
            return 'No FoxyData or FoxySubscriptionData received.';
59
        }
60
    }
61
62
    /**
63
     * @param $encrypted
64
     * @param $decrypted
65
     *
66
     * @throws \SilverStripe\ORM\ValidationException
67
     */
68
    public function handleDataFeed($encrypted, $decrypted)
69
    {
70
        $orders = new \SimpleXMLElement($decrypted);
71
72
        // loop over each transaction to find FoxyCart Order ID
73
        foreach ($orders->transactions->transaction as $transaction) {
74
            // if FoxyCart order id, then parse order
75
            if (isset($transaction->id)) {
76
                ($order = Order::get()->filter('Order_ID', (int)$transaction->id)->First()) ?
0 ignored issues
show
Unused Code introduced by
The assignment to $order is dead and can be removed.
Loading history...
77
                    $order = Order::get()->filter('Order_ID', (int)$transaction->id)->First() :
78
                    $order = Order::create();
79
80
                // save base order info
81
                $order->Order_ID = (int)$transaction->id;
82
                $order->Response = urlencode($encrypted);
83
                $this->parseOrder($orders, $order);
0 ignored issues
show
Bug introduced by
$orders of type SimpleXMLElement is incompatible with the type array expected by parameter $transactions of Dynamic\FoxyStripe\Contr...ontroller::parseOrder(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

83
                $this->parseOrder(/** @scrutinizer ignore-type */ $orders, $order);
Loading history...
84
                $order->write();
85
            }
86
        }
87
    }
88
89
    /**
90
     * @param array $transactions
91
     * @param Order $order
92
     */
93
    public function parseOrder($transactions, $order)
94
    {
95
        $this->parseOrderInfo($transactions, $order);
96
        $this->parseOrderCustomer($transactions, $order);
97
        $this->parseOrderDetails($transactions, $order);
98
    }
99
100
    /**
101
     * @param array $orders
102
     * @param Order $transaction
103
     */
104
    public function parseOrderInfo($orders, $transaction)
105
    {
106
        foreach ($orders->transactions->transaction as $order) {
107
            // Record transaction data from FoxyCart Datafeed:
108
            $transaction->Store_ID = (int)$order->store_id;
0 ignored issues
show
Bug Best Practice introduced by
The property Store_ID does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
109
            $transaction->TransactionDate = (string)$order->transaction_date;
0 ignored issues
show
Documentation Bug introduced by
It seems like (string)$order->transaction_date of type string is incompatible with the declared type SilverStripe\ORM\FieldType\DBDatetime of property $TransactionDate.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
110
            $transaction->ProductTotal = (float)$order->product_total;
0 ignored issues
show
Documentation Bug introduced by
It seems like (double)$order->product_total of type double is incompatible with the declared type SilverStripe\ORM\FieldType\DBCurrency of property $ProductTotal.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
111
            $transaction->TaxTotal = (float)$order->tax_total;
0 ignored issues
show
Documentation Bug introduced by
It seems like (double)$order->tax_total of type double is incompatible with the declared type SilverStripe\ORM\FieldType\DBCurrency of property $TaxTotal.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
112
            $transaction->ShippingTotal = (float)$order->shipping_total;
0 ignored issues
show
Documentation Bug introduced by
It seems like (double)$order->shipping_total of type double is incompatible with the declared type SilverStripe\ORM\FieldType\DBCurrency of property $ShippingTotal.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
113
            $transaction->OrderTotal = (float)$order->order_total;
0 ignored issues
show
Documentation Bug introduced by
It seems like (double)$order->order_total of type double is incompatible with the declared type SilverStripe\ORM\FieldType\DBCurrency of property $OrderTotal.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
114
            $transaction->ReceiptURL = (string)$order->receipt_url;
0 ignored issues
show
Documentation Bug introduced by
It seems like (string)$order->receipt_url of type string is incompatible with the declared type SilverStripe\ORM\FieldType\DBVarchar of property $ReceiptURL.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
115
            $transaction->OrderStatus = (string)$order->status;
0 ignored issues
show
Documentation Bug introduced by
It seems like (string)$order->status of type string is incompatible with the declared type SilverStripe\ORM\FieldType\DBVarchar of property $OrderStatus.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
116
        }
117
    }
118
119
    /**
120
     * @param array $orders
121
     * @param Order $transaction
122
     * @throws \SilverStripe\ORM\ValidationException
123
     */
124
    public function parseOrderCustomer($orders, $transaction)
125
    {
126
        foreach ($orders->transactions->transaction as $order) {
127
            if (!isset($order->customer_email) || $order->is_anonymous != 0) {
128
                continue;
129
            }
130
131
            // if Customer is existing member, associate with current order
132
            if (Member::get()->filter('Email', $order->customer_email)->First()) {
133
                $customer = Member::get()->filter('Email', $order->customer_email)->First();
134
                /* todo: make sure local password is updated if changed on FoxyCart
135
                $this->updatePasswordFromData($customer, $order);
136
                */
137
            } else {
138
                // create new Member, set password info from FoxyCart
139
                $customer = Member::create();
140
                $customer->Customer_ID = (int)$order->customer_id;
141
                $customer->FirstName = (string)$order->customer_first_name;
142
                $customer->Surname = (string)$order->customer_last_name;
143
                $customer->Email = (string)$order->customer_email;
144
                $this->updatePasswordFromData($customer, $order);
145
            }
146
            $customer->write();
147
            // set Order MemberID
148
            $transaction->MemberID = $customer->ID;
149
        }
150
    }
151
152
    /**
153
     * Updates a customer's password. Sets password encryption to 'none' to avoid encryting it again.
154
     *
155
     * @param Member $customer
156
     * @param $order
157
     */
158
    public function updatePasswordFromData($customer, $order)
159
    {
160
        $password_encryption_algorithm = Security::config()->get('password_encryption_algorithm');
161
        Security::config()->update('password_encryption_algorithm', 'none');
162
163
        $customer->PasswordEncryption = 'none';
164
        $customer->Password = (string)$order->customer_password;
165
        $customer->Salt = (string)$order->customer_password_salt;
166
167
        Security::config()->update('password_encryption_algorithm', $password_encryption_algorithm);
168
    }
169
170
    /**
171
     * @param array $orders
172
     * @param Order $transaction
173
     */
174
    public function parseOrderDetails($orders, $transaction)
175
    {
176
        // remove previous OrderDetails so we don't end up with duplicates
177
        foreach ($transaction->Details() as $detail) {
178
            $detail->delete();
179
        }
180
181
        foreach ($orders->transactions->transaction as $order) {
182
            // Associate ProductPages, Options, Quanity with Order
183
            foreach ($order->transaction_details->transaction_detail as $product) {
184
                $this->orderDetailFromProduct($product, $transaction);
185
            }
186
        }
187
    }
188
189
    /**
190
     * @param $product
191
     * @param $transaction
192
     */
193
    public function orderDetailFromProduct($product, $transaction)
194
    {
195
        $OrderDetail = OrderDetail::create();
196
        $OrderDetail->Quantity = (int)$product->product_quantity;
197
        $OrderDetail->Price = (float)$product->product_price;
198
        // Find product via product_id custom variable
199
200
        foreach ($this->getTransactionOptions($product) as $productID) {
201
            $productPage = $this->getProductPage($product);
202
            $this->modifyOrderDetailPrice($productPage, $OrderDetail, $product);
203
            // associate with this order
204
            $OrderDetail->OrderID = $transaction->ID;
205
            // extend OrderDetail parsing, allowing for recording custom fields from FoxyCart
206
            $this->extend('handleOrderItem', $decrypted, $product, $OrderDetail);
207
            // write
208
            $OrderDetail->write();
209
        }
210
    }
211
212
    /**
213
     * @param $product
214
     * @return \Generator
215
     */
216
    public function getTransactionOptions($product)
217
    {
218
        foreach ($product->transaction_detail_options->transaction_detail_option as $productOption) {
219
            yield $productOption;
220
        }
221
    }
222
223
    /**
224
     * @param $product
225
     * @return bool|ProductPage
226
     */
227
    public function getProductPage($product)
228
    {
229
        foreach ($this->getTransactionOptions($product) as $productOptions) {
230
            if ($productOptions->product_option_name != 'product_id') {
231
                continue;
232
            }
233
234
            return ProductPage::get()
235
                ->filter('ID', (int) $productOptions->product_option_value)
236
                ->First();
237
        }
238
    }
239
240
    /**
241
     * @param bool|ProductPage $OrderProduct
242
     * @param OrderDetail $OrderDetail
243
     */
244
    public function modifyOrderDetailPrice($OrderProduct, $OrderDetail, $product)
245
    {
246
        if (!$OrderProduct) {
247
            return;
248
        }
249
250
        $OrderDetail->ProductID = $OrderProduct->ID;
251
252
        foreach ($this->getTransactionOptions($product) as $option) {
253
            $OptionItem = OptionItem::get()->filter(array(
254
                'ProductID' => (string)$OrderProduct->ID,
255
                'Title' => (string)$option->product_option_value
256
            ))->First();
257
258
            if (!$OptionItem) {
259
                continue;
260
            }
261
262
            $OrderDetail->OptionItems()->add($OptionItem);
263
            // modify product price
264
            if ($priceMod = $option->price_mod) {
265
                $OrderDetail->Price += $priceMod;
266
            }
267
        }
268
    }
269
270
    /**
271
     * Single Sign on integration with FoxyCart.
272
     */
273
    public function sso()
274
    {
275
276
        // GET variables from FoxyCart Request
277
        $fcsid = $this->request->getVar('fcsid');
278
        $timestampNew = strtotime('+30 days');
279
280
        // get current member if logged in. If not, create a 'fake' user with Customer_ID = 0
281
        // fake user will redirect to FC checkout, ask customer to log in
282
        // to do: consider a login/registration form here if not logged in
283
        if ($Member = Security::getCurrentUser()) {
0 ignored issues
show
Unused Code introduced by
The assignment to $Member is dead and can be removed.
Loading history...
284
            $Member = Security::getCurrentUser();
285
        } else {
286
            $Member = new Member();
287
            $Member->Customer_ID = 0;
288
        }
289
290
        $auth_token = sha1($Member->Customer_ID . '|' . $timestampNew . '|' . FoxyCart::getStoreKey());
0 ignored issues
show
Bug introduced by
Are you sure Dynamic\FoxyStripe\Model\FoxyCart::getStoreKey() of type SilverStripe\ORM\FieldType\DBVarchar|false can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

290
        $auth_token = sha1($Member->Customer_ID . '|' . $timestampNew . '|' . /** @scrutinizer ignore-type */ FoxyCart::getStoreKey());
Loading history...
291
292
        $config = FoxyStripeSetting::current_foxystripe_setting();
293
        if ($config->CustomSSL) {
0 ignored issues
show
Bug Best Practice introduced by
The property CustomSSL does not exist on Dynamic\FoxyStripe\Model\FoxyStripeSetting. Since you implemented __get, consider adding a @property annotation.
Loading history...
294
            $link = FoxyCart::getFoxyCartStoreName();
295
        } else {
296
            $link = FoxyCart::getFoxyCartStoreName() . '.foxycart.com';
0 ignored issues
show
Bug introduced by
Are you sure Dynamic\FoxyStripe\Model...:getFoxyCartStoreName() of type SilverStripe\ORM\FieldType\DBVarchar|false can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

296
            $link = /** @scrutinizer ignore-type */ FoxyCart::getFoxyCartStoreName() . '.foxycart.com';
Loading history...
297
        }
298
299
        $redirect_complete = 'https://'.$link.'.foxycart.com/checkout?fc_auth_token='.
0 ignored issues
show
Bug introduced by
Are you sure $link of type SilverStripe\ORM\FieldType\DBVarchar|false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

299
        $redirect_complete = 'https://'./** @scrutinizer ignore-type */ $link.'.foxycart.com/checkout?fc_auth_token='.
Loading history...
300
            $auth_token.'&fcsid='.$fcsid.'&fc_customer_id='.$Member->Customer_ID.'&timestamp='.$timestampNew;
301
302
        $this->redirect($redirect_complete);
303
    }
304
}
305