Completed
Pull Request — master (#11208)
by Mike
09:06
created

WC_Order_Item::get_formatted_meta_data()   C

Complexity

Conditions 11
Paths 5

Size

Total Lines 30
Code Lines 19

Duplication

Lines 9
Ratio 30 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 9
loc 30
rs 5.2653
cc 11
eloc 19
nc 5
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
	 * Stores meta in cache for future reads.
33
	 * A group must be set to to enable caching.
34
	 * @var string
35
	 */
36
	protected $_cache_group = 'order_itemmeta';
37
38
	/**
39
	 * Meta type. This should match up with
40
	 * the types avaiable at https://codex.wordpress.org/Function_Reference/add_metadata.
41
	 * WP defines 'post', 'user', 'comment', and 'term'.
42
	 */
43
	protected $_meta_type = 'order_item';
44
45
	/**
46
	 * Constructor.
47
	 * @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...
48
	 */
49
	public function __construct( $item = 0 ) {
50
		if ( $item instanceof WC_Order_Item ) {
51
			if ( $this->is_type( $item->get_type() ) ) {
52
				$this->set_all( $item->get_data() );
53
			}
54
		} elseif ( is_array( $item ) ) {
55
			$this->set_all( $item );
56
		} else {
57
			$this->read( $item );
58
		}
59
	}
60
61
	/**
62
	 * Set all data based on input array.
63
	 * @param array $data
64
	 * @access private
65
	 */
66
	public function set_all( $data ) {
67
		foreach ( $data as $key => $value ) {
68 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...
69
				$this->{"set_$key"}( $value );
70
			} else {
71
				$this->_data[ $key ] = $value;
72
			}
73
		}
74
	}
75
76
	/**
77
	 * Type checking
78
	 * @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...
79
	 * @return boolean
80
	 */
81
	public function is_type( $type ) {
82
		return is_array( $type ) ? in_array( $this->get_type(), $type ) : $type === $this->get_type();
83
	}
84
85
	/**
86
	 * Get qty.
87
	 * @return int
88
	 */
89
	public function get_qty() {
90
		return 1;
91
	}
92
93
	/*
94
	|--------------------------------------------------------------------------
95
	| Getters
96
	|--------------------------------------------------------------------------
97
	*/
98
99
	/**
100
	 * Get order item ID.
101
	 * @return int
102
	 */
103
	public function get_id() {
104
		return $this->get_order_item_id();
105
	}
106
107
	/**
108
	 * Get order ID this meta belongs to.
109
	 * @return int
110
	 */
111
	public function get_order_id() {
112
		return absint( $this->_data['order_id'] );
113
	}
114
115
	/**
116
	 * Get order item ID this meta belongs to.
117
	 * @return int
118
	 */
119
	public function get_order_item_id() {
120
		return absint( $this->_data['order_item_id'] );
121
	}
122
123
	/**
124
	 * Get order item name.
125
	 * @return string
126
	 */
127
	public function get_name() {
128
		return $this->_data['name'];
129
	}
130
131
	/**
132
	 * Get order item type.
133
	 * @return string
134
	 */
135
	public function get_type() {
136
		return $this->_data['type'];
137
	}
138
139
	/*
140
	|--------------------------------------------------------------------------
141
	| Setters
142
	|--------------------------------------------------------------------------
143
	*/
144
145
	/**
146
	 * Set ID
147
	 * @param int $value
148
	 */
149
	public function set_id( $value ) {
150
		$this->set_order_item_id( $value );
151
	}
152
153
	/**
154
	 * Set order ID.
155
	 * @param int $value
156
	 */
157
	public function set_order_id( $value ) {
158
		$this->_data['order_id'] = absint( $value );
159
	}
160
161
	/**
162
	 * Set order item ID.
163
	 * @param int $value
164
	 */
165
	public function set_order_item_id( $value ) {
166
		$this->_data['order_item_id'] = absint( $value );
167
	}
168
169
	/**
170
	 * Set order item name.
171
	 * @param string $value
172
	 */
173
	public function set_name( $value ) {
174
		$this->_data['name'] = wc_clean( $value );
175
	}
176
177
	/**
178
	 * Set order item type.
179
	 * @param string $value
180
	 */
