Completed
Pull Request — master (#11716)
by Claudio
08:25
created

WC_Order_Item::get_qty()   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
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * Order Item
8
 *
9
 * A class which represents an item within an order and handles CRUD.
10
 * Uses ArrayAccess to be BW compatible with WC_Orders::get_items().
11
 *
12
 * @version     2.7.0
13
 * @since       2.7.0
14
 * @package     WooCommerce/Classes
15
 * @author      WooThemes
16
 */
17
class WC_Order_Item extends WC_Data implements ArrayAccess {
18
19
	/**
20
	 * Data array, with defaults.
21
	 * @since 2.7.0
22
	 * @var array
23
	 */
24
	protected $_data = array(
25
		'order_id' => 0,
26
		'id'       => 0, // order_item_id
27
		'name'     => '',
28
		'type'     => '',
29
	);
30
31
	/**
32
	 * May store an order to prevent retriving it multiple times.
33
	 * @var object
34
	 */
35
	protected $_order;
36
37
	/**
38
	 * Stores meta in cache for future reads.
39
	 * A group must be set to to enable caching.
40
	 * @var string
41
	 */
42
	protected $_cache_group = 'order_itemmeta';
43
44
	/**
45
	 * Meta type. This should match up with
46
	 * the types avaiable at https://codex.wordpress.org/Function_Reference/add_metadata.
47
	 * WP defines 'post', 'user', 'comment', and 'term'.
48
	 */
49
	protected $_meta_type = 'order_item';
50
51
	/**
52
	 * Constructor.
53
	 * @param int|object|array $order_item ID to load from the DB (optional) or already queried data.
0 ignored issues
show
Bug introduced by
There is no parameter named $order_item. Was it maybe removed?

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

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

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

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

Loading history...
54
	 */
55
	public function __construct( $item = 0 ) {
56
		if ( $item instanceof WC_Order_Item ) {
57
			if ( $this->is_type( $item->get_type() ) ) {
58
				$this->set_all( $item->get_data() );
59
			}
60
		} elseif ( is_array( $item ) ) {
61
			$this->set_all( $item );
62
		} else {
63
			$this->read( $item );
64
		}
65
	}
66
67
	/**
68
	 * Set all data based on input array.
69
	 * @param array $data
70
	 * @access private
71
	 */
72
	public function set_all( $data ) {
73
		foreach ( $data as $key => $value ) {
74
			if ( is_callable( array( $this, "set_$key" ) ) ) {
75
				$this->{"set_$key"}( $value );
76
			}
77
		}
78
	}
79
80
	/**
81
	 * Type checking
82
	 * @param  string|array  $Type
0 ignored issues
show
Bug introduced by
There is no parameter named $Type. Was it maybe removed?

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

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

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

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

Loading history...
83
	 * @return boolean
84
	 */
85
	public function is_type( $type ) {
86
		return is_array( $type ) ? in_array( $this->get_type(), $type ) : $type === $this->get_type();
87
	}
88
89
	/**
90
	 * Get quantity.
91
	 * @return int
92
	 */
93
	public function get_quantity() {
94
		return 1;
95
	}
96
97
	/**
98
	 * Get parent order object.
99
	 * @return int
100
	 */
101
	public function get_order() {
102
		if ( ! $this->_order ) {
103
		 	$this->_order = wc_get_order( $this->get_order_id() );
0 ignored issues
show
Documentation Bug introduced by
It seems like wc_get_order($this->get_order_id()) can also be of type false. However, the property $_order is declared as type object. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
104
		}
105
		return $this->_order;
106
	}
107
108
	/*
109
	|--------------------------------------------------------------------------
110
	| Getters
111
	|--------------------------------------------------------------------------
112
	*/
113
114
	/**
115
	 * Get order item ID.
116
	 * @return int
117
	 */
118
	public function get_id() {
119
		return $this->_data['id'];
120
	}
121
122
	/**
123
	 * Get order ID this meta belongs to.
124
	 * @return int
125
	 */
126
	public function get_order_id() {
127
		return $this->_data['order_id'];
128
	}
129
130
	/**
131
	 * Get order item name.
132
	 * @return string
133
	 */
134
	public function get_name() {
135
		return $this->_data['name'];
136
	}
137
138
	/**
139
	 * Get order item type.
140
	 * @return string
141
	 */
142
	public function get_type() {
143
		return $this->_data['type'];
144
	}
145
146
	/*
147
	|--------------------------------------------------------------------------
148
	| Setters
149
	|--------------------------------------------------------------------------
150
	*/
151
152
	/**
153
	 * Set ID
154
	 * @param int $value
155
	 */
156
	public function set_id( $value ) {
157
		$this->_data['id'] = absint( $value );
158
	}
159
160
	/**
161
	 * Set order ID.
162
	 * @param int $value
163
	 */
164
	public function set_order_id( $value ) {
165
		$this->_data['order_id'] = absint( $value );
166
	}
167
168
	/**
169
	 * Set order item name.
170
	 * @param string $value
171
	 */
172
	public function set_name( $value ) {
173
		$this->_data['name'] = wc_clean( $value );
174
	}
175
176
	/**
177
	 * Set order item type.
178
	 * @param string $value
179
	 */
180
	protected function set_type( $value ) {
181
		$this->_data['type'] = wc_clean( $value );
182
	}
183
184
	/*
185
	|--------------------------------------------------------------------------
186
	| CRUD methods
187
	|--------------------------------------------------------------------------
188
	|
189
	| Methods which create, read, update and delete data from the database.
190
	|
191
	*/
192
193
	/**
194
	 * Insert data into the database.
195
	 * @since 2.7.0
196
	 */
197 View Code Duplication
	public function create() {
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...
198
		global $wpdb;
199
200
		$wpdb->insert( $wpdb->prefix . 'woocommerce_order_items', array(
201
			'order_item_name' => $this->get_name(),
202
			'order_item_type' => $this->get_type(),
203
			'order_id'        => $this->get_order_id()
204
		) );
205
		$this->set_id( $wpdb->insert_id );
206
207
		do_action( 'woocommerce_new_order_item', $this->get_id(), $this, $this->get_order_id() );
208
	}
209
210
	/**
211
	 * Update data in the database.
212
	 * @since 2.7.0
213
	 */
214 View Code Duplication
	public function update() {
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...
215
		global $wpdb;
216
217
		$wpdb->update( $wpdb->prefix . 'woocommerce_order_items', array(
218
			'order_item_name' => $this->get_name(),
219
			'order_item_type' => $this->get_type(),
220
			'order_id'        => $this->get_order_id()
221
		), array( 'order_item_id' => $this->get_id() ) );
222
223
		do_action( 'woocommerce_update_order_item', $this->get_id(), $this, $this->get_order_id() );
224
	}
225
226
	/**
227
	 * Read from the database.
228
	 * @since 2.7.0
229
	 * @param int|object $item ID of object to read, or already queried object.
230
	 */
231
	public function read( $item ) {
232
		global $wpdb;
233
234
		if ( is_numeric( $item ) && ! empty( $item ) ) {
235
			$data = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d LIMIT 1;", $item ) );
236
		} elseif ( ! empty( $item->order_item_id ) ) {
237
			$data = $item;
238
		} else {
239
			$data = false;
240
		}
241
242
		if ( $data ) {
243
			$this->set_order_id( $data->order_id );
244
			$this->set_id( $data->order_item_id );
245
			$this->set_name( $data->order_item_name );
246
			$this->set_type( $data->order_item_type );
247
			$this->read_meta_data();
248
		}
249
	}
250
251
	/**
252
	 * Save data to the database.
253
	 * @since 2.7.0
254
	 * @return int Item ID
255
	 */
256
	public function save() {
257
		if ( ! $this->get_id() ) {
258
			$this->create();
259
		} else {
260
			$this->update();
261
		}
262
		$this->save_meta_data();
263
264
		return $this->get_id();
265
	}
266
267
	/**
268
	 * Delete data from the database.
269
	 * @since 2.7.0
270
	 */
271
	public function delete() {
272
		if ( $this->get_id() ) {
273
			global $wpdb;
274
			do_action( 'woocommerce_before_delete_order_item', $this->get_id() );
275
			$wpdb->delete( $wpdb->prefix . 'woocommerce_order_items', array( 'order_item_id' => $this->get_id() ) );
276
			$wpdb->delete( $wpdb->prefix . 'woocommerce_order_itemmeta', array( 'order_item_id' => $this->get_id() ) );
277
			do_action( 'woocommerce_delete_order_item', $this->get_id() );
278
		}
279
	}
280
281
	/*
282
	|--------------------------------------------------------------------------
283
	| Meta Data Handling
284
	|--------------------------------------------------------------------------
285
	*/
286
287
	/**
288
	 * Expands things like term slugs before return.
289
	 * @param string $hideprefix (default: _)
290
	 * @return array
291
	 */
292
	public function get_formatted_meta_data( $hideprefix = '_' ) {
293
		$formatted_meta = array();
294
		$meta_data      = $this->get_meta_data();
295
296
		foreach ( $meta_data as $meta ) {
297 View Code Duplication
			if ( "" === $meta->value || is_serialized( $meta->value ) || ( ! empty( $hideprefix ) && substr( $meta->key, 0, 1 ) === $hideprefix ) ) {
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...
298
				continue;
299
			}
300
301
			$meta->key     = rawurldecode( $meta->key );
302
			$meta->value   = rawurldecode( $meta->value );
303
			$attribute_key = str_replace( 'attribute_', '', $meta->key );
304
			$display_key   = wc_attribute_label( $attribute_key, is_callable( array( $this, 'get_product' ) ) ? $this->get_product() : false );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_product() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Product. 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...
305
			$display_value = $meta->value;
306
307 View Code Duplication
			if ( taxonomy_exists( $attribute_key ) ) {
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...
308
				$term = get_term_by( 'slug', $meta->value, $attribute_key );
309
				if ( ! is_wp_error( $term ) && is_object( $term ) && $term->name ) {
310
					$display_value = $term->name;
311
				}
312
			}
313
314
			$formatted_meta[ $meta->meta_id ] = (object) array(
315
				'key'           => $meta->key,
316
				'value'         => $meta->value,
317
				'display_key'   => apply_filters( 'woocommerce_order_item_display_meta_key', $display_key ),
318
				'display_value' => apply_filters( 'woocommerce_order_item_display_meta_value', wpautop( make_clickable( $display_value ) ) ),
319
			);
320
		}
321
322
		return $formatted_meta;
323
	}
324
325
	/*
326
	|--------------------------------------------------------------------------
327
	| Array Access Methods
328
	|--------------------------------------------------------------------------
329
	|
330
	| For backwards compat with legacy arrays.
331
	|
332
	*/
333
334
	/**
335
	 * offsetSet for ArrayAccess
336
	 * @param string $offset
337
	 * @param mixed $value
338
	 */
339
	public function offsetSet( $offset, $value ) {
340
		if ( 'item_meta_array' === $offset ) {
341
			foreach ( $value as $meta_id => $meta ) {
342
				$this->update_meta_data( $meta->key, $meta->value, $meta_id );
343
			}
344
			return;
345
		}
346
347
		if ( array_key_exists( $offset, $this->_data ) ) {
348
			$this->_data[ $offset ] = $value;
349
		}
350
351
		$this->update_meta_data( '_' . $offset, $value );
352
	}
353
354
	/**
355
	 * offsetUnset for ArrayAccess
356
	 * @param string $offset
357
	 */
358
	public function offsetUnset( $offset ) {
359
		if ( 'item_meta_array' === $offset || 'item_meta' === $offset ) {
360
			$this->_meta_data = array();
361
			return;
362
		}
363
364
		if ( array_key_exists( $offset, $this->_data ) ) {
365
			unset( $this->_data[ $offset ] );
366
		}
367
368
		$this->delete_meta_data( '_' . $offset );
0 ignored issues
show
Documentation introduced by
'_' . $offset is of type string, but the function expects a array.

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...
369
	}
370
371
	/**
372
	 * offsetExists for ArrayAccess
373
	 * @param string $offset
374
	 * @return bool
375
	 */
376
	public function offsetExists( $offset ) {
377
		if ( 'item_meta_array' === $offset || 'item_meta' === $offset || array_key_exists( $offset, $this->_data ) ) {
378
			return true;
379
		}
380
		return array_key_exists( '_' . $offset, wp_list_pluck( $this->_meta_data, 'value', 'key' ) );
381
	}
382
383
	/**
384
	 * offsetGet for ArrayAccess
385
	 * @param string $offset
386
	 * @return mixed
387
	 */
388
	public function offsetGet( $offset ) {
389
		if ( 'item_meta_array' === $offset ) {
390
			$return = array();
391
392
			foreach ( $this->_meta_data as $meta ) {
393
				$return[ $meta->meta_id ] = $meta;
394
			}
395
396
			return $return;
397
		}
398
399
		$meta_values = wp_list_pluck( $this->_meta_data, 'value', 'key' );
400
401
		if ( 'item_meta' === $offset ) {
402
			return $meta_values;
403
		} elseif ( array_key_exists( $offset, $this->_data ) ) {
404
			return $this->_data[ $offset ];
405
		} elseif ( array_key_exists( '_' . $offset, $meta_values ) ) {
406
			// Item meta was expanded in previous versions, with prefixes removed. This maintains support.
407
			return $meta_values[ '_' . $offset ];
408
		}
409
410
		return null;
411
	}
412
}
413