Completed
Push — master ( 58161c...221f3e )
by Mike
24:16 queued 12s
created

Abstract_WC_Order_Data_Store_CPT::get_post_status()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 1
dl 0
loc 20
ccs 9
cts 9
cp 1
crap 4
rs 9.6
c 0
b 0
f 0
1
<?php
2
/**
3
 * Abstract_WC_Order_Data_Store_CPT class file.
4
 *
5
 * @package WooCommerce/Classes
6
 */
7
8
if ( ! defined( 'ABSPATH' ) ) {
9
	exit;
10
}
11
12
/**
13
 * Abstract Order Data Store: Stored in CPT.
14
 *
15
 * @version  3.0.0
16
 */
17
abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Data_Store_Interface, WC_Abstract_Order_Data_Store_Interface {
18
19
	/**
20
	 * Internal meta type used to store order data.
21
	 *
22
	 * @var string
23
	 */
24
	protected $meta_type = 'post';
25
26
	/**
27
	 * Data stored in meta keys, but not considered "meta" for an order.
28
	 *
29
	 * @since 3.0.0
30
	 * @var array
31
	 */
32
	protected $internal_meta_keys = array(
33
		'_order_currency',
34
		'_cart_discount',
35
		'_cart_discount_tax',
36
		'_order_shipping',
37
		'_order_shipping_tax',
38
		'_order_tax',
39
		'_order_total',
40
		'_order_version',
41
		'_prices_include_tax',
42
		'_payment_tokens',
43
	);
44
45
	/*
46
	|--------------------------------------------------------------------------
47
	| CRUD Methods
48
	|--------------------------------------------------------------------------
49
	*/
50
51
	/**
52
	 * Method to create a new order in the database.
53
	 *
54
	 * @param WC_Order $order Order object.
55
	 */
56 159
	public function create( &$order ) {
57 159
		$order->set_version( WC_VERSION );
58 159
		$order->set_date_created( current_time( 'timestamp', true ) );
59 159
		$order->set_currency( $order->get_currency() ? $order->get_currency() : get_woocommerce_currency() );
60
61 159
		$id = wp_insert_post(
62 159
			apply_filters(
63 159
				'woocommerce_new_order_data',
64
				array(
65 159
					'post_date'     => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getOffsetTimestamp() ),
66 159
					'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
67 159
					'post_type'     => $order->get_type( 'edit' ),
68 159
					'post_status'   => $this->get_post_status( $order ),
69 159
					'ping_status'   => 'closed',
70 159
					'post_author'   => 1,
71 159
					'post_title'    => $this->get_post_title(),
72 159
					'post_password' => wc_generate_order_key(),
73 159
					'post_parent'   => $order->get_parent_id( 'edit' ),
74 159
					'post_excerpt'  => $this->get_post_excerpt( $order ),
75
				)
76
			),
77 159
			true
78
		);
79
80 159
		if ( $id && ! is_wp_error( $id ) ) {
81 159
			$order->set_id( $id );
82 159
			$this->update_post_meta( $order );
83 159
			$order->save_meta_data();
84 159
			$order->apply_changes();
85 159
			$this->clear_caches( $order );
86
		}
87
	}
88
89
	/**
90
	 * Method to read an order from the database.
91
	 *
92
	 * @param WC_Data $order Order object.
93
	 *
94
	 * @throws Exception If passed order is invalid.
95
	 */
96 86
	public function read( &$order ) {
97 86
		$order->set_defaults();
98 86
		$post_object = get_post( $order->get_id() );
99
100 86
		if ( ! $order->get_id() || ! $post_object || ! in_array( $post_object->post_type, wc_get_order_types(), true ) ) {
101
			throw new Exception( __( 'Invalid order.', 'woocommerce' ) );
102
		}
103
104 86
		$order->set_props(
105
			array(
106 86
				'parent_id'     => $post_object->post_parent,
107 86
				'date_created'  => 0 < $post_object->post_date_gmt ? wc_string_to_timestamp( $post_object->post_date_gmt ) : null,
108 86
				'date_modified' => 0 < $post_object->post_modified_gmt ? wc_string_to_timestamp( $post_object->post_modified_gmt ) : null,
109 86
				'status'        => $post_object->post_status,
110
			)
111
		);
112
113 86
		$this->read_order_data( $order, $post_object );
0 ignored issues
show
Compatibility introduced by
$order of type object<WC_Data> is not a sub-type of object<WC_Order>. It seems like you assume a child class of the class WC_Data to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
114 86
		$order->read_meta_data();
115 86
		$order->set_object_read( true );
116
117
		/**
118
		 * In older versions, discounts may have been stored differently.
119
		 * Update them now so if the object is saved, the correct values are
120
		 * stored. @todo When meta is flattened, handle this during migration.
121
		 */
122 86
		if ( version_compare( $order->get_version( 'edit' ), '2.3.7', '<' ) && $order->get_prices_include_tax( 'edit' ) ) {
123
			$order->set_discount_total( (float) get_post_meta( $order->get_id(), '_cart_discount', true ) - (float) get_post_meta( $order->get_id(), '_cart_discount_tax', true ) );
124
		}
125
	}
