Completed
Pull Request — master (#10259)
by Mike
09:41
created

WC_Order::is_editable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 18 and the first side effect is on line 4.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
if ( ! defined( 'ABSPATH' ) ) {
4
	exit;
5
}
6
7
/**
8
 * Order Class.
9
 *
10
 * These are regular WooCommerce orders, which extend the abstract order class.
11
 *
12
 * @class    WC_Order
13
 * @version  2.2.0
14
 * @package  WooCommerce/Classes
15
 * @category Class
16
 * @author   WooThemes
17
 */
18
class WC_Order extends WC_Abstract_Order {
19
20
	/**
21
     * When a payment is complete this function is called.
22
     *
23
     * Most of the time this should mark an order as 'processing' so that admin can process/post the items.
24
     * If the cart contains only downloadable items then the order is 'completed' since the admin needs to take no action.
25
     * Stock levels are reduced at this point.
26
     * Sales are also recorded for products.
27
     * Finally, record the date of payment.
28
     *
29
     * @param string $transaction_id Optional transaction id to store in post meta.
30
     */
31
    public function payment_complete( $transaction_id = '' ) {
32
        do_action( 'woocommerce_pre_payment_complete', $this->get_id() );
33
34
        if ( ! empty( WC()->session ) ) {
35
            WC()->session->set( 'order_awaiting_payment', false );
36
        }
37
38
        if ( $this->get_id() && $this->has_status( apply_filters( 'woocommerce_valid_order_statuses_for_payment_complete', array( 'on-hold', 'pending', 'failed', 'cancelled' ), $this ) ) ) {
39
            $order_needs_processing = false;
40
41
            if ( sizeof( $this->get_items() ) > 0 ) {
42
                foreach ( $this->get_items() as $item ) {
43
					if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) ) {
44
						$virtual_downloadable_item = $product->is_downloadable() && $product->is_virtual();
45
46
						if ( apply_filters( 'woocommerce_order_item_needs_processing', ! $virtual_downloadable_item, $product, $this->get_id() ) ) {
47
                            $order_needs_processing = true;
48
                            break;
49
                        }
50
					}
51
                }
52
            }
53
54
			if ( ! empty( $transaction_id ) ) {
55
				$this->set_transaction_id( $transaction_id );
56
			}
57
58
			$this->set_status( apply_filters( 'woocommerce_payment_complete_order_status', $order_needs_processing ? 'processing' : 'completed', $this->get_id() ) );
59
			$this->set_date_paid( current_time( 'timestamp' ) );
60
			$this->save();
61
62
            do_action( 'woocommerce_payment_complete', $this->get_id() );
63
        } else {
64
            do_action( 'woocommerce_payment_complete_order_status_' . $this->get_status(), $this->get_id() );
65
        }
66
    }
67
68
	/*
69
    |--------------------------------------------------------------------------
70
    | Conditionals
71
    |--------------------------------------------------------------------------
72
    |
73
    | Checks if a condition is true or false.
74
    |
75
    */
76
77
	/**
78
     * Checks if an order can be edited, specifically for use on the Edit Order screen.
79
     * @return bool
80
     */
81
    public function is_editable() {
82
        return apply_filters( 'wc_order_is_editable', in_array( $this->get_status(), array( 'pending', 'on-hold', 'auto-draft' ) ), $this );
83
    }
84
85
	/**
86
     * Returns if an order has been paid for based on the order status.
87
     * @since 2.5.0
88
     * @return bool
89
     */
90
    public function is_paid() {
91
        return apply_filters( 'woocommerce_order_is_paid', $this->has_status( apply_filters( 'woocommerce_order_is_paid_statuses', array( 'processing', 'completed' ) ) ), $this );
92
    }
93
94
    /**
95
     * Checks if product download is permitted.
96
     *
97
     * @return bool
98
     */
