Completed
Push — master ( 15aa29...17da96 )
by Claudio
18:39 queued 11s
created

includes/abstracts/abstract-wc-data.php (7 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Abstract Data.
4
 *
5
 * Handles generic data interaction which is implemented by
6
 * the different data store classes.
7
 *
8
 * @class       WC_Data
9
 * @version     3.0.0
10
 * @package     WooCommerce/Classes
11
 */
12
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit;
15
}
16
17
/**
18
 * Abstract WC Data Class
19
 *
20
 * Implemented by classes using the same CRUD(s) pattern.
21
 *
22
 * @version  2.6.0
23
 * @package  WooCommerce/Abstracts
24
 */
25
abstract class WC_Data {
26
27
	/**
28
	 * ID for this object.
29
	 *
30
	 * @since 3.0.0
31
	 * @var int
32
	 */
33
	protected $id = 0;
34
35
	/**
36
	 * Core data for this object. Name value pairs (name + default value).
37
	 *
38
	 * @since 3.0.0
39
	 * @var array
40
	 */
41
	protected $data = array();
42
43
	/**
44
	 * Core data changes for this object.
45
	 *
46
	 * @since 3.0.0
47
	 * @var array
48
	 */
49
	protected $changes = array();
50
51
	/**
52
	 * This is false until the object is read from the DB.
53
	 *
54
	 * @since 3.0.0
55
	 * @var bool
56
	 */
57
	protected $object_read = false;
58
59
	/**
60
	 * This is the name of this object type.
61
	 *
62
	 * @since 3.0.0
63
	 * @var string
64
	 */
65
	protected $object_type = 'data';
66
67
	/**
68
	 * Extra data for this object. Name value pairs (name + default value).
69
	 * Used as a standard way for sub classes (like product types) to add
70
	 * additional information to an inherited class.
71
	 *
72
	 * @since 3.0.0
73
	 * @var array
74
	 */
75
	protected $extra_data = array();
76
77
	/**
78
	 * Set to _data on construct so we can track and reset data if needed.
79
	 *
80
	 * @since 3.0.0
81
	 * @var array
82
	 */
83
	protected $default_data = array();
84
85
	/**
86
	 * Contains a reference to the data store for this class.
87
	 *
88
	 * @since 3.0.0
89
	 * @var object
90
	 */
91
	protected $data_store;
92
93
	/**
94
	 * Stores meta in cache for future reads.
95
	 * A group must be set to to enable caching.
96
	 *
97
	 * @since 3.0.0
98
	 * @var string
99
	 */
100
	protected $cache_group = '';
101
102
	/**
103
	 * Stores additional meta data.
104
	 *
105
	 * @since 3.0.0
106
	 * @var array
107
	 */
108
	protected $meta_data = null;
109
110
	/**
111
	 * Default constructor.
112
	 *
113
	 * @param int|object|array $read ID to load from the DB (optional) or already queried data.
114
	 */
115 726
	public function __construct( $read = 0 ) {
116 726
		$this->data         = array_merge( $this->data, $this->extra_data );
117 726
		$this->default_data = $this->data;
118
	}
119
120
	/**
121
	 * Only store the object ID to avoid serializing the data object instance.
122
	 *
123
	 * @return array
124
	 */
125
	public function __sleep() {
126
		return array( 'id' );
127
	}
128
129
	/**
130
	 * Re-run the constructor with the object ID.
131
	 *
132
	 * If the object no longer exists, remove the ID.
133
	 */
134
	public function __wakeup() {
135
		try {
136
			$this->__construct( absint( $this->id ) );
137
		} catch ( Exception $e ) {
138
			$this->set_id( 0 );
139
			$this->set_object_read( true );
140
		}
141
	}
142
143
	/**
144
	 * When the object is cloned, make sure meta is duplicated correctly.
145
	 *
146
	 * @since 3.0.2
147
	 */
148
	public function __clone() {
149
		$this->maybe_read_meta_data();
150
		if ( ! empty( $this->meta_data ) ) {
151
			foreach ( $this->meta_data as $array_key => $meta ) {
152
				$this->meta_data[ $array_key ] = clone $meta;
153
				if ( ! empty( $meta->id ) ) {
154
					$this->meta_data[ $array_key ]->id = null;
155
				}
156
			}
157
		}
158
	}
159
160
	/**
161
	 * Get the data store.
162
	 *
163
	 * @since  3.0.0
164
	 * @return object
165
	 */
166 23
	public function get_data_store() {
167 23
		return $this->data_store;
168
	}
169
170
	/**
171
	 * Returns the unique ID for this object.
172
	 *
173
	 * @since  2.6.0
174
	 * @return int
175
	 */
176 989
	public function get_id() {
177 989
		return $this->id;
178
	}
179
180
	/**
181
	 * Delete an object, set the ID to 0, and return result.
182
	 *
183
	 * @since  2.6.0
184
	 * @param  bool $force_delete Should the date be deleted permanently.
185
	 * @return bool result
186
	 */
187 78 View Code Duplication
	public function delete( $force_delete = false ) {
0 ignored issues
show
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...
188 78
		if ( $this->data_store ) {
189 78
			$this->data_store->delete( $this, array( 'force_delete' => $force_delete ) );
190 78
			$this->set_id( 0 );
191 78
			return true;
192
		}
193
		return false;
194
	}
195
196
	/**
197
	 * Save should create or update based on object existence.
198
	 *
199
	 * @since  2.6.0
200
	 * @return int
201
	 */
202 228 View Code Duplication
	public function save() {
0 ignored issues
show
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...
203 228
		if ( $this->data_store ) {
204
			// Trigger action before saving to the DB. Allows you to adjust object props before save.
205 228
			do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
206
207 228
			if ( $this->get_id() ) {
208 141
				$this->data_store->update( $this );
209
			} else {
210 217
				$this->data_store->create( $this );
211
			}
212
		}
213 228
		return $this->get_id();
214
	}
215
216
	/**
217
	 * Change data to JSON format.
218
	 *
219
	 * @since  2.6.0
220
	 * @return string Data in JSON format.
221
	 */
222 2
	public function __toString() {
223 2
		return wp_json_encode( $this->get_data() );
224
	}
225
226
	/**
227
	 * Returns all data for this object.
228
	 *
229
	 * @since  2.6.0
230
	 * @return array
231
	 */
232 60
	public function get_data() {
233 60
		return array_merge( array( 'id' => $this->get_id() ), $this->data, array( 'meta_data' => $this->get_meta_data() ) );
234
	}
235
236
	/**
237
	 * Returns array of expected data keys for this object.
238
	 *
239
	 * @since   3.0.0
240
	 * @return array
241
	 */
242 338
	public function get_data_keys() {
243 338
		return array_keys( $this->data );
244
	}
245
246
	/**
247
	 * Returns all "extra" data keys for an object (for sub objects like product types).
248
	 *
249
	 * @since  3.0.0
250
	 * @return array
251
	 */
252 445
	public function get_extra_data_keys() {
253 445
		return array_keys( $this->extra_data );
254
	}
255
256
	/**
257
	 * Filter null meta values from array.
258
	 *
259
	 * @since  3.0.0
260
	 * @param mixed $meta Meta value to check.
261
	 * @return bool
262
	 */
263 36
	protected function filter_null_meta( $meta ) {
264 36
		return ! is_null( $meta->value );
265
	}
266
267
	/**
268
	 * Get All Meta Data.
269
	 *
270
	 * @since 2.6.0
271
	 * @return array of objects.
272
	 */
273 168
	public function get_meta_data() {
274 168
		$this->maybe_read_meta_data();
275 168
		return array_values( array_filter( $this->meta_data, array( $this, 'filter_null_meta' ) ) );
276
	}
277
278
	/**
279
	 * Check if the key is an internal one.
280
	 *
281
	 * @since  3.2.0
282
	 * @param  string $key Key to check.
283
	 * @return bool   true if it's an internal key, false otherwise
284
	 */
285 82
	protected function is_internal_meta_key( $key ) {
286 82
		$internal_meta_key = ! empty( $key ) && $this->data_store && in_array( $key, $this->data_store->get_internal_meta_keys(), true );
287
288 82
		if ( ! $internal_meta_key ) {
289 82
			return false;
290
		}
291
292
		$has_setter_or_getter = is_callable( array( $this, 'set_' . $key ) ) || is_callable( array( $this, 'get_' . $key ) );
293
294
		if ( ! $has_setter_or_getter ) {
295
			return false;
296
		}
297
		/* translators: %s: $key Key to check */
298
		wc_doing_it_wrong( __FUNCTION__, sprintf( __( 'Generic add/update/get meta methods should not be used for internal meta data, including "%s". Use getters and setters.', 'woocommerce' ), $key ), '3.2.0' );
299
300
		return true;
301
	}
302
303
	/**
304
	 * Get Meta Data by Key.
305
	 *
306
	 * @since  2.6.0
307
	 * @param  string $key Meta Key.
308
	 * @param  bool   $single return first found meta with key, or all with $key.
309
	 * @param  string $context What the value is for. Valid values are view and edit.
310
	 * @return mixed
311
	 */
312 63
	public function get_meta( $key = '', $single = true, $context = 'view' ) {
313 63
		if ( $this->is_internal_meta_key( $key ) ) {
314
			$function = 'get_' . $key;
315
316
			if ( is_callable( array( $this, $function ) ) ) {
317
				return $this->{$function}();
318
			}
319
		}
320
321 63
		$this->maybe_read_meta_data();
322 63
		$meta_data  = $this->get_meta_data();
323 63
		$array_keys = array_keys( wp_list_pluck( $meta_data, 'key' ), $key, true );
324 63
		$value      = $single ? '' : array();
325
326 63
		if ( ! empty( $array_keys ) ) {
327
			// We don't use the $this->meta_data property directly here because we don't want meta with a null value (i.e. meta which has been deleted via $this->delete_meta_data()).
328 22
			if ( $single ) {
329 22
				$value = $meta_data[ current( $array_keys ) ]->value;
330
			} else {
331 1
				$value = array_intersect_key( $meta_data, array_flip( $array_keys ) );
332
			}
333
334 22
			if ( 'view' === $context ) {
335 22
				$value = apply_filters( $this->get_hook_prefix() . $key, $value, $this );
336
			}
337
		}
338
339 63
		return $value;
340
	}
341
342
	/**
343
	 * See if meta data exists, since get_meta always returns a '' or array().
344
	 *
345
	 * @since  3.0.0
346
	 * @param  string $key Meta Key.
347
	 * @return boolean
348
	 */
349 1
	public function meta_exists( $key = '' ) {
350 1
		$this->maybe_read_meta_data();
351 1
		$array_keys = wp_list_pluck( $this->get_meta_data(), 'key' );
352 1
		return in_array( $key, $array_keys, true );
353
	}
354
355
	/**
356
	 * Set all meta data from array.
357
	 *
358
	 * @since 2.6.0
359
	 * @param array $data Key/Value pairs.
360
	 */
361 1
	public function set_meta_data( $data ) {
362 1
		if ( ! empty( $data ) && is_array( $data ) ) {
363 1
			$this->maybe_read_meta_data();
364 1
			foreach ( $data as $meta ) {
365 1
				$meta = (array) $meta;
366 1
				if ( isset( $meta['key'], $meta['value'], $meta['id'] ) ) {
367 1
					$this->meta_data[] = new WC_Meta_Data(
368
						array(
369 1
							'id'    => $meta['id'],
370 1
							'key'   => $meta['key'],
371 1
							'value' => $meta['value'],
372
						)
373
					);
374
				}
375
			}
376
		}
377
	}
378
379
	/**
380
	 * Add meta data.
381
	 *
382
	 * @since 2.6.0
383
	 *
384
	 * @param string       $key Meta key.
385
	 * @param string|array $value Meta value.
386
	 * @param bool         $unique Should this be a unique key?.
387
	 */
388 37
	public function add_meta_data( $key, $value, $unique = false ) {
389 37 View Code Duplication
		if ( $this->is_internal_meta_key( $key ) ) {
390
			$function = 'set_' . $key;
391
392
			if ( is_callable( array( $this, $function ) ) ) {
393
				return $this->{$function}( $value );
394
			}
395
		}
396
397 37
		$this->maybe_read_meta_data();
398 37
		if ( $unique ) {
399 28
			$this->delete_meta_data( $key );
400
		}
401 37
		$this->meta_data[] = new WC_Meta_Data(
402
			array(
403 37
				'key'   => $key,
404 37
				'value' => $value,
405
			)
406
		);
407
	}
408
409
	/**
410
	 * Update meta data by key or ID, if provided.
411
	 *
412
	 * @since  2.6.0
413
	 *
414
	 * @param  string       $key Meta key.
415
	 * @param  string|array $value Meta value.
416
	 * @param  int          $meta_id Meta ID.
417
	 */
418 5
	public function update_meta_data( $key, $value, $meta_id = 0 ) {
419 5 View Code Duplication
		if ( $this->is_internal_meta_key( $key ) ) {
420
			$function = 'set_' . $key;
421
422
			if ( is_callable( array( $this, $function ) ) ) {
423
				return $this->{$function}( $value );
424
			}
425
		}
426
427 5
		$this->maybe_read_meta_data();
428
429 5
		$array_key = false;
430
431 5
		if ( $meta_id ) {
432 2
			$array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id, true );
433 2
			$array_key  = $array_keys ? current( $array_keys ) : false;
434
		} else {
435
			// Find matches by key.
436 3
			$matches = array();
437 3
			foreach ( $this->meta_data as $meta_data_array_key => $meta ) {
438 2
				if ( $meta->key === $key ) {
439 1
					$matches[] = $meta_data_array_key;
440
				}
441
			}
442
443 3
			if ( ! empty( $matches ) ) {
444
				// Set matches to null so only one key gets the new value.
445 1
				foreach ( $matches as $meta_data_array_key ) {
446 1
					$this->meta_data[ $meta_data_array_key ]->value = null;
447
				}
448 1
				$array_key = current( $matches );
449
			}
450
		}
451
452 5
		if ( false !== $array_key ) {
453 1
			$meta        = $this->meta_data[ $array_key ];
454 1
			$meta->key   = $key;
455 1
			$meta->value = $value;
456
		} else {
457 4
			$this->add_meta_data( $key, $value, true );
458
		}
459
	}