126
127
	/**
128
	 * Method to update an order in the database.
129
	 *
130
	 * @param WC_Order $order Order object.
131
	 */
132 119
	public function update( &$order ) {
133 119
		$order->save_meta_data();
134 119
		$order->set_version( WC_VERSION );
135
136 119
		if ( null === $order->get_date_created( 'edit' ) ) {
137
			$order->set_date_created( current_time( 'timestamp', true ) );
138
		}
139
140 119
		$changes = $order->get_changes();
141
142
		// Only update the post when the post data changes.
143 119
		if ( array_intersect( array( 'date_created', 'date_modified', 'status', 'parent_id', 'post_excerpt' ), array_keys( $changes ) ) ) {
144
			$post_data = array(
145 21
				'post_date'         => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getOffsetTimestamp() ),
146 21
				'post_date_gmt'     => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
147 21
				'post_status'       => $this->get_post_status( $order ),
148 21
				'post_parent'       => $order->get_parent_id(),
149 21
				'post_excerpt'      => $this->get_post_excerpt( $order ),
150 21
				'post_modified'     => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $order->get_date_modified( 'edit' )->getOffsetTimestamp() ) : current_time( 'mysql' ),
151 21
				'post_modified_gmt' => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $order->get_date_modified( 'edit' )->getTimestamp() ) : current_time( 'mysql', 1 ),
152
			);
153
154
			/**
155
			 * When updating this object, to prevent infinite loops, use $wpdb
156
			 * to update data, since wp_update_post spawns more calls to the
157
			 * save_post action.
158
			 *
159
			 * This ensures hooks are fired by either WP itself (admin screen save),
160
			 * or an update purely from CRUD.
161
			 */
162 21 View Code Duplication
			if ( doing_action( 'save_post' ) ) {
163
				$GLOBALS['wpdb']->update( $GLOBALS['wpdb']->posts, $post_data, array( 'ID' => $order->get_id() ) );
164
				clean_post_cache( $order->get_id() );
165
			} else {
166 21
				wp_update_post( array_merge( array( 'ID' => $order->get_id() ), $post_data ) );
167
			}
168 21
			$order->read_meta_data( true ); // Refresh internal meta data, in case things were hooked into `save_post` or another WP hook.
169
		}
170 119
		$this->update_post_meta( $order );
171 119
		$order->apply_changes();
172 119
		$this->clear_caches( $order );
173
	}
174
175
	/**
176
	 * Method to delete an order from the database.
177
	 *
178
	 * @param WC_Order $order Order object.
179
	 * @param array    $args Array of args to pass to the delete method.
180
	 *
181
	 * @return void
182
	 */
183 9
	public function delete( &$order, $args = array() ) {
184 9
		$id   = $order->get_id();
185 9
		$args = wp_parse_args(
186 9
			$args,
187
			array(
188 9
				'force_delete' => false,
189
			)
190
		);
191
192 9
		if ( ! $id ) {
193
			return;
194
		}
195
196 9
		if ( $args['force_delete'] ) {
197 7
			wp_delete_post( $id );
198 7
			$order->set_id( 0 );
199 7
			do_action( 'woocommerce_delete_order', $id );
200
		} else {
201 2
			wp_trash_post( $id );
202 2
			$order->set_status( 'trash' );
203 2
			do_action( 'woocommerce_trash_order', $id );
204
		}
205
	}
206
207
	/*
208
	|--------------------------------------------------------------------------
209
	| Additional Methods
210
	|--------------------------------------------------------------------------
211
	*/