181
	public function set_type( $value ) {
182
		$this->_data['type'] = wc_clean( $value );
183
184
	}
185
186
	/*
187
	|--------------------------------------------------------------------------
188
	| CRUD methods
189
	|--------------------------------------------------------------------------
190
	|
191
	| Methods which create, read, update and delete data from the database.
192
	|
193
	*/
194
195
	/**
196
	 * Insert data into the database.
197
	 * @since 2.7.0
198
	 */
199 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...
200
		global $wpdb;
201
202
		$wpdb->insert( $wpdb->prefix . 'woocommerce_order_items', array(
203
			'order_item_name' => $this->get_name(),
204
			'order_item_type' => $this->get_type(),
205
			'order_id'        => $this->get_order_id()
206
		) );
207
		$this->set_id( $wpdb->insert_id );
208
209
		do_action( 'woocommerce_new_order_item', $this->get_id(), $this, $this->get_order_id() );
210
	}
211
212
	/**
213
	 * Update data in the database.
214
	 * @since 2.7.0
215
	 */
216 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...
217
		global $wpdb;
218
219
		$wpdb->update( $wpdb->prefix . 'woocommerce_order_items', array(
220
			'order_item_name' => $this->get_name(),
221
			'order_item_type' => $this->get_type(),
222
			'order_id'        => $this->get_order_id()
223
		), array( 'order_item_id' => $this->get_id() ) );
224
225
		do_action( 'woocommerce_update_order_item', $this->get_id(), $this, $this->get_order_id() );
226
	}
227
228
	/**
229
	 * Read from the database.
230
	 * @since 2.7.0
231
	 * @param int|object $item ID of object to read, or already queried object.
232
	 */
233
	public function read( $item ) {
234
		global $wpdb;
235
236
		if ( is_numeric( $item ) && ! empty( $item ) ) {
237
			$data = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d LIMIT 1;", $item ) );
238
		} elseif ( ! empty( $item->order_item_id ) ) {
239
			$data = $item;
240
		} else {
241
			$data = false;
242
		}
243
244
		if ( $data ) {
245
			$this->set_order_id( $data->order_id );
246
			$this->set_id( $data->order_item_id );
247
			$this->set_name( $data->order_item_name );
248
			$this->set_type( $data->order_item_type );
249
			$this->read_meta_data();
250
		}
251
	}
252
253
	/**
254
	 * Save data to the database.
255
	 * @since 2.7.0
256
	 * @return int Item ID
257
	 */
258
	public function save() {
259
		if ( ! $this->get_id() ) {
260
			$this->create();
261
		} else {
262
			$this->update();
263
		}
264
		$this->save_meta_data();
265
266
		return $this->get_id();
267
	}
268
269
	/**
270
	 * Delete data from the database.
271
	 * @since 2.7.0
272
	 */
273
	public function delete() {
274
		if ( $this->get_id() ) {
275
			global $wpdb;
276
			do_action( 'woocommerce_before_delete_order_item', $this->get_id() );
277
			$wpdb->delete( $wpdb->prefix . 'woocommerce_order_items', array( 'order_item_id' => $this->get_id() ) );
278
			$wpdb->delete( $wpdb->prefix . 'woocommerce_order_itemmeta', array( 'order_item_id' => $this->get_id() ) );
279
			do_action( 'woocommerce_delete_order_item', $this->get_id() );
280
		}
281
	}
282
283
	/*
284
	|--------------------------------------------------------------------------
285
	| Meta Data Handling
286
	|--------------------------------------------------------------------------
287
	*/
288
289
	/**
290
	 * Expands things like term slugs before return.
291
	 * @param string $hideprefix (default: _)
292
	 * @return array
293
	 */
294
	public function get_formatted_meta_data( $hideprefix = '_' ) {
295
		$formatted_meta = array();
296
		$meta_data      = $this->get_meta_data();
297
298
		foreach ( $meta_data as $meta ) {
299 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...
300
				continue;
301
			}
302
303
			$attribute_key = urldecode( 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->key,
317
				'display_key'   => apply_filters( 'woocommerce_order_item_display_meta_key', $display_key ),
318
				'display_value' => apply_filters( 'woocommerce_order_item_display_meta_value', $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