460
461
	/**
462
	 * Delete meta data.
463
	 *
464
	 * @since 2.6.0
465
	 * @param string $key Meta key.
466
	 */
467 31 View Code Duplication
	public function delete_meta_data( $key ) {
0 ignored issues
show
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...
468 31
		$this->maybe_read_meta_data();
469 31
		$array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key, true );
470
471 31
		if ( $array_keys ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $array_keys of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
472 4
			foreach ( $array_keys as $array_key ) {
473 4
				$this->meta_data[ $array_key ]->value = null;
474
			}
475
		}
476
	}
477
478
	/**
479
	 * Delete meta data.
480
	 *
481
	 * @since 2.6.0
482
	 * @param int $mid Meta ID.
483
	 */
484 1 View Code Duplication
	public function delete_meta_data_by_mid( $mid ) {
0 ignored issues
show
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...
485 1
		$this->maybe_read_meta_data();
486 1
		$array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), (int) $mid, true );
487
488 1
		if ( $array_keys ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $array_keys of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
489 1
			foreach ( $array_keys as $array_key ) {
490 1
				$this->meta_data[ $array_key ]->value = null;
491
			}
492
		}
493
	}
494
495
	/**
496
	 * Read meta data if null.
497
	 *
498
	 * @since 3.0.0
499
	 */
