Completed
Push — master ( 1c592b...3f9388 )
by Mike
15:22
created

WC_Order_Item_Product::read()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 12
nc 2
nop 1
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * Order Line Item (product).
8
 *
9
 * @version     2.7.0
10
 * @since       2.7.0
11
 * @package     WooCommerce/Classes
12
 * @author      WooThemes
13
 */
14
class WC_Order_Item_Product extends WC_Order_Item {
15
16
	/**
17
	 * Data properties of this order item object.
18
	 * @since 2.7.0
19
	 * @var array
20
	 */
21
	protected $_data = array(
22
		'order_id'      => 0,
23
		'order_item_id' => 0,
24
		'name'          => '',
25
		'product_id'    => 0,
26
		'variation_id'  => 0,
27
		'qty'           => 0,
28
		'tax_class'     => '',
29
		'subtotal'      => 0,
30
		'subtotal_tax'  => 0,
31
		'total'         => 0,
32
		'total_tax'     => 0,
33
		'taxes'         => array(
34
			'subtotal' => array(),
35
			'total'    => array()
36
		),
37
	);
38
39
	/**
40
	 * offsetGet for ArrayAccess/Backwards compatibility.
41
	 * @deprecated Add deprecation notices in future release.
42
	 * @param string $offset
43
	 * @return mixed
44
	 */
45 View Code Duplication
	public function offsetGet( $offset ) {
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...
46
		if ( 'line_subtotal' === $offset ) {
47
			$offset = 'subtotal';
48
		} elseif ( 'line_subtotal_tax' === $offset ) {
49
			$offset = 'subtotal_tax';
50
		} elseif ( 'line_total' === $offset ) {
51
			$offset = 'total';
52
		} elseif ( 'line_tax' === $offset ) {
53
			$offset = 'total_tax';
54
		} elseif ( 'line_tax_data' === $offset ) {
55
			$offset = 'taxes';
56
		}
57
		return parent::offsetGet( $offset );
58
	}
59
60
	/**
61
	 * offsetSet for ArrayAccess/Backwards compatibility.
62
	 * @deprecated Add deprecation notices in future release.
63
	 * @param string $offset
64
	 * @param mixed $value
65
	 */
66 View Code Duplication
	public function offsetSet( $offset, $value ) {
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...
67
		if ( 'line_subtotal' === $offset ) {
68
			$offset = 'subtotal';
69
		} elseif ( 'line_subtotal_tax' === $offset ) {
70
			$offset = 'subtotal_tax';
71
		} elseif ( 'line_total' === $offset ) {
72
			$offset = 'total';
73
		} elseif ( 'line_tax' === $offset ) {
74
			$offset = 'total_tax';
75
		} elseif ( 'line_tax_data' === $offset ) {
76
			$offset = 'taxes';
77
		}
78
		parent::offsetSet( $offset, $value );
79
	}
80
81
	/**
82
	 * offsetExists for ArrayAccess
83
	 * @param string $offset
84
	 * @return bool
85
	 */
86
	public function offsetExists( $offset ) {
87
		if ( in_array( $offset, array( 'line_subtotal', 'line_subtotal_tax', 'line_total', 'line_tax', 'line_tax_data', 'item_meta_array', 'item_meta' ) ) ) {
88
			return true;
89
		}
90
		return parent::offsetExists( $offset );
91
	}
92
93
	/**
94
	 * Read/populate data properties specific to this order item.
95
	 */
96
	public function read( $id ) {
97
		parent::read( $id );
98
		if ( $this->get_id() ) {
99
			$this->set_product_id( get_metadata( 'order_item', $this->get_id(), '_product_id', true ) );
100
			$this->set_variation_id( get_metadata( 'order_item', $this->get_id(), '_variation_id', true ) );
101
			$this->set_qty( get_metadata( 'order_item', $this->get_id(), '_qty', true ) );
102
			$this->set_tax_class( get_metadata( 'order_item', $this->get_id(), '_tax_class', true ) );
103
			$this->set_subtotal( get_metadata( 'order_item', $this->get_id(), '_line_subtotal', true ) );
104
			$this->set_subtotal_tax( get_metadata( 'order_item', $this->get_id(), '_line_subtotal_tax', true ) );
105
			$this->set_total( get_metadata( 'order_item', $this->get_id(), '_line_total', true ) );
106
			$this->set_total_tax( get_metadata( 'order_item', $this->get_id(), '_line_tax', true ) );
107
			$this->set_taxes( get_metadata( 'order_item', $this->get_id(), '_line_tax_data', true ) );
108
		}
109
	}
110
111
	/**
112
	 * Save properties specific to this order item.
113
	 * @return int Item ID
114
	 */
115
	public function save() {
116
		parent::save();
117
		if ( $this->get_id() ) {
118
			wc_update_order_item_meta( $this->get_id(), '_product_id', $this->get_product_id() );
119
			wc_update_order_item_meta( $this->get_id(), '_variation_id', $this->get_variation_id() );
120
			wc_update_order_item_meta( $this->get_id(), '_qty', $this->get_qty() );
121
			wc_update_order_item_meta( $this->get_id(), '_tax_class', $this->get_tax_class() );
122
			wc_update_order_item_meta( $this->get_id(), '_line_subtotal', $this->get_subtotal() );
123
			wc_update_order_item_meta( $this->get_id(), '_line_subtotal_tax', $this->get_subtotal_tax() );
124
			wc_update_order_item_meta( $this->get_id(), '_line_total', $this->get_total() );
125
			wc_update_order_item_meta( $this->get_id(), '_line_tax', $this->get_total_tax() );
126
			wc_update_order_item_meta( $this->get_id(), '_line_tax_data', $this->get_taxes() );
127
		}
128
129
		return $this->get_id();
130
	}
131
132
	/**
133
	 * Internal meta keys we don't want exposed as part of meta_data.
134
	 * @return array()
0 ignored issues
show
Documentation introduced by
The doc-type array() could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
135
	 */
136
	protected function get_internal_meta_keys() {
137
		return array( '_product_id', '_variation_id', '_qty', '_tax_class', '_line_subtotal', '_line_subtotal_tax', '_line_total', '_line_tax', '_line_tax_data' );
138
	}
139
140
	/**
141
	 * Get the associated product.
142
	 * @return WC_Product|bool
143
	 */
144
	public function get_product() {
145
		if ( $this->get_variation_id() ) {
146
			$product = wc_get_product( $this->get_variation_id() );
147
		} else {
148
			$product = wc_get_product( $this->get_product_id() );
149
		}
150
151
		// Backwards compatible filter from WC_Order::get_product_from_item()
152
		if ( has_filter( 'woocommerce_get_product_from_item' ) ) {
153
			$product = apply_filters( 'woocommerce_get_product_from_item', $product, $this, wc_get_order( $this->get_order_id() ) );
154
		}
155
156
		return apply_filters( 'woocommerce_order_item_product', $product, $this );
157
	}
158
159
	/**
160
	 * Get the Download URL.
161
	 * @param  int $download_id
162
	 * @return string
163
	 */
164
	public function get_item_download_url( $download_id ) {
165
		$order = $this->get_order();
166
167
		return $order ? add_query_arg( array(
168
			'download_file' => $this->get_variation_id() ? $this->get_variation_id() : $this->get_product_id(),
169
			'order'         => $order->get_order_key(),
170
			'email'         => urlencode( $order->get_billing_email() ),
171
			'key'           => $download_id
172
		), trailingslashit( home_url() ) ) : '';
173
	}
174
175
	/**
176
	 * Get any associated downloadable files.
177
	 * @return array
178
	 */
179
	public function get_item_downloads() {
180
		global $wpdb;
181
182
		$files   = array();
183
		$product = $this->get_product();
184
		$order   = $this->get_order();
185
186
		if ( $product && $order && $product->is_downloadable() && $order->is_download_permitted() ) {
187
			$download_ids        = $wpdb->get_col(
188
				$wpdb->prepare(
189
					"SELECT download_id FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE user_email = %s AND order_key = %s AND product_id = %d ORDER BY permission_id",
190
					$order->get_billing_email(),
191
					$order->get_order_key(),
192
					$this->get_variation_id() ? $this->get_variation_id() : $this->get_product_id()
193
				)
194
			);
195
196
			foreach ( $download_ids as $download_id ) {
197
				if ( $product->has_file( $download_id ) ) {
198
					$files[ $download_id ]                 = $product->get_file( $download_id );
199
					$files[ $download_id ]['download_url'] = $this->get_item_download_url( $download_id );
200
				}
201
			}
202
		}
203
204
		return apply_filters( 'woocommerce_get_item_downloads', $files, $this, $order );
205
	}
206
207
	/**
208
	 * Get tax status.
209
	 * @return string
210
	 */
211
	public function get_tax_status() {
212
		$product = $this->get_product();
213
		return $product ? $product->get_tax_status() : 'taxable';
214
	}
215
216
	/*
217
	|--------------------------------------------------------------------------
218
	| Setters
219
	|--------------------------------------------------------------------------
220
	*/
221
222
	/**
223
	 * Set qty.
224
	 * @param int $value
225
	 */
226
	public function set_qty( $value ) {
227
		$this->_data['qty'] = wc_stock_amount( $value );
228
	}
229
230
	/**
231
	 * Set tax class.
232
	 * @param string $value
233
	 */
234
	public function set_tax_class( $value ) {
235
		$this->_data['tax_class'] = $value;
236
	}
237
238
	/**
239
	 * Set Product ID
240
	 * @param int $value
241
	 */
242
	public function set_product_id( $value ) {
243
		$this->_data['product_id'] = absint( $value );
244
	}
245
246
	/**
247
	 * Set variation ID.
248
	 * @param int $value
249
	 */
250
	public function set_variation_id( $value ) {
251
		$this->_data['variation_id'] = absint( $value );
252
	}
253
254
	/**
255
	 * Line subtotal (before discounts).
256
	 * @param string $value
257
	 */
258
	public function set_subtotal( $value ) {
259
		$this->_data['subtotal'] = wc_format_decimal( $value );
260
	}
261
262
	/**
263
	 * Line total (after discounts).
264
	 * @param string $value
265
	 */
266
	public function set_total( $value ) {
267
		$this->_data['total'] = wc_format_decimal( $value );
268
	}
269
270
	/**
271
	 * Line subtotal tax (before discounts).
272
	 * @param string $value
273
	 */
274
	public function set_subtotal_tax( $value ) {
275
		$this->_data['subtotal_tax'] = wc_format_decimal( $value );
276
	}
277
278
	/**
279
	 * Line total tax (after discounts).
280
	 * @param string $value
281
	 */
282
	public function set_total_tax( $value ) {
283
		$this->_data['total_tax'] = wc_format_decimal( $value );
284
	}
285
286
	/**
287
	 * Set line taxes.
288
	 * @param array $raw_tax_data
289
	 */
290
	public function set_taxes( $raw_tax_data ) {
291
		$raw_tax_data = maybe_unserialize( $raw_tax_data );
292
		$tax_data     = array(
293
			'total'    => array(),
294
			'subtotal' => array()
295
		);
296
		if ( ! empty( $raw_tax_data['total'] ) && ! empty( $raw_tax_data['subtotal'] ) ) {
297
			$tax_data['total']    = array_map( 'wc_format_decimal', $raw_tax_data['total'] );
298
			$tax_data['subtotal'] = array_map( 'wc_format_decimal', $raw_tax_data['subtotal'] );
299
		}
300
		$this->_data['taxes'] = $tax_data;
301
	}
302
303
	/**
304
	 * Set variation data (stored as meta data - write only).
305
	 * @param array $data Key/Value pairs
306
	 */
307
	public function set_variation( $data ) {
308
		foreach ( $data as $key => $value ) {
309
			$this->_meta_data[ str_replace( 'attribute_', '', $key ) ] = $value;
310
		}
311
	}
312
313
	/**
314
	 * Set properties based on passed in product object.
315
	 * @param WC_Product $product
316
	 */
317
	public function set_product( $product ) {
318
		if ( $product ) {
319
			$this->set_product_id( $product->get_id() );
320
			$this->set_name( $product->get_title() );
321
			$this->set_tax_class( $product->get_tax_class() );
322
			$this->set_variation_id( is_callable( array( $product, 'get_variation_id' ) ) ? $product->get_variation_id() : 0 );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Product as the method get_variation_id() does only exist in the following sub-classes of WC_Product: WC_Product_Variation. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
323
			$this->set_variation( is_callable( array( $product, 'get_variation_attributes' ) ) ? $product->get_variation_attributes() : array() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Product as the method get_variation_attributes() does only exist in the following sub-classes of WC_Product: WC_Product_Variable, WC_Product_Variation. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
324
		}
325
	}
326
327
	/*
328
	|--------------------------------------------------------------------------
329
	| Getters
330
	|--------------------------------------------------------------------------
331
	*/
332
333
	/**
334
	 * Get order item type.
335
	 * @return string
336
	 */
337
	public function get_type() {
338
		return 'line_item';
339
	}
340
341
	/**
342
	 * Get product ID.
343
	 * @return int
344
	 */
345
	public function get_product_id() {
346
		return absint( $this->_data['product_id'] );
347
	}
348
349
	/**
350
	 * Get variation ID.
351
	 * @return int
352
	 */
353
	public function get_variation_id() {
354
		return absint( $this->_data['variation_id'] );
355
	}
356
357
	/**
358
	 * Get qty.
359
	 * @return int
360
	 */
361
	public function get_qty() {
362
		return wc_stock_amount( $this->_data['qty'] );
363
	}
364
365
	/**
366
	 * Get tax class.
367
	 * @return string
368
	 */
369
	public function get_tax_class() {
370
		return $this->_data['tax_class'];
371
	}
372
373
	/**
374
	 * Get subtotal.
375
	 * @return string
376
	 */
377
	public function get_subtotal() {
378
		return wc_format_decimal( $this->_data['subtotal'] );
379
	}
380
381
	/**
382
	 * Get subtotal tax.
383
	 * @return string
384
	 */
385
	public function get_subtotal_tax() {
386
		return wc_format_decimal( $this->_data['subtotal_tax'] );
387
	}
388
389
	/**
390
	 * Get total.
391
	 * @return string
392
	 */
393
	public function get_total() {
394
		return wc_format_decimal( $this->_data['total'] );
395
	}
396
397
	/**
398
	 * Get total tax.
399
	 * @return string
400
	 */
401
	public function get_total_tax() {
402
		return wc_format_decimal( $this->_data['total_tax'] );
403
	}
404
405
	/**
406
	 * Get fee taxes.
407
	 * @return array
408
	 */
409
	public function get_taxes() {
410
		return $this->_data['taxes'];
411
	}
412
}
413