Completed
Push — master ( fb1e2f...9a3784 )
by Justin
25:57
created

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