500 177
	protected function maybe_read_meta_data() {
501 177
		if ( is_null( $this->meta_data ) ) {
502 108
			$this->read_meta_data();
503
		}
504
	}
505
506
	/**
507
	 * Read Meta Data from the database. Ignore any internal properties.
508
	 * Uses it's own caches because get_metadata does not provide meta_ids.
509
	 *
510
	 * @since 2.6.0
511
	 * @param bool $force_read True to force a new DB read (and update cache).
512
	 */
513 664
	public function read_meta_data( $force_read = false ) {
514 664
		$this->meta_data = array();
515 664
		$cache_loaded    = false;
516
517 664
		if ( ! $this->get_id() ) {
518 475
			return;
519
		}
520
521 338
		if ( ! $this->data_store ) {
522
			return;
523
		}
524
525 338 View Code Duplication
		if ( ! empty( $this->cache_group ) ) {
526
			// Prefix by group allows invalidation by group until https://core.trac.wordpress.org/ticket/4476 is implemented.
527 245
			$cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $this->get_id() ) . 'object_meta_' . $this->get_id();
528
		}
529
530 338
		if ( ! $force_read ) {
531 328
			if ( ! empty( $this->cache_group ) ) {
532 235
				$cached_meta  = wp_cache_get( $cache_key, $this->cache_group );
533 235
				$cache_loaded = ! empty( $cached_meta );
534
			}
535
		}