99
    public function is_download_permitted() {
100
        return apply_filters( 'woocommerce_order_is_download_permitted', $this->has_status( 'completed' ) || ( 'yes' === get_option( 'woocommerce_downloads_grant_access_after_payment' ) && $this->has_status( 'processing' ) ), $this );
101
    }
102
103
	/**
104
     * Checks if an order needs display the shipping address, based on shipping method.
105
     * @return bool
106
     */
107
    public function needs_shipping_address() {
108
        if ( 'no' === get_option( 'woocommerce_calc_shipping' ) ) {
109
            return false;
110
        }
111
112
        $hide  = apply_filters( 'woocommerce_order_hide_shipping_address', array( 'local_pickup' ), $this );
113
        $needs_address = false;
114
115
        foreach ( $this->get_shipping_methods() as $shipping_method ) {
116
            if ( ! in_array( $shipping_method['method_id'], $hide ) ) {
117
                $needs_address = true;
118
                break;
119
            }
120
        }
121
122
        return apply_filters( 'woocommerce_order_needs_shipping_address', $needs_address, $hide, $this );
123
    }
124
125
	/**
126
     * Returns true if the order contains a downloadable product.
127
     * @return bool
128
     */
129
    public function has_downloadable_item() {
130
        foreach ( $this->get_items() as $item ) {
131
            if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) && $product->is_downloadable() && $product->has_file() ) {
132
				return true;
133
			}
134
        }
135
        return false;
136
    }
137
138
	/**
139
     * Checks if an order needs payment, based on status and order total.
140
     *
141
     * @return bool
142
     */
143
    public function needs_payment() {
144
        $valid_order_statuses = apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'failed' ), $this );
145
        return apply_filters( 'woocommerce_order_needs_payment', ( $this->has_status( $valid_order_statuses ) && $this->get_total() > 0 ), $this, $valid_order_statuses );
146
    }
147
148
	/*
149
    |--------------------------------------------------------------------------
150
    | Downloadable file permissions
151
    |--------------------------------------------------------------------------
152
    */
153
154
	/**
155
     * Get the Download URL.
156
     *
157
     * @param  int $product_id
158
     * @param  int $download_id
159
     * @return string
160
     */
161
    public function get_download_url( $product_id, $download_id ) {
162
        return add_query_arg( array(
163
            'download_file' => $product_id,
164
            'order'         => $this->get_order_key(),
165
            'email'         => urlencode( $this->get_billing_email() ),
166
            'key'           => $download_id
167
        ), trailingslashit( home_url() ) );
168
    }
169
170
	/**
171
     * Get the downloadable files for an item in this order.
172
     *
173
     * @param  array $item
174
     * @return array
175
     */
