Completed
Push — master ( d4cc1f...efd390 )
by Mike
08:08
created

WC_Checkout::checkout_form_billing()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
eloc 2
nc 1
nop 0
1
<?php
2
3
if ( ! defined( 'ABSPATH' ) ) {
4
	exit; // Exit if accessed directly
5
}
6
7
/**
8
 * Checkout
9
 *
10
 * The WooCommerce checkout class handles the checkout process, collecting user data and processing the payment.
11
 *
12
 * @class 		WC_Checkout
13
 * @version		2.1.0
14
 * @package		WooCommerce/Classes
15
 * @category		Class
16
 * @author 		WooThemes
17
 */
18
class WC_Checkout {
19
20
	/** @var array Array of posted form data. */
21
	public $posted;
22
23
	/** @var array Array of fields to display on the checkout. */
24
	public $checkout_fields;
25
26
	/** @var bool Whether or not the user must create an account to checkout. */
27
	public $must_create_account;
28
29
	/** @var bool Whether or not signups are allowed. */
30
	public $enable_signup;
31
32
	/** @var object The shipping method being used. */
33
	private $shipping_method;
0 ignored issues
show
Unused Code introduced by
The property $shipping_method is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
34
35
	/** @var WC_Payment_Gateway|string The payment gateway being used. */
36
	private $payment_method;
37
38
	/** @var int ID of customer. */
39
	private $customer_id;
40
41
	/** @var array Where shipping_methods are stored. */
42
	public $shipping_methods;
43
44
	/**
45
	 * @var WC_Checkout The single instance of the class
46
	 * @since 2.1
47
	 */
48
	protected static $_instance = null;
49
50
	/** @var Bool */
51
	public $enable_guest_checkout;
52
53
	/**
54
	 * Main WC_Checkout Instance.
55
	 *
56
	 * Ensures only one instance of WC_Checkout is loaded or can be loaded.
57
	 *
58
	 * @since 2.1
59
	 * @static
60
	 * @return WC_Checkout Main instance
61
	 */
62
	public static function instance() {
63
		if ( is_null( self::$_instance ) )
64
			self::$_instance = new self();
65
		return self::$_instance;
66
	}
67
68
	/**
69
	 * Cloning is forbidden.
70
	 *
71
	 * @since 2.1
72
	 */
73
	public function __clone() {
74
		_doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'woocommerce' ), '2.1' );
75
	}
76
77
	/**
78
	 * Unserializing instances of this class is forbidden.
79
	 *
80
	 * @since 2.1
81
	 */
82
	public function __wakeup() {
83
		_doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'woocommerce' ), '2.1' );
84
	}
85
86
	/**
87
	 * Constructor for the checkout class. Hooks in methods and defines checkout fields.
88
	 *
89
	 * @access public
90
	 */
91
	public function __construct () {
92
		add_action( 'woocommerce_checkout_billing', array( $this,'checkout_form_billing' ) );
93
		add_action( 'woocommerce_checkout_shipping', array( $this,'checkout_form_shipping' ) );
94
95
		$this->enable_signup         = get_option( 'woocommerce_enable_signup_and_login_from_checkout' ) == 'yes' ? true : false;
96
		$this->enable_guest_checkout = get_option( 'woocommerce_enable_guest_checkout' ) == 'yes' ? true : false;
97
		$this->must_create_account   = $this->enable_guest_checkout || is_user_logged_in() ? false : true;
98
99
		// Define all Checkout fields
100
		$this->checkout_fields['billing'] 	= WC()->countries->get_address_fields( $this->get_value( 'billing_country' ), 'billing_' );
101
		$this->checkout_fields['shipping'] 	= WC()->countries->get_address_fields( $this->get_value( 'shipping_country' ), 'shipping_' );
102
103 View Code Duplication
		if ( get_option( 'woocommerce_registration_generate_username' ) == 'no' ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
104
			$this->checkout_fields['account']['account_username'] = array(
105
				'type' 			=> 'text',
106
				'label' 		=> __( 'Account username', 'woocommerce' ),
107
				'required'      => true,
108
				'placeholder' 	=> _x( 'Username', 'placeholder', 'woocommerce' )
109
			);
110
		}
111
112 View Code Duplication
		if ( get_option( 'woocommerce_registration_generate_password' ) == 'no' ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
113
			$this->checkout_fields['account']['account_password'] = array(
114
				'type' 				=> 'password',
115
				'label' 			=> __( 'Account password', 'woocommerce' ),
116
				'required'          => true,
117
				'placeholder' 		=> _x( 'Password', 'placeholder', 'woocommerce' )
118
			);
119
		}
120
121
		$this->checkout_fields['order']	= array(
122
			'order_comments' => array(
123
				'type' => 'textarea',
124
				'class' => array('notes'),
125
				'label' => __( 'Order Notes', 'woocommerce' ),
126
				'placeholder' => _x('Notes about your order, e.g. special notes for delivery.', 'placeholder', 'woocommerce')
127
			)
128
		);
129
130
		$this->checkout_fields = apply_filters( 'woocommerce_checkout_fields', $this->checkout_fields );
131
132
		do_action( 'woocommerce_checkout_init', $this );
133
	}