536
537 338
		$raw_meta_data = $cache_loaded ? $cached_meta : $this->data_store->read_meta( $this );
538 338
		if ( $raw_meta_data ) {
539 59
			foreach ( $raw_meta_data as $meta ) {
540 59
				$this->meta_data[] = new WC_Meta_Data(
541
					array(
542 59
						'id'    => (int) $meta->meta_id,
543 59
						'key'   => $meta->meta_key,
544 59
						'value' => maybe_unserialize( $meta->meta_value ),
545
					)
546
				);
547
			}
548
549 59
			if ( ! $cache_loaded && ! empty( $this->cache_group ) ) {
550 14
				wp_cache_set( $cache_key, $raw_meta_data, $this->cache_group );
551
			}
552
		}
553
	}
554
555
	/**
556
	 * Update Meta Data in the database.
557
	 *
558
	 * @since 2.6.0
559
	 */
560 585
	public function save_meta_data() {
561 585
		if ( ! $this->data_store || is_null( $this->meta_data ) ) {
562 564
			return;
563
		}
564 97
		foreach ( $this->meta_data as $array_key => $meta ) {
565 44
			if ( is_null( $meta->value ) ) {
566 2
				if ( ! empty( $meta->id ) ) {
567 2
					$this->data_store->delete_meta( $this, $meta );
568 2
					unset( $this->meta_data[ $array_key ] );
569
				}
570 44
			} elseif ( empty( $meta->id ) ) {
571 31
				$meta->id = $this->data_store->add_meta( $this, $meta );
572 31
				$meta->apply_changes();
573
			} else {
574 21
				if ( $meta->get_changes() ) {
575 2
					$this->data_store->update_meta( $this, $meta );
576 2
					$meta->apply_changes();
577
				}
578
			}
579
		}
580 97 View Code Duplication
		if ( ! empty( $this->cache_group ) ) {
581 62
			$cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $this->get_id() ) . 'object_meta_' . $this->get_id();
582 62
			wp_cache_delete( $cache_key, $this->cache_group );
583
		}
