Completed
Push — master ( 63fb13...1c592b )
by Mike
22:09
created

WC_Order_Item::__construct()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 1
dl 0
loc 11
rs 9.2
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
		'order_item_id' => 0,
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 View Code Duplication
			if ( is_callable( array( $this, "set_$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...
75
				$this->{"set_$key"}( $value );
76
			} else {
77
				$this->_data[ $key ] = $value;
78
			}
79
		}
80
	}
81
82
	/**
83
	 * Type checking
84
	 * @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...
85
	 * @return boolean
86
	 */
87
	public function is_type( $type ) {
88
		return is_array( $type ) ? in_array( $this->get_type(), $type ) : $type === $this->get_type();
89
	}
90
91
	/**
92
	 * Get qty.
93
	 * @return int
94
	 */
95
	public function get_qty() {
96
		return 1;
97
	}
98
99
	/**
100
	 * Get parent order object.
101
	 * @return int
102
	 */
103
	public function get_order() {
104
		if ( ! $this->_order ) {
105
		 	$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...
106
		}
107
		return $this->_order;
108
	}
109
110
	/*
111
	|--------------------------------------------------------------------------
112
	| Getters
113
	|--------------------------------------------------------------------------
114
	*/
115
116
	/**
117
	 * Get order item ID.
118
	 * @return int
119
	 */
120
	public function get_id() {
121
		return $this->get_order_item_id();
122
	}
123
124
	/**
125
	 * Get order ID this meta belongs to.
126
	 * @return int
127
	 */
128
	public function get_order_id() {
129
		return absint( $this->_data['order_id'] );
130
	}
131
132
	/**
133
	 * Get order item ID this meta belongs to.
134
	 * @return int
135
	 */
136
	protected function get_order_item_id() {
137
		return absint( $this->_data['order_item_id'] );
138
	}
139
140
	/**
141
	 * Get order item name.
142
	 * @return string
143
	 */
144
	public function get_name() {
145
		return $this->_data['name'];
146
	}
147
148
	/**
149
	 * Get order item type.
150
	 * @return string
151
	 */
152
	public function get_type() {
153
		return $this->_data['type'];
154
	}
155
156
	/*
157
	|--------------------------------------------------------------------------
158
	| Setters
159
	|--------------------------------------------------------------------------
160
	*/
161
162
	/**
163
	 * Set ID
164
	 * @param int $value
165
	 */
166
	public function set_id( $value ) {
167
		$this->set_order_item_id( $value );
168
	}
169
170
	/**
171
	 * Set order ID.
172
	 * @param int $value
173
	 */
174
	public function set_order_id( $value ) {
175
		$this->_data['order_id'] = absint( $value );
176
	}
177
178
	/**
179
	 * Set order item ID.
180
	 * @param int $value
181
	 */
182
	protected function set_order_item_id( $value ) {
183
		$this->_data['order_item_id'] = absint( $value );
184
	}
185
186
	/**
187
	 * Set order item name.
188
	 * @param string $value
189
	 */
190
	public function set_name( $value ) {
191
		$this->_data['name'] = wc_clean( $value );
192
	}
193
194
	/**
195
	 * Set order item type.
196
	 * @param string $value
197
	 */
198
	protected function set_type( $value ) {
199
		$this->_data['type'] = wc_clean( $value );
200
	}
201
202
	/*
203
	|--------------------------------------------------------------------------
204
	| CRUD methods
205
	|--------------------------------------------------------------------------
206
	|
207
	| Methods which create, read, update and delete data from the database.
208
	|
209
	*/
210
211
	/**
212
	 * Insert data into the database.
213
	 * @since 2.7.0
214
	 */
215 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...
216
		global $wpdb;
217
218
		$wpdb->insert( $wpdb->prefix . 'woocommerce_order_items', array(
219
			'order_item_name' => $this->get_name(),
220
			'order_item_type' => $this->get_type(),
221
			'order_id'        => $this->get_order_id()
222
		) );
223
		$this->set_id( $wpdb->insert_id );
224
225
		do_action( 'woocommerce_new_order_item', $this->get_id(), $this, $this->get_order_id() );
226
	}
227
228
	/**
229
	 * Update data in the database.
230
	 * @since 2.7.0
231
	 */
232 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...
233
		global $wpdb;
234
235
		$wpdb->update( $wpdb->prefix . 'woocommerce_order_items', array(
236
			'order_item_name' => $this->get_name(),
237
			'order_item_type' => $this->get_type(),
238
			'order_id'        => $this->get_order_id()
239
		), array( 'order_item_id' => $this->get_id() ) );
240
241
		do_action( 'woocommerce_update_order_item', $this->get_id(), $this, $this->get_order_id() );
242
	}
243
244
	/**
245
	 * Read from the database.
246
	 * @since 2.7.0
247
	 * @param int|object $item ID of object to read, or already queried object.
248
	 */
249
	public function read( $item ) {
250
		global $wpdb;
251
252
		if ( is_numeric( $item ) && ! empty( $item ) ) {
253
			$data = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d LIMIT 1;", $item ) );
254
		} elseif ( ! empty( $item->order_item_id ) ) {
255
			$data = $item;
256
		} else {
257
			$data = false;
258
		}
259
260
		if ( $data ) {
261
			$this->set_order_id( $data->order_id );
262
			$this->set_id( $data->order_item_id );
263
			$this->set_name( $data->order_item_name );
264
			$this->set_type( $data->order_item_type );
265
			$this->read_meta_data();
266
		}
267
	}