212
213
	/**
214
	 * Get the status to save to the post object.
215
	 *
216
	 * Plugins extending the order classes can override this to change the stored status/add prefixes etc.
217
	 *
218
	 * @since 3.6.0
219
	 * @param  WC_order $order Order object.
220
	 * @return string
221
	 */
222 159
	protected function get_post_status( $order ) {
223 159
		$order_status = $order->get_status( 'edit' );
224
225 159
		if ( ! $order_status ) {
226 60
			$order_status = apply_filters( 'woocommerce_default_order_status', 'pending' );
227
		}
228
229 159
		$post_status    = $order_status;
230 159
		$valid_statuses = get_post_stati();
231
232
		// Add a wc- prefix to the status, but exclude some core statuses which should not be prefixed.
233
		// @todo In the future this should only happen based on `wc_is_order_status`, but in order to
234
		// preserve back-compatibility this happens to all statuses except a select few. A doing_it_wrong
235
		// Notice will be needed here, followed by future removal.
236 159
		if ( ! in_array( $post_status, array( 'auto-draft', 'draft', 'trash' ), true ) && in_array( 'wc-' . $post_status, $valid_statuses, true ) ) {
237 159
			$post_status = 'wc-' . $post_status;
238
		}
239
240 159
		return $post_status;
241
	}
242
243
	/**
244
	 * Excerpt for post.
245
	 *
246
	 * @param  WC_order $order Order object.
247
	 * @return string
248
	 */
249 9
	protected function get_post_excerpt( $order ) {
250 9
		return '';
251
	}
252
253
	/**
254
	 * Get a title for the new post type.
255
	 *
256
	 * @return string
257
	 */