584
	}
585
586
	/**
587
	 * Set ID.
588
	 *
589
	 * @since 3.0.0
590
	 * @param int $id ID.
591
	 */
592 870
	public function set_id( $id ) {
593 870
		$this->id = absint( $id );
594
	}
595
596
	/**
597
	 * Set all props to default values.
598
	 *
599
	 * @since 3.0.0
600
	 */
601 454
	public function set_defaults() {
602 454
		$this->data    = $this->default_data;
603 454
		$this->changes = array();
604 454
		$this->set_object_read( false );
605
	}
606
607
	/**
608
	 * Set object read property.
609
	 *
610
	 * @since 3.0.0
611
	 * @param boolean $read Should read?.
612
	 */
613 988
	public function set_object_read( $read = true ) {
614 988
		$this->object_read = (bool) $read;
615
	}
616
617
	/**
618
	 * Get object read property.
619
	 *
620
	 * @since  3.0.0
621
	 * @return boolean
622
	 */
623 666
	public function get_object_read() {
624 666
		return (bool) $this->object_read;
625
	}
626
627
	/**
628
	 * Set a collection of props in one go, collect any errors, and return the result.
629
	 * Only sets using public methods.
630
	 *
631
	 * @since  3.0.0
632
	 *
633
	 * @param array  $props Key value pairs to set. Key is the prop and should map to a setter function name.
634
	 * @param string $context In what context to run this.
635
	 *
636
	 * @return bool|WP_Error
637
	 */
