Completed
Pull Request — master (#11762)
by Mike
35:47
created

WC_Order_Item::set_all()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 7
rs 9.4285

1 Method

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