258 159
	protected function get_post_title() {
259
		// @codingStandardsIgnoreStart
260
		/* translators: %s: Order date */
261 159
		return sprintf( __( 'Order &ndash; %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) );
262
		// @codingStandardsIgnoreEnd
263
	}
264
265
	/**
266
	 * Read order data. Can be overridden by child classes to load other props.
267
	 *
268
	 * @param WC_Order $order Order object.
269
	 * @param object   $post_object Post object.
270
	 * @since 3.0.0
271
	 */
272 86
	protected function read_order_data( &$order, $post_object ) {
273 86
		$id = $order->get_id();
274
275 86
		$order->set_props(
276
			array(
277 86
				'currency'           => get_post_meta( $id, '_order_currency', true ),
278 86
				'discount_total'     => get_post_meta( $id, '_cart_discount', true ),
279 86
				'discount_tax'       => get_post_meta( $id, '_cart_discount_tax', true ),
280 86
				'shipping_total'     => get_post_meta( $id, '_order_shipping', true ),
281 86
				'shipping_tax'       => get_post_meta( $id, '_order_shipping_tax', true ),
282 86
				'cart_tax'           => get_post_meta( $id, '_order_tax', true ),
283 86
				'total'              => get_post_meta( $id, '_order_total', true ),
284 86
				'version'            => get_post_meta( $id, '_order_version', true ),
285 86
				'prices_include_tax' => metadata_exists( 'post', $id, '_prices_include_tax' ) ? 'yes' === get_post_meta( $id, '_prices_include_tax', true ) : 'yes' === get_option( 'woocommerce_prices_include_tax' ),
286
			)
287
		);
288
289
		// Gets extra data associated with the order if needed.
290 86
		foreach ( $order->get_extra_data_keys() as $key ) {
291 5
			$function = 'set_' . $key;
292 5
			if ( is_callable( array( $order, $function ) ) ) {
293 5
				$order->{$function}( get_post_meta( $order->get_id(), '_' . $key, true ) );
294
			}
295
		}
296
	}
297
298
	/**
299
	 * Helper method that updates all the post meta for an order based on it's settings in the WC_Order class.
300
	 *
301
	 * @param WC_Order $order Order object.
302
	 * @since 3.0.0
303
	 */
304 159
	protected function update_post_meta( &$order ) {
305 159
		$updated_props     = array();
306
		$meta_key_to_props = array(
307 159
			'_order_currency'     => 'currency',
308
			'_cart_discount'      => 'discount_total',
309
			'_cart_discount_tax'  => 'discount_tax',
310
			'_order_shipping'     => 'shipping_total',
311
			'_order_shipping_tax' => 'shipping_tax',
312
			'_order_tax'          => 'cart_tax',
313
			'_order_total'        => 'total',
314
			'_order_version'      => 'version',
315
			'_prices_include_tax' => 'prices_include_tax',
316
		);
317
318 159
		$props_to_update = $this->get_props_to_update( $order, $meta_key_to_props );
319
320 159
		foreach ( $props_to_update as $meta_key => $prop ) {
321 159
			$value = $order->{"get_$prop"}( 'edit' );
322 159
			$value = is_string( $value ) ? wp_slash( $value ) : $value;
323
324 159
			if ( 'prices_include_tax' === $prop ) {
325 159
				$value = $value ? 'yes' : 'no';
326
			}
327
328 159
			$updated = $this->update_or_delete_post_meta( $order, $meta_key, $value );
329
330 159
			if ( $updated ) {
331 159
				$updated_props[] = $prop;
332
			}
333
		}
334
335 159
		do_action( 'woocommerce_order_object_updated_props', $order, $updated_props );
336
	}
337
338
	/**
339
	 * Clear any caches.
340
	 *
341
	 * @param WC_Order $order Order object.
342
	 * @since 3.0.0
343
	 */
344 159
	protected function clear_caches( &$order ) {
345 159
		clean_post_cache( $order->get_id() );
346 159
		wc_delete_shop_order_transients( $order );
0 ignored issues
show
Documentation introduced by
$order is of type object<WC_Order>, but the function expects a integer.

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...
347 159
		wp_cache_delete( 'order-items-' . $order->get_id(), 'orders' );
348
	}
349
350
	/**
351
	 * Read order items of a specific type from the database for this order.
352
	 *
353
	 * @param  WC_Order $order Order object.
354
	 * @param  string   $type Order item type.
355
	 * @return array
356
	 */
357 138
	public function read_items( $order, $type ) {
358
		global $wpdb;
359
360
		// Get from cache if available.
361 138
		$items = 0 < $order->get_id() ? wp_cache_get( 'order-items-' . $order->get_id(), 'orders' ) : false;
362
363 138
		if ( false === $items ) {
364 138
			$items = $wpdb->get_results(
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
365 138
				$wpdb->prepare( "SELECT order_item_type, order_item_id, order_id, order_item_name FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d ORDER BY order_item_id;", $order->get_id() )
366
			);
367 138
			foreach ( $items as $item ) {
368 55
				wp_cache_set( 'item-' . $item->order_item_id, $item, 'order-items' );
369
			}
370 138
			if ( 0 < $order->get_id() ) {
371 115
				wp_cache_set( 'order-items-' . $order->get_id(), $items, 'orders' );
372
			}
373
		}
374
375 138
		$items = wp_list_filter( $items, array( 'order_item_type' => $type ) );
376
377 138
		if ( ! empty( $items ) ) {
378 52
			$items = array_map( array( 'WC_Order_Factory', 'get_order_item' ), array_combine( wp_list_pluck( $items, 'order_item_id' ), $items ) );
379
		} else {
380 136
			$items = array();
381
		}
382
383 138
		return $items;
384
	}
385
386
	/**
387
	 * Remove all line items (products, coupons, shipping, taxes) from the order.
388
	 *
389
	 * @param WC_Order $order Order object.
390
	 * @param string   $type Order item type. Default null.
391
	 */
392 1
	public function delete_items( $order, $type = null ) {
393
		global $wpdb;
394 1
		if ( ! empty( $type ) ) {
395
			$wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id AND items.order_id = %d AND items.order_item_type = %s", $order->get_id(), $type ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
396
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $order->get_id(), $type ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
397
		} else {
398 1
			$wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id and items.order_id = %d", $order->get_id() ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
399 1
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $order->get_id() ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
400
		}
401 1
		$this->clear_caches( $order );
402
	}
403
404
	/**
405
	 * Get token ids for an order.
406
	 *
407
	 * @param WC_Order $order Order object.
408
	 * @return array
409
	 */
410 5
	public function get_payment_token_ids( $order ) {
411 5
		$token_ids = array_filter( (array) get_post_meta( $order->get_id(), '_payment_tokens', true ) );
412 5
		return $token_ids;
413
	}
414
415
	/**
416
	 * Update token ids for an order.
417
	 *
418
	 * @param WC_Order $order Order object.
419
	 * @param array    $token_ids Payment token ids.
420
	 */
421 3
	public function update_payment_token_ids( $order, $token_ids ) {
422 3
		update_post_meta( $order->get_id(), '_payment_tokens', $token_ids );
423
	}
424
}
425