134
135
	/**
136
	 * Checkout process.
137
	 */
138
	public function check_cart_items() {
139
		// When we process the checkout, lets ensure cart items are rechecked to prevent checkout
140
		do_action('woocommerce_check_cart_items');
141
	}
142
143
	/**
144
	 * Output the billing information form.
145
	 */
146
	public function checkout_form_billing() {
147
		wc_get_template( 'checkout/form-billing.php', array( 'checkout' => $this ) );
148
	}
149
150
	/**
151
	 * Output the shipping information form.
152
	 */
153
	public function checkout_form_shipping() {
154
		wc_get_template( 'checkout/form-shipping.php', array( 'checkout' => $this ) );
155
	}
156
157
	/**
158
	 * Create an order. Error codes:
159
	 * 		520 - Cannot insert order into the database.
160
	 * 		521 - Cannot get order after creation.
161
	 * 		522 - Cannot update order.
162
	 * 		525 - Cannot create line item.
163
	 * 		526 - Cannot create fee item.
164
	 * 		527 - Cannot create shipping item.
165
	 * 		528 - Cannot create tax item.
166
	 * 		529 - Cannot create coupon item.
167
	 * @throws Exception
168
	 * @return int|WP_ERROR
169
	 */
170
	public function create_order() {
171
		global $wpdb;
172
173
		// Give plugins the opportunity to create an order themselves
174
		if ( $order_id = apply_filters( 'woocommerce_create_order', null, $this ) ) {
175
			return $order_id;
176
		}
177
178
		try {
179
			// Start transaction if available
180
			wc_transaction_query( 'start' );
181
182
			// Insert or update the post data
183
			$order_id  = absint( WC()->session->order_awaiting_payment );
184
			$cart_hash = md5( json_encode( wc_clean( WC()->cart->get_cart_for_session() ) ) . WC()->cart->total );
185
186
			/**
187
			 * If there is an order pending payment, we can resume it here so
188
			 * long as it has not changed. If the order has changed, i.e.
189
			 * different items or cost, create a new order. We use a hash to
190
			 * detect changes which is based on cart items + order total.
191
			 */
192
			if ( $order_id && ( $order = wc_get_order( $order_id ) ) && $order->has_cart_hash( $cart_hash ) && $order->has_status( array( 'pending', 'failed' ) ) ) {
193
				// Action for 3rd parties.
194
				do_action( 'woocommerce_resume_order', $order_id );
195
196
				// Remove all items - we will re-add them later.
197
				$order->remove_order_items();
198
			/**
199
			 * Not resuming - lets create a new order object.
200
			 */
201
			} else {
202
				$order = new WC_Order();
203
			}
204
205
			$order->set_created_via( 'checkout' );
206
			$order->set_cart_hash( $cart_hash );
207
			$order->set_customer_id( $this->customer_id );
208
			$order->set_currency( get_woocommerce_currency() );
209
			$order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
210
			$order->set_customer_ip_address( WC_Geolocation::get_ip_address() );
211
			$order->set_customer_user_agent( wc_get_user_agent() );
212
			$order->set_customer_note( isset( $this->posted['order_comments'] ) ? $this->posted['order_comments'] : '' );
213
			$order->set_payment_method( $this->payment_method );
214
			$order->set_shipping_total( WC()->cart->shipping_total );
215
			$order->set_discount_total( WC()->cart->get_cart_discount_total() );
216
			$order->set_discount_tax( WC()->cart->get_cart_discount_tax_total() );
217
			$order->set_cart_tax( WC()->cart->tax_total );
218
			$order->set_shipping_tax( WC()->cart->shipping_tax_total );
219
			$order->set_total( WC()->cart->total );
220
221
			// Billing and shipping addresses
222
			if ( $address_keys = array_merge( array_keys( $this->checkout_fields['billing'] ), array_keys( $this->checkout_fields['shipping'] ) ) ) {
223
				foreach ( $address_keys as $key ) {
224
					if ( is_callable( array( $order, "set_{$key}" ) ) ) {
225
						$order->{"set_{$key}"}( $this->get_posted_address_data( str_replace( array( 'billing_', 'shipping_' ), '', $key ), strstr( $key, 'billing_' ) ? 'billing' : 'shipping' ) );
226
					}
227
				}
228
			}
229
230
			// Add line items.
231
			foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
232
				$product = $values['data'];
233
				$item    = new WC_Order_Item_Product( array(
0 ignored issues
show
Documentation introduced by
array('qty' => $values['...alues['line_tax_data']) is of type array<string,?,{"qty":"?..._tax":"?","taxes":"?"}>, but the function expects a 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...
234
					'qty'          => $values['quantity'],
235
					'name'         => $product ? $product->get_title() : '',
236
					'tax_class'    => $product ? $product->get_tax_class() : '',
237
					'product_id'   => $product ? $product->get_id() : '',
238
					'variation_id' => $product && isset( $product->variation_id ) ? $product->variation_id : 0,
239
					'variation'    => $values['variation'],
240
					'subtotal'     => $values['line_subtotal'],
241
					'total'        => $values['line_total'],
242
					'subtotal_tax' => $values['line_subtotal_tax'],
243
					'total_tax'    => $values['line_tax'],
244
					'taxes'        => $values['line_tax_data'],
245
				) );
246
247
				// Handle backorders @todo improve how these are handled/stored
248 View Code Duplication
				if ( $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
249
					$item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $values['quantity'] - max( 0, $product->get_total_stock() ), true );
250
				}
251
252
				// Set this to pass to legacy actions @todo remove in future release
253
				$item->legacy_values        = $values;
254
				$item->legacy_cart_item_key = $cart_item_key;
255
256
				$order->add_item( $item );
257
			}
258
259
			// Add fees
260
			foreach ( WC()->cart->get_fees() as $fee_key => $fee ) {
261
				$item = new WC_Order_Item_Fee( array(
0 ignored issues
show
Documentation introduced by
array('name' => $fee->na...al' => $fee->tax_data)) is of type array<string,?,{"name":"...?,{\"total\":\"?\"}>"}>, but the function expects a 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...
262
					'name'      => $fee->name,
263
					'tax_class' => $fee->taxable ? $fee->tax_class : 0,
264
					'total'     => $fee->amount,
265
					'total_tax' => $fee->tax,
266
					'taxes'     => array(
267
						'total' => $fee->tax_data,
268
					),
269
				) );
270
271
				// Set this to pass to legacy actions @todo remove in future release
272
				$item->legacy_fee     = $fee;
273
				$item->legacy_fee_key = $fee_key;
274
275
				$order->add_item( $item );
276
			}
277
278
			// Store shipping for all packages
279
			foreach ( WC()->shipping->get_packages() as $package_key => $package ) {
280
				if ( isset( $package['rates'][ $this->shipping_methods[ $package_key ] ] ) ) {
281
					$shipping_rate = $package['rates'][ $this->shipping_methods[ $package_key ] ];
282
					$item = new WC_Order_Item_Shipping( array(
0 ignored issues
show
Documentation introduced by
array('method_title' => ..._rate->get_meta_data()) is of type array<string,?,{"method_...":"?","meta_data":"?"}>, but the function expects a 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...
283
						'method_title' => $shipping_rate->label,
284
						'method_id'    => $shipping_rate->id,
285
						'total'        => wc_format_decimal( $shipping_rate->cost ),
286
						'taxes'        => $shipping_rate->taxes,
287
						'meta_data'    => $shipping_rate->get_meta_data(),
288
					) );
289
290
					// Set this to pass to legacy actions @todo remove in future release
291
					$item->legacy_package_key = $package_key;
292
293
					$order->add_item( $item );
294
				}
295
			}
296
297
			// Store tax rows
298
			foreach ( array_keys( WC()->cart->taxes + WC()->cart->shipping_taxes ) as $tax_rate_id ) {
299
				if ( $tax_rate_id && $tax_rate_id !== apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) ) {
300
					$order->add_item( new WC_Order_Item_Tax( array(
0 ignored issues
show
Documentation introduced by
array('rate_id' => $tax_...compound($tax_rate_id)) is of type array<string,integer|str...,"compound":"boolean"}>, but the function expects a 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...
301
						'rate_id'            => $tax_rate_id,
302
						'tax_total'          => WC()->cart->get_tax_amount( $tax_rate_id ),
303
						'shipping_tax_total' => WC()->cart->get_shipping_tax_amount( $tax_rate_id ),
304
						'rate_code'          => WC_Tax::get_rate_code( $tax_rate_id ),
305
						'label'              => WC_Tax::get_rate_label( $tax_rate_id ),
306
						'compound'           => WC_Tax::is_compound( $tax_rate_id ),
307
					) ) );
308
				}
309
			}
310
311
			// Store coupons
312
			foreach ( WC()->cart->get_coupons() as $code => $coupon ) {
313
				$item = new WC_Order_Item_Coupon( array(
0 ignored issues
show
Documentation introduced by
array('code' => $code, '...ount_tax_amount($code)) is of type array<string,integer|str...iscount_tax":"double"}>, but the function expects a 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...
314
					'code'         => $code,
315
					'discount'     => WC()->cart->get_coupon_discount_amount( $code ),
316
					'discount_tax' => WC()->cart->get_coupon_discount_tax_amount( $code ),
317
				) );
318
				$order->add_item( $item );
319
			}
320
321
			// Save the order
322
			$order_id = $order->save();
323
324
			$customer = new WC_Customer( $this->customer_id );
0 ignored issues
show
Unused Code introduced by
$customer 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...
325
326
			// Update user meta
327
			$this->update_customer_data();
328
329
			// Let plugins add their own meta data
330
			do_action( 'woocommerce_checkout_update_order_meta', $order_id, $this->posted );
331
332
			// If we got here, the order was created without problems!
333
			wc_transaction_query( 'commit' );
334
335
		} catch ( Exception $e ) {
336
			// There was an error adding order data!
337
			wc_transaction_query( 'rollback' );
338
			return new WP_Error( 'checkout-error', $e->getMessage() );
339
		}
340
341
		return $order_id;
342
	}