638 501
	public function set_props( $props, $context = 'set' ) {
0 ignored issues
show
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
639 501
		$errors = false;
640
641 501
		foreach ( $props as $prop => $value ) {
642
			try {
643
				/**
644
				 * Checks if the prop being set is allowed, and the value is not null.
645
				 */
646 501
				if ( is_null( $value ) || in_array( $prop, array( 'prop', 'date_prop', 'meta_data' ), true ) ) {
647 422
					continue;
648
				}
649 501
				$setter = "set_$prop";
650
651 501
				if ( is_callable( array( $this, $setter ) ) ) {
652 501
					$this->{$setter}( $value );
653
				}
654 92
			} catch ( WC_Data_Exception $e ) {
655 92
				if ( ! $errors ) {
656 92
					$errors = new WP_Error();
657
				}
658 92
				$errors->add( $e->getErrorCode(), $e->getMessage() );
659
			}
660
		}
661
662 501
		return $errors && count( $errors->get_error_codes() ) ? $errors : true;
663
	}
664
665
	/**
666
	 * Sets a prop for a setter method.
667
	 *
668
	 * This stores changes in a special array so we can track what needs saving
669
	 * the the DB later.
670
	 *
671
	 * @since 3.0.0
672
	 * @param string $prop Name of prop to set.
673
	 * @param mixed  $value Value of the prop.
674
	 */
675 929
	protected function set_prop( $prop, $value ) {
676 929
		if ( array_key_exists( $prop, $this->data ) ) {
677 929
			if ( true === $this->object_read ) {
678 683 View Code Duplication
				if ( $value !== $this->data[ $prop ] || array_key_exists( $prop, $this->changes ) ) {
679 683
					$this->changes[ $prop ] = $value;
680
				}
681
			} else {
682 773
				$this->data[ $prop ] = $value;
683
			}
684
		}
685
	}
686
687
	/**
688
	 * Return data changes only.
689
	 *
690
	 * @since 3.0.0
691
	 * @return array
692
	 */
693 569
	public function get_changes() {
694 569
		return $this->changes;
695
	}
696
697
	/**
698
	 * Merge changes with data and clear.
699
	 *
700
	 * @since 3.0.0
701
	 */
702 599
	public function apply_changes() {
703 599
		$this->data    = array_replace_recursive( $this->data, $this->changes ); // @codingStandardsIgnoreLine
704 599
		$this->changes = array();
705
	}