176
    public function get_item_downloads( $item ) {
177
        global $wpdb;
178
179
        $product_id   = $item['variation_id'] > 0 ? $item['variation_id'] : $item['product_id'];
180
        $product      = wc_get_product( $product_id );
181
        if ( ! $product ) {
182
            /**
183
             * $product can be `false`. Example: checking an old order, when a product or variation has been deleted.
184
             * @see \WC_Product_Factory::get_product
185
             */
186
            return array();
187
        }
188
        $download_ids = $wpdb->get_col( $wpdb->prepare("
189
            SELECT download_id
190
            FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
191
            WHERE user_email = %s
192
            AND order_key = %s
193
            AND product_id = %s
194
            ORDER BY permission_id
195
        ", $this->get_billing_email(), $this->get_order_key(), $product_id ) );
196
197
        $files = array();
198
199
        foreach ( $download_ids as $download_id ) {
200
201
            if ( $product->has_file( $download_id ) ) {
202
                $files[ $download_id ]                 = $product->get_file( $download_id );
203
                $files[ $download_id ]['download_url'] = $this->get_download_url( $product_id, $download_id );
204
            }
205
        }
206
207
        return apply_filters( 'woocommerce_get_item_downloads', $files, $item, $this );
208
    }
209
210
    /**
211
     * Display download links for an order item.
212
     * @param  array $item
213
     */
214
    public function display_item_downloads( $item ) {
215
        $product = $this->get_product_from_item( $item );
0 ignored issues
show
Documentation introduced by
$item is of type array, but the function expects a object.

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...
216
217
        if ( $product && $product->exists() && $product->is_downloadable() && $this->is_download_permitted() ) {
218
            $download_files = $this->get_item_downloads( $item );
219
            $i              = 0;
220
            $links          = array();
221
222
            foreach ( $download_files as $download_id => $file ) {
223
                $i++;
224
                $prefix  = count( $download_files ) > 1 ? sprintf( __( 'Download %d', 'woocommerce' ), $i ) : __( 'Download', 'woocommerce' );
225
                $links[] = '<small class="download-url">' . $prefix . ': <a href="' . esc_url( $file['download_url'] ) . '" target="_blank">' . esc_html( $file['name'] ) . '</a></small>' . "\n";
226
            }
227
228
            echo '<br/>' . implode( '<br/>', $links );
229
        }
230
    }
231
232
	/**
233
	 * Gets order total - formatted for display.
234
	 * @return string
235
	 */
236
	public function get_formatted_order_total( $tax_display = '', $display_refunded = true ) {
237
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_order_currency() ) );
238
		$order_total    = $this->get_total();
239
		$total_refunded = $this->get_total_refunded();
240
		$tax_string     = '';
241
242
		// Tax for inclusive prices
243
		if ( wc_tax_enabled() && 'incl' == $tax_display ) {
244
			$tax_string_array = array();
245
246
			if ( 'itemized' == get_option( 'woocommerce_tax_total_display' ) ) {
247
				foreach ( $this->get_tax_totals() as $code => $tax ) {
248
					$tax_amount         = ( $total_refunded && $display_refunded ) ? wc_price( WC_Tax::round( $tax->amount - $this->get_total_tax_refunded_by_rate_id( $tax->rate_id ) ), array( 'currency' => $this->get_order_currency() ) ) : $tax->formatted_amount;
249
					$tax_string_array[] = sprintf( '%s %s', $tax_amount, $tax->label );
250
				}
251
			} else {
252
				$tax_amount         = ( $total_refunded && $display_refunded ) ? $this->get_total_tax() - $this->get_total_tax_refunded() : $this->get_total_tax();
253
				$tax_string_array[] = sprintf( '%s %s', wc_price( $tax_amount, array( 'currency' => $this->get_order_currency() ) ), WC()->countries->tax_or_vat() );
254
			}
255
			if ( ! empty( $tax_string_array ) ) {
256
				$tax_string = ' ' . sprintf( __( '(Includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
257
			}
258
		}
259
260
		if ( $total_refunded && $display_refunded ) {
261
			$formatted_total = '<del>' . strip_tags( $formatted_total ) . '</del> <ins>' . wc_price( $order_total - $total_refunded, array( 'currency' => $this->get_order_currency() ) ) . $tax_string . '</ins>';
262
		} else {
263
			$formatted_total .= $tax_string;
264
		}
265
266
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
267
	}
268
269
	/*
270
    |--------------------------------------------------------------------------
271
    | URLs and Endpoints
272
    |--------------------------------------------------------------------------
273
    */
274
275
    /**
276
     * Generates a URL so that a customer can pay for their (unpaid - pending) order. Pass 'true' for the checkout version which doesn't offer gateway choices.
277
     *
278
     * @param  bool $on_checkout
279
     * @return string
280
     */
281
    public function get_checkout_payment_url( $on_checkout = false ) {
282
        $pay_url = wc_get_endpoint_url( 'order-pay', $this->get_id(), wc_get_page_permalink( 'checkout' ) );
283
284 View Code Duplication
        if ( 'yes' == get_option( 'woocommerce_force_ssl_checkout' ) || is_ssl() ) {
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...
285
            $pay_url = str_replace( 'http:', 'https:', $pay_url );
286
        }
287
288
        if ( $on_checkout ) {
289
            $pay_url = add_query_arg( 'key', $this->get_order_key(), $pay_url );
290
        } else {
291
            $pay_url = add_query_arg( array( 'pay_for_order' => 'true', 'key' => $this->get_order_key() ), $pay_url );
292
        }
293
294
        return apply_filters( 'woocommerce_get_checkout_payment_url', $pay_url, $this );
295
    }
296
297
    /**
298
     * Generates a URL for the thanks page (order received).
299
     *
300
     * @return string
301
     */
302
    public function get_checkout_order_received_url() {
303
        $order_received_url = wc_get_endpoint_url( 'order-received', $this->get_id(), wc_get_page_permalink( 'checkout' ) );
304
305
        if ( 'yes' === get_option( 'woocommerce_force_ssl_checkout' ) || is_ssl() ) {
306
            $order_received_url = str_replace( 'http:', 'https:', $order_received_url );
307
        }
308
309
        $order_received_url = add_query_arg( 'key', $this->get_order_key(), $order_received_url );
310
311
        return apply_filters( 'woocommerce_get_checkout_order_received_url', $order_received_url, $this );
312
    }
313
314
    /**
315
     * Generates a URL so that a customer can cancel their (unpaid - pending) order.
316
     *
317
     * @param string $redirect
318
     *
319
     * @return string
320
     */
321 View Code Duplication
    public function get_cancel_order_url( $redirect = '' ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
322
        return apply_filters( 'woocommerce_get_cancel_order_url', wp_nonce_url( add_query_arg( array(
323
            'cancel_order' => 'true',
324
            'order'        => $this->get_order_key(),
325
            'order_id'     => $this->get_id(),
326
            'redirect'     => $redirect
327
        ), $this->get_cancel_endpoint() ), 'woocommerce-cancel_order' ) );
328
    }
329
330
    /**
331
     * Generates a raw (unescaped) cancel-order URL for use by payment gateways.
332
     *
333
     * @param string $redirect
334
     *
335
     * @return string The unescaped cancel-order URL.
336
     */
337 View Code Duplication
    public function get_cancel_order_url_raw( $redirect = '' ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
338
        return apply_filters( 'woocommerce_get_cancel_order_url_raw', add_query_arg( array(
339
            'cancel_order' => 'true',
340
            'order'        => $this->get_order_key(),
341
            'order_id'     => $this->get_id(),
342
            'redirect'     => $redirect,
343
            '_wpnonce'     => wp_create_nonce( 'woocommerce-cancel_order' )
344
        ), $this->get_cancel_endpoint() ) );
345
    }
346
347
    /**
348
     * Helper method to return the cancel endpoint.
349
     *
350
     * @return string the cancel endpoint; either the cart page or the home page.
351
     */
352
    public function get_cancel_endpoint() {
353
        $cancel_endpoint = wc_get_page_permalink( 'cart' );
354
        if ( ! $cancel_endpoint ) {
355
            $cancel_endpoint = home_url();
356
        }
357
358
        if ( false === strpos( $cancel_endpoint, '?' ) ) {
359
            $cancel_endpoint = trailingslashit( $cancel_endpoint );
360
        }
361
362
        return $cancel_endpoint;
363
    }
364
365
    /**
366
     * Generates a URL to view an order from the my account page.
367
     *
368
     * @return string
369
     */
370
    public function get_view_order_url() {
371
        return apply_filters( 'woocommerce_get_view_order_url', wc_get_endpoint_url( 'view-order', $this->get_id(), wc_get_page_permalink( 'myaccount' ) ), $this );
372
    }
373
374
	/*
375
    |--------------------------------------------------------------------------
376
    | Order notes.
377
    |--------------------------------------------------------------------------
378
    */
379
380
	/**
381
     * Adds a note (comment) to the order.
382
     *
383
     * @param string $note Note to add.
384
     * @param int $is_customer_note (default: 0) Is this a note for the customer?
385
     * @param  bool added_by_user Was the note added by a user?
386
     * @return int Comment ID.
387
     */
388
    public function add_order_note( $note, $is_customer_note = 0, $added_by_user = false ) {
389
        if ( is_user_logged_in() && current_user_can( 'edit_shop_order', $this->get_id() ) && $added_by_user ) {
390
            $user                 = get_user_by( 'id', get_current_user_id() );
391
            $comment_author       = $user->display_name;
392
            $comment_author_email = $user->user_email;
393
        } else {
394
            $comment_author       = __( 'WooCommerce', 'woocommerce' );
395
            $comment_author_email = strtolower( __( 'WooCommerce', 'woocommerce' ) ) . '@';
396
            $comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com';
397
            $comment_author_email = sanitize_email( $comment_author_email );
398
        }
399
400
        $comment_post_ID        = $this->get_id();
401
        $comment_author_url     = '';
402
        $comment_content        = $note;
403
        $comment_agent          = 'WooCommerce';
404
        $comment_type           = 'order_note';
405
        $comment_parent         = 0;
406
        $comment_approved       = 1;
407
        $commentdata            = apply_filters( 'woocommerce_new_order_note_data', compact( 'comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_agent', 'comment_type', 'comment_parent', 'comment_approved' ), array( 'order_id' => $this->get_id(), 'is_customer_note' => $is_customer_note ) );
408
409
        $comment_id = wp_insert_comment( $commentdata );
410
411
        if ( $is_customer_note ) {
412
            add_comment_meta( $comment_id, 'is_customer_note', 1 );
413
414
            do_action( 'woocommerce_new_customer_note', array( 'order_id' => $this->get_id(), 'customer_note' => $commentdata['comment_content'] ) );
415
        }
416
417
        return $comment_id;
418
    }
419
420
	/**
421
     * List order notes (public) for the customer.
422
     *
423
     * @return array
424
     */
425
    public function get_customer_order_notes() {
426
        $notes = array();
427
        $args  = array(
428
            'post_id' => $this->get_id(),
429
            'approve' => 'approve',
430
            'type'    => ''
431
        );
432
433
        remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
434
435
        $comments = get_comments( $args );
436
437
        foreach ( $comments as $comment ) {
438
            if ( ! get_comment_meta( $comment->comment_ID, 'is_customer_note', true ) ) {
439
                continue;
440
            }
441
            $comment->comment_content = make_clickable( $comment->comment_content );
442
            $notes[] = $comment;
443
        }
444
445
        add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
446
447
        return $notes;
448
    }
449
450
	/*
451
    |--------------------------------------------------------------------------
452
    | Refunds
453
    |--------------------------------------------------------------------------
454
    */
455
456
	/**
457
	 * Get order refunds.
458
	 * @since 2.2
459
	 * @return array of WC_Order_Refund objects
460
	 */
461
	public function get_refunds() {
462
		if ( empty( $this->refunds ) && ! is_array( $this->refunds ) ) {
463
			$refunds      = array();
464
			$refund_items = get_posts(
465
				array(
466
					'post_type'      => 'shop_order_refund',
467
					'post_parent'    => $this->get_id(),
468
					'posts_per_page' => -1,
469
					'post_status'    => 'any',
470
					'fields'         => 'ids'
471
				)
472
			);
473
474
			foreach ( $refund_items as $refund_id ) {
475
				$refunds[] = new WC_Order_Refund( $refund_id );
476
			}
477
478
			$this->refunds = $refunds;
479
		}
480
		return $this->refunds;
481
	}
482
483
	/**
484
	 * Get amount already refunded.
485
	 *
486
	 * @since 2.2
487
	 * @return string
488
	 */
489 View Code Duplication
	public function get_total_refunded() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
490
		global $wpdb;
491
492
		$total = $wpdb->get_var( $wpdb->prepare( "
493
			SELECT SUM( postmeta.meta_value )
494
			FROM $wpdb->postmeta AS postmeta
495
			INNER JOIN $wpdb->posts AS posts ON ( posts.post_type = 'shop_order_refund' AND posts.post_parent = %d )
496
			WHERE postmeta.meta_key = '_refund_amount'
497
			AND postmeta.post_id = posts.ID
498
		", $this->get_id() ) );
499
500
		return $total;
501
	}
502
503
	/**
504
	 * Get the total tax refunded.
505
	 *
506
	 * @since  2.3
507
	 * @return float
508
	 */
509 View Code Duplication
	public function get_total_tax_refunded() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
510
		global $wpdb;
511
512
		$total = $wpdb->get_var( $wpdb->prepare( "
513
			SELECT SUM( order_itemmeta.meta_value )
514
			FROM {$wpdb->prefix}woocommerce_order_itemmeta AS order_itemmeta
515
			INNER JOIN $wpdb->posts AS posts ON ( posts.post_type = 'shop_order_refund' AND posts.post_parent = %d )
516
			INNER JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON ( order_items.order_id = posts.ID AND order_items.order_item_type = 'tax' )
517
			WHERE order_itemmeta.order_item_id = order_items.order_item_id
518
			AND order_itemmeta.meta_key IN ('tax_amount', 'shipping_tax_amount')
519
		", $this->get_id() ) );
520
521
		return abs( $total );
522
	}
523
524
	/**
525
	 * Get the total shipping refunded.
526
	 *
527
	 * @since  2.4
528
	 * @return float
529
	 */
530 View Code Duplication
	public function get_total_shipping_refunded() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
531
		global $wpdb;
532
533
		$total = $wpdb->get_var( $wpdb->prepare( "
534
			SELECT SUM( order_itemmeta.meta_value )
535
			FROM {$wpdb->prefix}woocommerce_order_itemmeta AS order_itemmeta
536
			INNER JOIN $wpdb->posts AS posts ON ( posts.post_type = 'shop_order_refund' AND posts.post_parent = %d )
537
			INNER JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON ( order_items.order_id = posts.ID AND order_items.order_item_type = 'shipping' )
538
			WHERE order_itemmeta.order_item_id = order_items.order_item_id
539
			AND order_itemmeta.meta_key IN ('cost')
540
		", $this->get_id() ) );
541
542
		return abs( $total );
543
	}
544
545
	/**
546
	 * Gets the count of order items of a certain type that have been refunded.
547
	 * @since  2.4.0
548
	 * @param string $item_type
549
	 * @return string
550
	 */
551
	public function get_item_count_refunded( $item_type = '' ) {
552
		if ( empty( $item_type ) ) {
553
			$item_type = array( 'line_item' );
554
		}
555
		if ( ! is_array( $item_type ) ) {
556
			$item_type = array( $item_type );
557
		}
558
		$count = 0;
559
560
		foreach ( $this->get_refunds() as $refund ) {
561
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
562
				$count += empty( $refunded_item['qty'] ) ? 0 : $refunded_item['qty'];
563
			}
564
		}
565
566
		return apply_filters( 'woocommerce_get_item_count_refunded', $count, $item_type, $this );
567
	}
568
569
	/**
570
	 * Get the total number of items refunded.
571
	 *
572
	 * @since  2.4.0
573
	 * @param  string $item_type type of the item we're checking, if not a line_item
574
	 * @return integer
575
	 */
576
	public function get_total_qty_refunded( $item_type = 'line_item' ) {
577
		$qty = 0;
578
		foreach ( $this->get_refunds() as $refund ) {
579
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
580
				$qty += $refunded_item['qty'];
581
			}
582
		}
583
		return $qty;
584
	}
585
586
	/**
587
	 * Get the refunded amount for a line item.
588
	 *
589
	 * @param  int $item_id ID of the item we're checking
590
	 * @param  string $item_type type of the item we're checking, if not a line_item
591
	 * @return integer
592
	 */
593
	public function get_qty_refunded_for_item( $item_id, $item_type = 'line_item' ) {
594
		$qty = 0;
595
		foreach ( $this->get_refunds() as $refund ) {
596
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
597
				if ( isset( $refunded_item['refunded_item_id'] ) && $refunded_item['refunded_item_id'] == $item_id ) {
598
					$qty += $refunded_item['qty'];
599
				}
600
			}
601
		}
602
		return $qty;
603
	}
604
605
	/**
606
	 * Get the refunded amount for a line item.
607
	 *
608
	 * @param  int $item_id ID of the item we're checking
609
	 * @param  string $item_type type of the item we're checking, if not a line_item
610
	 * @return integer
611
	 */
612
	public function get_total_refunded_for_item( $item_id, $item_type = 'line_item' ) {
613
		$total = 0;
614
		foreach ( $this->get_refunds() as $refund ) {
615
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
616
				if ( isset( $refunded_item['refunded_item_id'] ) && $refunded_item['refunded_item_id'] == $item_id ) {
617
					switch ( $item_type ) {
618
						case 'shipping' :
619
							$total += $refunded_item['cost'];
620
						break;
621
						default :
622
							$total += $refunded_item['line_total'];
623
						break;
624
					}
625
				}
626
			}
627
		}
628
		return $total * -1;
629
	}
630
631
	/**
632
	 * Get the refunded amount for a line item.
633
	 *
634
	 * @param  int $item_id ID of the item we're checking
635
	 * @param  int $tax_id ID of the tax we're checking
636
	 * @param  string $item_type type of the item we're checking, if not a line_item
637
	 * @return double
638
	 */
639
	public function get_tax_refunded_for_item( $item_id, $tax_id, $item_type = 'line_item' ) {
640
		$total = 0;
641
		foreach ( $this->get_refunds() as $refund ) {
642
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
643
				if ( isset( $refunded_item['refunded_item_id'] ) && $refunded_item['refunded_item_id'] == $item_id ) {
644
					switch ( $item_type ) {
645 View Code Duplication
						case '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...
646
							$tax_data = maybe_unserialize( $refunded_item['taxes'] );
647
							if ( isset( $tax_data[ $tax_id ] ) ) {
648
								$total += $tax_data[ $tax_id ];
649
							}
650
						break;
651 View Code Duplication
						default :
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...
652
							$tax_data = maybe_unserialize( $refunded_item['line_tax_data'] );
653
							if ( isset( $tax_data['total'][ $tax_id ] ) ) {
654
								$total += $tax_data['total'][ $tax_id ];
655
							}
656
						break;
657
					}
658
				}
659
			}
660
		}
661
		return wc_round_tax_total( $total ) * -1;
662
	}
663
664
	/**
665
	 * Get total tax refunded by rate ID.
666
	 *
667
	 * @param  int $rate_id
668
	 *
669
	 * @return float
670
	 */
671
	public function get_total_tax_refunded_by_rate_id( $rate_id ) {
672
		$total = 0;
673
		foreach ( $this->get_refunds() as $refund ) {
674
			foreach ( $refund->get_items( 'tax' ) as $refunded_item ) {
675
				if ( isset( $refunded_item['rate_id'] ) && $refunded_item['rate_id'] == $rate_id ) {
676
					$total += abs( $refunded_item['tax_amount'] ) + abs( $refunded_item['shipping_tax_amount'] );
677
				}
678
			}
679
		}
680
681
		return $total;
682
	}
683
684
	/**
685
	 * How much money is left to refund?
686
	 * @return string
687
	 */
688
	public function get_remaining_refund_amount() {
689
		return wc_format_decimal( $this->get_total() - $this->get_total_refunded(), wc_get_price_decimals() );
690
	}
691
692
	/**
693
	 * How many items are left to refund?
694
	 * @return int
695
	 */
696
	public function get_remaining_refund_items() {
697
		return absint( $this->get_item_count() - $this->get_item_count_refunded() );
698
	}
699
}
700