343
344
	/**
345
	 * Store customer data to meta.
346
	 * @since 2.7.0
347
	 */
348
	protected function update_customer_data() {
349
		if ( $this->customer_id ) {
350
			if ( apply_filters( 'woocommerce_checkout_update_customer_data', true, $this ) ) {
351
				$customer = new WC_Customer( $this->customer_id );
352
353 View Code Duplication
				if ( $keys = array_keys( $this->checkout_fields['billing'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
354
					foreach ( $keys as $key ) {
355
						if ( is_callable( array( $customer, "set_{$key}" ) ) ) {
356
							$customer->{"set_{$key}"}( $this->get_posted_address_data( str_replace( array( 'billing_', 'shipping_' ), '', $key ) ) );
357
						}
358
					}
359
				}
360
361 View Code Duplication
				if ( WC()->cart->needs_shipping() && ( $keys = array_keys( $this->checkout_fields['shipping'] ) ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
362
					foreach ( $keys as $key ) {
363
						if ( is_callable( array( $customer, "set_{$key}" ) ) ) {
364
							$customer->{"set_{$key}"}( $this->get_posted_address_data( str_replace( array( 'billing_', 'shipping_' ), '', $key ), 'shipping' ) );
365
						}
366
					}
367
				}
368
369
				$customer->save();
370
			}
371
			do_action( 'woocommerce_checkout_update_user_meta', $this->customer_id, $this->posted );
372
		}
373
	}
374
375
	/**
376
	 * Process the checkout after the confirm order button is pressed.
377
	 */
378
	public function process_checkout() {
379
		try {
380
			if ( empty( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'woocommerce-process_checkout' ) ) {
381
				WC()->session->set( 'refresh_totals', true );
382
				throw new Exception( __( 'We were unable to process your order, please try again.', 'woocommerce' ) );
383
			}
384
385
			if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
386
				define( 'WOOCOMMERCE_CHECKOUT', true );
387
			}
388
389
			// Prevent timeout
390
			@set_time_limit(0);
391
392
			do_action( 'woocommerce_before_checkout_process' );
393
394
			if ( WC()->cart->is_empty() ) {
395
				throw new Exception( sprintf( __( 'Sorry, your session has expired. <a href="%s" class="wc-backward">Return to shop</a>', 'woocommerce' ), esc_url( wc_get_page_permalink( 'shop' ) ) ) );
396
			}
397
398
			do_action( 'woocommerce_checkout_process' );
399
400
			// Checkout fields (not defined in checkout_fields)
401
			$this->posted['terms']                     = isset( $_POST['terms'] ) ? 1 : 0;
402
			$this->posted['createaccount']             = isset( $_POST['createaccount'] ) && ! empty( $_POST['createaccount'] ) ? 1 : 0;
403
			$this->posted['payment_method']            = isset( $_POST['payment_method'] ) ? stripslashes( $_POST['payment_method'] ) : '';
404
			$this->posted['shipping_method']           = isset( $_POST['shipping_method'] ) ? $_POST['shipping_method'] : '';
405
			$this->posted['ship_to_different_address'] = ! empty( $_POST['ship_to_different_address'] );
406
407
			if ( isset( $_POST['shiptobilling'] ) ) {
408
				_deprecated_argument( 'WC_Checkout::process_checkout()', '2.1', 'The "shiptobilling" field is deprecated. The template files are out of date' );
409
410
				$this->posted['ship_to_different_address'] = $_POST['shiptobilling'] ? false : true;
411
			}
412
413
			// Ship to billing only option
414
			if ( wc_ship_to_billing_address_only() ) {
415
				$this->posted['ship_to_different_address']  = false;
416
			}
417
418
			// Update customer shipping and payment method to posted method
419
			$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
420
421
			if ( isset( $this->posted['shipping_method'] ) && is_array( $this->posted['shipping_method'] ) ) {
422
				foreach ( $this->posted['shipping_method'] as $i => $value ) {
423
					$chosen_shipping_methods[ $i ] = wc_clean( $value );
424
				}
425
			}
426
427
			WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
428
			WC()->session->set( 'chosen_payment_method', $this->posted['payment_method'] );
429
430
			// Note if we skip shipping
431
			$skipped_shipping = false;
432
433
			// Get posted checkout_fields and do validation
434
			foreach ( $this->checkout_fields as $fieldset_key => $fieldset ) {
435
436
				// Skip shipping if not needed
437
				if ( $fieldset_key == 'shipping' && ( $this->posted['ship_to_different_address'] == false || ! WC()->cart->needs_shipping_address() ) ) {
438
					$skipped_shipping = true;
439
					continue;
440
				}
441
442
				// Skip account if not needed
443
				if ( 'account' === $fieldset_key && ( is_user_logged_in() || ( false === $this->must_create_account && empty( $this->posted['createaccount'] ) ) ) ) {
444
					continue;
445
				}
446
447
				foreach ( $fieldset as $key => $field ) {
448
449
					if ( ! isset( $field['type'] ) ) {
450
						$field['type'] = 'text';
451
					}
452
453
					// Get Value
454
					switch ( $field['type'] ) {
455
						case "checkbox" :
456
							$this->posted[ $key ] = isset( $_POST[ $key ] ) ? 1 : 0;
457
						break;
458
						case "multiselect" :
459
							$this->posted[ $key ] = isset( $_POST[ $key ] ) ? implode( ', ', array_map( 'wc_clean', $_POST[ $key ] ) ) : '';
460
						break;
461
						case "textarea" :
462
							$this->posted[ $key ] = isset( $_POST[ $key ] ) ? wp_strip_all_tags( wp_check_invalid_utf8( stripslashes( $_POST[ $key ] ) ) ) : '';
463
						break;
464
						default :
465
							$this->posted[ $key ] = isset( $_POST[ $key ] ) ? ( is_array( $_POST[ $key ] ) ? array_map( 'wc_clean', $_POST[ $key ] ) : wc_clean( $_POST[ $key ] ) ) : '';
466
						break;
467
					}
468
469
					// Hooks to allow modification of value
470
					$this->posted[ $key ] = apply_filters( 'woocommerce_process_checkout_' . sanitize_title( $field['type'] ) . '_field', $this->posted[ $key ] );
471
					$this->posted[ $key ] = apply_filters( 'woocommerce_process_checkout_field_' . $key, $this->posted[ $key ] );
472
473
					// Validation: Required fields
474
					if ( isset( $field['required'] ) && $field['required'] && ( ! isset( $this->posted[ $key ] ) || "" === $this->posted[ $key ] ) ) {
475
						switch ( $fieldset_key ) {
476
							case 'shipping' :
477
								$field_label = sprintf( _x( 'Shipping %s', 'Shipping FIELDNAME', 'woocommerce' ), $field['label'] );
478
							break;
479
							case 'billing' :
480
								$field_label = sprintf( _x( 'Billing %s', 'Billing FIELDNAME', 'woocommerce' ), $field['label'] );
481
							break;
482
							default :
483
								$field_label = $field['label'];
484
							break;
485
						}
486
						wc_add_notice( apply_filters( 'woocommerce_checkout_required_field_notice', sprintf( _x( '%s is a required field.', 'FIELDNAME is a required field.', 'woocommerce' ), '<strong>' . $field_label . '</strong>' ), $field_label ), 'error' );
487
					}
488
489
					if ( ! empty( $this->posted[ $key ] ) ) {
490
491
						// Validation rules
492
						if ( ! empty( $field['validate'] ) && is_array( $field['validate'] ) ) {
493
							foreach ( $field['validate'] as $rule ) {
494
								switch ( $rule ) {
495
									case 'postcode' :
496
										$this->posted[ $key ] = strtoupper( str_replace( ' ', '', $this->posted[ $key ] ) );
497
498
										if ( ! WC_Validation::is_postcode( $this->posted[ $key ], $_POST[ $fieldset_key . '_country' ] ) ) :
499
											wc_add_notice( __( 'Please enter a valid postcode/ZIP.', 'woocommerce' ), 'error' );
500
										else :
501
											$this->posted[ $key ] = wc_format_postcode( $this->posted[ $key ], $_POST[ $fieldset_key . '_country' ] );
502
										endif;
503
									break;
504 View Code Duplication
									case 'phone' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
505
										$this->posted[ $key ] = wc_format_phone_number( $this->posted[ $key ] );
506
507
										if ( ! WC_Validation::is_phone( $this->posted[ $key ] ) )
508
											wc_add_notice( '<strong>' . $field['label'] . '</strong> ' . __( 'is not a valid phone number.', 'woocommerce' ), 'error' );
509
									break;
510 View Code Duplication
									case 'email' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
511
										$this->posted[ $key ] = strtolower( $this->posted[ $key ] );
512
513
										if ( ! is_email( $this->posted[ $key ] ) )
514
											wc_add_notice( '<strong>' . $field['label'] . '</strong> ' . __( 'is not a valid email address.', 'woocommerce' ), 'error' );
515
									break;
516
									case 'state' :
517
										// Get valid states
518
										$valid_states = WC()->countries->get_states( isset( $_POST[ $fieldset_key . '_country' ] ) ? $_POST[ $fieldset_key . '_country' ] : ( 'billing' === $fieldset_key ? WC()->customer->get_country() : WC()->customer->get_shipping_country() ) );
519
520
										if ( ! empty( $valid_states ) && is_array( $valid_states ) ) {
521
											$valid_state_values = array_flip( array_map( 'strtolower', $valid_states ) );
522
523
											// Convert value to key if set
524
											if ( isset( $valid_state_values[ strtolower( $this->posted[ $key ] ) ] ) ) {
525
												 $this->posted[ $key ] = $valid_state_values[ strtolower( $this->posted[ $key ] ) ];
526
											}
527
										}
528
529
										// Only validate if the country has specific state options
530
										if ( ! empty( $valid_states ) && is_array( $valid_states ) && sizeof( $valid_states ) > 0 ) {
531
											if ( ! in_array( $this->posted[ $key ], array_keys( $valid_states ) ) ) {
532
												wc_add_notice( '<strong>' . $field['label'] . '</strong> ' . __( 'is not valid. Please enter one of the following:', 'woocommerce' ) . ' ' . implode( ', ', $valid_states ), 'error' );
533
											}
534
										}
535
									break;
536
								}
537
							}
538
						}
539
					}
540
				}
541
			}
542
543
			// Update customer location to posted location so we can correctly check available shipping methods
544
			if ( isset( $this->posted['billing_country'] ) ) {
545
				WC()->customer->set_billing_country( $this->posted['billing_country'] );
546
			}
547
			if ( isset( $this->posted['billing_state'] ) ) {
548
				WC()->customer->set_billing_state( $this->posted['billing_state'] );
549
			}
550
			if ( isset( $this->posted['billing_postcode'] ) ) {
551
				WC()->customer->set_billing_postcode( $this->posted['billing_postcode'] );
552
			}
553
554
			// Shipping Information
555
			if ( ! $skipped_shipping ) {
556
557
				// Update customer location to posted location so we can correctly check available shipping methods
558
				if ( isset( $this->posted['shipping_country'] ) ) {
559
					WC()->customer->set_shipping_country( $this->posted['shipping_country'] );
560
				}
561
				if ( isset( $this->posted['shipping_state'] ) ) {
562
					WC()->customer->set_shipping_state( $this->posted['shipping_state'] );
563
				}
564
				if ( isset( $this->posted['shipping_postcode'] ) ) {
565
					WC()->customer->set_shipping_postcode( $this->posted['shipping_postcode'] );
566
				}
567
568
			} else {
569
570
				// Update customer location to posted location so we can correctly check available shipping methods
571
				if ( isset( $this->posted['billing_country'] ) ) {
572
					WC()->customer->set_shipping_country( $this->posted['billing_country'] );
573
				}
574
				if ( isset( $this->posted['billing_state'] ) ) {
575
					WC()->customer->set_shipping_state( $this->posted['billing_state'] );
576
				}
577
				if ( isset( $this->posted['billing_postcode'] ) ) {
578
					WC()->customer->set_shipping_postcode( $this->posted['billing_postcode'] );
579
				}
580
581
			}
582
583
			WC()->customer->save();
584
585
			// Update cart totals now we have customer address
586
			WC()->cart->calculate_totals();
587
588
			// Terms
589
			if ( ! isset( $_POST['woocommerce_checkout_update_totals'] ) && empty( $this->posted['terms'] ) && wc_get_page_id( 'terms' ) > 0 && apply_filters( 'woocommerce_checkout_show_terms', true ) ) {
590
				wc_add_notice( __( 'You must accept our Terms &amp; Conditions.', 'woocommerce' ), 'error' );
591
			}
592
593
			if ( WC()->cart->needs_shipping() ) {
594
				$shipping_country = WC()->customer->get_shipping_country();
595
596
				if ( empty( $shipping_country ) ) {
597
					wc_add_notice( __( 'Please enter an address to continue.', 'woocommerce' ), 'error' );
598
				} elseif ( ! in_array( WC()->customer->get_shipping_country(), array_keys( WC()->countries->get_shipping_countries() ) ) ) {
599
					wc_add_notice( sprintf( __( 'Unfortunately <strong>we do not ship %s</strong>. Please enter an alternative shipping address.', 'woocommerce' ), WC()->countries->shipping_to_prefix() . ' ' . WC()->customer->get_shipping_country() ), 'error' );
600
				}
601
602
				// Validate Shipping Methods
603
				$packages               = WC()->shipping->get_packages();
604
				$this->shipping_methods = (array) WC()->session->get( 'chosen_shipping_methods' );
605
606
				foreach ( $packages as $i => $package ) {
607
					if ( ! isset( $package['rates'][ $this->shipping_methods[ $i ] ] ) ) {
608
						wc_add_notice( __( 'No shipping method has been selected. Please double check your address, or contact us if you need any help.', 'woocommerce' ), 'error' );
609
						$this->shipping_methods[ $i ] = '';
610
					}
611
				}
612
			}
613
614
			if ( WC()->cart->needs_payment() ) {
615
				// Payment Method
616
				$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
617
618
				if ( ! isset( $available_gateways[ $this->posted['payment_method'] ] ) ) {
619
					$this->payment_method = '';
620
					wc_add_notice( __( 'Invalid payment method.', 'woocommerce' ), 'error' );
621
				} else {
622
					$this->payment_method = $available_gateways[ $this->posted['payment_method'] ];
623
					$this->payment_method->validate_fields();
624
				}
625
			} else {
626
				$available_gateways = array();
627
			}
628
629
			// Action after validation
630
			do_action( 'woocommerce_after_checkout_validation', $this->posted );
631
632
			if ( ! isset( $_POST['woocommerce_checkout_update_totals'] ) && wc_notice_count( 'error' ) == 0 ) {
633
634
				// Customer accounts
635
				$this->customer_id = apply_filters( 'woocommerce_checkout_customer_id', get_current_user_id() );
636
637
				if ( ! is_user_logged_in() && ( $this->must_create_account || ! empty( $this->posted['createaccount'] ) ) ) {
638
639
					$username     = ! empty( $this->posted['account_username'] ) ? $this->posted['account_username'] : '';
640
					$password     = ! empty( $this->posted['account_password'] ) ? $this->posted['account_password'] : '';
641
					$new_customer = wc_create_new_customer( $this->posted['billing_email'], $username, $password );
642
643
					if ( is_wp_error( $new_customer ) ) {
644
						throw new Exception( $new_customer->get_error_message() );
645
					} else {
646
						$this->customer_id = absint( $new_customer );
647
					}
648
649
					wc_set_customer_auth_cookie( $this->customer_id );
650
651
					// As we are now logged in, checkout will need to refresh to show logged in data
652
					WC()->session->set( 'reload_checkout', true );
653
654
					// Also, recalculate cart totals to reveal any role-based discounts that were unavailable before registering
655
					WC()->cart->calculate_totals();
656
657
					// Add customer info from other billing fields
658
					if ( $this->posted['billing_first_name'] && apply_filters( 'woocommerce_checkout_update_customer_data', true, $this ) ) {
659
						$userdata = array(
660
							'ID'           => $this->customer_id,
661
							'first_name'   => $this->posted['billing_first_name'] ? $this->posted['billing_first_name'] : '',
662
							'last_name'    => $this->posted['billing_last_name'] ? $this->posted['billing_last_name'] : '',
663
							'display_name' => $this->posted['billing_first_name'] ? $this->posted['billing_first_name'] : ''
664
						);
665
						wp_update_user( apply_filters( 'woocommerce_checkout_customer_userdata', $userdata, $this ) );
666
					}
667
				}
668
669
				// Do a final stock check at this point
670
				$this->check_cart_items();
671
672
				// Abort if errors are present
673
				if ( wc_notice_count( 'error' ) > 0 )
674
					throw new Exception();
675
676
				$order_id = $this->create_order();
677
678
				if ( is_wp_error( $order_id ) ) {
679
					throw new Exception( $order_id->get_error_message() );
680
				}
681
682
				do_action( 'woocommerce_checkout_order_processed', $order_id, $this->posted );
683
684
				// Process payment
685
				if ( WC()->cart->needs_payment() ) {
686
687
					// Store Order ID in session so it can be re-used after payment failure
688
					WC()->session->order_awaiting_payment = $order_id;
689
690
					// Process Payment
691
					$result = $available_gateways[ $this->posted['payment_method'] ]->process_payment( $order_id );
692
693
					// Redirect to success/confirmation/payment page
694
					if ( isset( $result['result'] ) && 'success' === $result['result'] ) {
695
696
						$result = apply_filters( 'woocommerce_payment_successful_result', $result, $order_id );
697
698
						if ( is_ajax() ) {
699
							wp_send_json( $result );
700
						} else {
701
							wp_redirect( $result['redirect'] );
702
							exit;
703
						}
704
705
					}
706
707
				} else {
708
709
					if ( empty( $order ) ) {
710
						$order = wc_get_order( $order_id );
711
					}
712
713
					// No payment was required for order
714
					$order->payment_complete();
715
716
					// Empty the Cart
717
					WC()->cart->empty_cart();
718
719
					// Get redirect
720
					$return_url = $order->get_checkout_order_received_url();
721
722
					// Redirect to success/confirmation/payment page
723
					if ( is_ajax() ) {
724
						wp_send_json( array(
725
							'result' 	=> 'success',
726
							'redirect'  => apply_filters( 'woocommerce_checkout_no_payment_needed_redirect', $return_url, $order )
727
						) );
728
					} else {
729
						wp_safe_redirect(
730
							apply_filters( 'woocommerce_checkout_no_payment_needed_redirect', $return_url, $order )
731
						);
732
						exit;
733
					}
734
735
				}
736
737
			}
738
739
		} catch ( Exception $e ) {
740
			if ( ! empty( $e ) ) {
741
				wc_add_notice( $e->getMessage(), 'error' );
742
			}
743
		}
744
745
		// If we reached this point then there were errors
746
		if ( is_ajax() ) {
747
748
			// only print notices if not reloading the checkout, otherwise they're lost in the page reload
749
			if ( ! isset( WC()->session->reload_checkout ) ) {
750
				ob_start();
751
				wc_print_notices();
752
				$messages = ob_get_clean();
753
			}
754
755
			$response = array(
756
				'result'	=> 'failure',
757
				'messages' 	=> isset( $messages ) ? $messages : '',
758
				'refresh' 	=> isset( WC()->session->refresh_totals ) ? 'true' : 'false',
759
				'reload'    => isset( WC()->session->reload_checkout ) ? 'true' : 'false'
760
			);
761
762
			unset( WC()->session->refresh_totals, WC()->session->reload_checkout );
763
764
			wp_send_json( $response );
765
		}
766
	}
767
768
	/**
769
	 * Get a posted address field after sanitization and validation.
770
	 * @param string $key
771
	 * @param string $type billing for shipping
772
	 * @return string
773
	 */
774
	public function get_posted_address_data( $key, $type = 'billing' ) {
775
		if ( 'billing' === $type || false === $this->posted['ship_to_different_address'] ) {
776
			$return = isset( $this->posted[ 'billing_' . $key ] ) ? $this->posted[ 'billing_' . $key ] : '';
777
		} else {
778
			$return = isset( $this->posted[ 'shipping_' . $key ] ) ? $this->posted[ 'shipping_' . $key ] : '';
779
		}
780
781
		// Use logged in user's billing email if neccessary
782
		if ( 'email' === $key && empty( $return ) && is_user_logged_in() ) {
783
			$current_user = wp_get_current_user();
784
			$return       = $current_user->user_email;
785
		}
786
		return $return;
787
	}
788
789
	/**
790
	 * Gets the value either from the posted data, or from the users meta data.
791
	 *
792
	 * @access public
793
	 * @param string $input
794
	 * @return string|null
795
	 */
796
	public function get_value( $input ) {
797
		if ( ! empty( $_POST[ $input ] ) ) {
798
799
			return wc_clean( $_POST[ $input ] );
800
801
		} else {
802
803
			$value = apply_filters( 'woocommerce_checkout_get_value', null, $input );
804
805
			if ( $value !== null ) {
806
				return $value;
807
			}
808
809
			// Get the billing_ and shipping_ address fields
810
			if ( isset( $this->checkout_fields['shipping'] ) && isset( $this->checkout_fields['billing'] ) ) {
811
812
				$address_fields = array_merge( $this->checkout_fields['billing'], $this->checkout_fields['shipping'] );
813
814
				if ( is_user_logged_in() && is_array( $address_fields ) && array_key_exists( $input, $address_fields ) ) {
815
					$current_user = wp_get_current_user();
816
817
					if ( $meta = get_user_meta( $current_user->ID, $input, true ) ) {
818
						return $meta;
819
					}
820
821
					if ( $input == 'billing_email' ) {
822
						return $current_user->user_email;
823
					}
824
				}
825
826
			}
827
828
			switch ( $input ) {
829
				case 'billing_country' :
830
					return apply_filters( 'default_checkout_country', WC()->customer->get_billing_country() ? WC()->customer->get_billing_country() : '', 'billing' );
831
				case 'billing_state' :
832
					return apply_filters( 'default_checkout_state', WC()->customer->get_billing_state() ? WC()->customer->get_billing_state() : '', 'billing' );
833
				case 'billing_postcode' :
834
					return apply_filters( 'default_checkout_postcode', WC()->customer->get_billing_postcode() ? WC()->customer->get_billing_postcode() : '', 'billing' );
835
				case 'shipping_country' :
836
					return apply_filters( 'default_checkout_country', WC()->customer->get_shipping_country() ? WC()->customer->get_shipping_country() : '', 'shipping' );
837
				case 'shipping_state' :
838
					return apply_filters( 'default_checkout_state', WC()->customer->get_shipping_state() ? WC()->customer->get_shipping_state() : '', 'shipping' );
839
				case 'shipping_postcode' :
840
					return apply_filters( 'default_checkout_postcode', WC()->customer->get_shipping_postcode() ? WC()->customer->get_shipping_postcode() : '', 'shipping' );
841
				default :
842
					return apply_filters( 'default_checkout_' . $input, null, $input );
843
			}
844
		}
845
	}
846
}
847