706
707
	/**
708
	 * Prefix for action and filter hooks on data.
709
	 *
710
	 * @since  3.0.0
711
	 * @return string
712
	 */
713 633
	protected function get_hook_prefix() {
714 633
		return 'woocommerce_' . $this->object_type . '_get_';
715
	}
716
717
	/**
718
	 * Gets a prop for a getter method.
719
	 *
720
	 * Gets the value from either current pending changes, or the data itself.
721
	 * Context controls what happens to the value before it's returned.
722
	 *
723
	 * @since  3.0.0
724
	 * @param  string $prop Name of prop to get.
725
	 * @param  string $context What the value is for. Valid values are view and edit.
726
	 * @return mixed
727
	 */
728 724
	protected function get_prop( $prop, $context = 'view' ) {
729 724
		$value = null;
730
731 724
		if ( array_key_exists( $prop, $this->data ) ) {
732 724
			$value = array_key_exists( $prop, $this->changes ) ? $this->changes[ $prop ] : $this->data[ $prop ];
733
734 724
			if ( 'view' === $context ) {
735 712
				$value = apply_filters( $this->get_hook_prefix() . $prop, $value, $this );
736
			}
737
		}
738
739 724
		return $value;
740
	}
741
742
	/**
743
	 * Sets a date prop whilst handling formatting and datetime objects.
744
	 *
745
	 * @since 3.0.0
746
	 * @param string         $prop Name of prop to set.
747
	 * @param string|integer $value Value of the prop.
748
	 */
749 556
	protected function set_date_prop( $prop, $value ) {
750
		try {
751 556
			if ( empty( $value ) ) {
752 224
				$this->set_prop( $prop, null );
753 224
				return;
754
			}
755
756 555
			if ( is_a( $value, 'WC_DateTime' ) ) {
757
				$datetime = $value;
758 555
			} elseif ( is_numeric( $value ) ) {
759
				// Timestamps are handled as UTC timestamps in all cases.
760 543
				$datetime = new WC_DateTime( "@{$value}", new DateTimeZone( 'UTC' ) );
761
			} else {
762
				// Strings are defined in local WP timezone. Convert to UTC.
763 87 View Code Duplication
				if ( 1 === preg_match( '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|((-|\+)\d{2}:\d{2}))$/', $value, $date_bits ) ) {
764 1
					$offset    = ! empty( $date_bits[7] ) ? iso8601_timezone_to_offset( $date_bits[7] ) : wc_timezone_offset();
765 1
					$timestamp = gmmktime( $date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1] ) - $offset;
766
				} else {
767 87
					$timestamp = wc_string_to_timestamp( get_gmt_from_date( gmdate( 'Y-m-d H:i:s', wc_string_to_timestamp( $value ) ) ) );
768
				}
769 87
				$datetime = new WC_DateTime( "@{$timestamp}", new DateTimeZone( 'UTC' ) );
770
			}
771
772
			// Set local timezone or offset.
773 555
			if ( get_option( 'timezone_string' ) ) {
774 1
				$datetime->setTimezone( new DateTimeZone( wc_timezone_string() ) );
775
			} else {
776 555
				$datetime->set_utc_offset( wc_timezone_offset() );
777
			}
778
779 555
			$this->set_prop( $prop, $datetime );
780
		} catch ( Exception $e ) {} // @codingStandardsIgnoreLine.
781
	}
782
783
	/**
784
	 * When invalid data is found, throw an exception unless reading from the DB.
785
	 *
786
	 * @throws WC_Data_Exception Data Exception.
787
	 * @since 3.0.0
788
	 * @param string $code             Error code.
789
	 * @param string $message          Error message.
790
	 * @param int    $http_status_code HTTP status code.
791
	 * @param array  $data             Extra error data.
792
	 */
793 93
	protected function error( $code, $message, $http_status_code = 400, $data = array() ) {
794 93
		throw new WC_Data_Exception( $code, $message, $http_status_code, $data );
795
	}
796
}
797