268
269
	/**
270
	 * Save data to the database.
271
	 * @since 2.7.0
272
	 * @return int Item ID
273
	 */
274
	public function save() {
275
		if ( ! $this->get_id() ) {
276
			$this->create();
277
		} else {
278
			$this->update();
279
		}
280
		$this->save_meta_data();
281
282
		return $this->get_id();
283
	}
284
285
	/**
286
	 * Delete data from the database.
287
	 * @since 2.7.0
288
	 */
289
	public function delete() {
290
		if ( $this->get_id() ) {
291
			global $wpdb;
292
			do_action( 'woocommerce_before_delete_order_item', $this->get_id() );
293
			$wpdb->delete( $wpdb->prefix . 'woocommerce_order_items', array( 'order_item_id' => $this->get_id() ) );
294
			$wpdb->delete( $wpdb->prefix . 'woocommerce_order_itemmeta', array( 'order_item_id' => $this->get_id() ) );
295
			do_action( 'woocommerce_delete_order_item', $this->get_id() );
296
		}
297
	}
298
299
	/*
300
	|--------------------------------------------------------------------------
301
	| Meta Data Handling
302
	|--------------------------------------------------------------------------
303
	*/
304
305
	/**
306
	 * Expands things like term slugs before return.
307
	 * @param string $hideprefix (default: _)
308
	 * @return array
309
	 */
310
	public function get_formatted_meta_data( $hideprefix = '_' ) {
311
		$formatted_meta = array();
312
		$meta_data      = $this->get_meta_data();
313
314
		foreach ( $meta_data as $meta ) {
315 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...
316
				continue;
317
			}
318
319
			$attribute_key = urldecode( str_replace( 'attribute_', '', $meta->key ) );
320
			$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...
321
			$display_value = $meta->value;
322
323 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...
324
				$term = get_term_by( 'slug', $meta->value, $attribute_key );
325
				if ( ! is_wp_error( $term ) && is_object( $term ) && $term->name ) {
326
					$display_value = $term->name;
327
				}
328
			}
329
330
			$formatted_meta[ $meta->meta_id ] = (object) array(
331
				'key'           => $meta->key,
332
				'value'         => $meta->key,
333
				'display_key'   => apply_filters( 'woocommerce_order_item_display_meta_key', $display_key ),
334
				'display_value' => apply_filters( 'woocommerce_order_item_display_meta_value', $display_value ),
335
			);
336
		}
337
338
		return $formatted_meta;
339
	}
340
341
	/*
342
	|--------------------------------------------------------------------------
343
	| Array Access Methods
344
	|--------------------------------------------------------------------------
345
	|
346
	| For backwards compat with legacy arrays.
347
	|
348
	*/
349
350
	/**
351
	 * offsetSet for ArrayAccess
352
	 * @param string $offset
353
	 * @param mixed $value
354
	 */
355
	public function offsetSet( $offset, $value ) {
356
		if ( 'item_meta_array' === $offset ) {
357
			foreach ( $value as $meta_id => $meta ) {
358
				$this->update_meta_data( $meta->key, $meta->value, $meta_id );
359
			}
360
			return;
361
		}
362
363
		if ( array_key_exists( $offset, $this->_data ) ) {
364
			$this->_data[ $offset ] = $value;
365
		}
366
367
		$this->update_meta_data( '_' . $offset, $value );
368
	}
369
370
	/**
371
	 * offsetUnset for ArrayAccess
372
	 * @param string $offset
373
	 */
374
	public function offsetUnset( $offset ) {
375
		if ( 'item_meta_array' === $offset || 'item_meta' === $offset ) {
376
			$this->_meta_data = array();
377
			return;
378
		}
379
380
		if ( array_key_exists( $offset, $this->_data ) ) {
381
			unset( $this->_data[ $offset ] );
382
		}
383
384
		$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...
385
	}
386
387
	/**
388
	 * offsetExists for ArrayAccess
389
	 * @param string $offset
390
	 * @return bool
391
	 */
392
	public function offsetExists( $offset ) {
393
		if ( 'item_meta_array' === $offset || 'item_meta' === $offset || array_key_exists( $offset, $this->_data ) ) {
394
			return true;
395
		}
396
		return array_key_exists( '_' . $offset, wp_list_pluck( $this->_meta_data, 'value', 'key' ) );
397
	}
398
399
	/**
400
	 * offsetGet for ArrayAccess
401
	 * @param string $offset
402
	 * @return mixed
403
	 */
404
	public function offsetGet( $offset ) {
405
		if ( 'item_meta_array' === $offset ) {
406
			$return = array();
407
408
			foreach ( $this->_meta_data as $meta ) {
409
				$return[ $meta->meta_id ] = $meta;
410
			}
411
412
			return $return;
413
		}
414
415
		$meta_values = wp_list_pluck( $this->_meta_data, 'value', 'key' );
416
417
		if ( 'item_meta' === $offset ) {
418
			return $meta_values;
419
		} elseif ( array_key_exists( $offset, $this->_data ) ) {
420
			return $this->_data[ $offset ];
421
		} elseif ( array_key_exists( '_' . $offset, $meta_values ) ) {
422
			// Item meta was expanded in previous versions, with prefixes removed. This maintains support.
423
			return $meta_values[ '_' . $offset ];
424
		}
425
426
		return null;
427
	}
428
}
429