Completed
Pull Request — master (#11762)
by Mike
26:45
created

WC_Data::set_defaults()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
* Abstract WC Data Class
8
*
9
* Implemented by classes using the same CRUD(s) pattern.
10
*
11
* @version  2.6.0
12
* @package  WooCommerce/Abstracts
13
* @category Abstract Class
14
* @author   WooThemes
15
*/
16
abstract class WC_Data {
17
18
	/**
19
	 * Core data default values for this object, name value pairs (name + default value).
20
	 * @var array
21
	 */
22
	protected $_default_data = array();
23
24
	/**
25
	 * Core data for this object.
26
	 * @var array
27
	 */
28
	protected $_data = array();
29
30
	/**
31
	 * True when reading from the database.
32
	 * @var bool
33
	 */
34
	protected $_reading = false;
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 = '';
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 = 'post';
49
50
	/**
51
	 * This only needs set if you are using a custom metadata type (for example payment tokens.
52
	 * This should be the name of the field your table uses for associating meta with objects.
53
	 * For example, in payment_tokenmeta, this would be payment_token_id.
54
	 * @var string
55
	 */
56
	protected $object_id_field_for_meta = '';
57
58
	/**
59
	 * Stores additonal meta data.
60
	 * @var array
61
	 */
62
	protected $_meta_data = array();
63
64
	/**
65
	 * Internal meta keys we don't want exposed for the object.
66
	 * @var array
67
	 */
68
	protected $_internal_meta_keys = array();
69
70
	/**
71
	 * Returns the unique ID for this object.
72
	 * @return int
73
	 */
74
	abstract public function get_id();
75
76
	/**
77
	 * Creates new object in the database.
78
	 */
79
	abstract public function create();
80
81
	/**
82
	 * Read object from the database.
83
	 * @param int ID of the object to load.
84
	 */
85
	abstract public function read( $id );
86
87
	/**
88
	 * Updates object data in the database.
89
	 */
90
	abstract public function update();
91
92
	/**
93
	 * Updates object data in the database.
94
	 */
95
	abstract public function delete();
96
97
	/**
98
	 * Save should create or update based on object existance.
99
	 */
100
	abstract public function save();
101
102
	/**
103
	 * Change data to JSON format.
104
	 * @return string Data in JSON format.
105
	 */
106
	public function __toString() {
107
		return json_encode( $this->get_data() );
108
	}
109
110
	/**
111
	 * Returns all data for this object.
112
	 * @return array
113
	 */
114
	public function get_data() {
115
		return array_merge( $this->_data, array( 'meta_data' => $this->get_meta_data() ) );
116
	}
117
118
	/**
119
	 * Filter null meta values from array.
120
	 * @return bool
121
	 */
122
	protected function filter_null_meta( $meta ) {
123
		return ! is_null( $meta->value );
124
	}
125
126
	/**
127
	 * Get All Meta Data.
128
	 * @since 2.6.0
129
	 * @return array
130
	 */
131
	public function get_meta_data() {
132
		return array_filter( $this->_meta_data, array( $this, 'filter_null_meta' ) );
133
	}
134
135
	/**
136
	 * Internal meta keys we don't want exposed as part of meta_data. This is in
137
	 * addition to all data props with _ prefix.
138
	 * @since 2.6.0
139
	 * @return array
140
	 */
141
	protected function prefix_key( $key ) {
142
		return '_' === substr( $key, 0, 1 ) ? $key : '_' . $key;
143
	}
144
145
	/**
146
	 * Internal meta keys we don't want exposed as part of meta_data. This is in
147
	 * addition to all data props with _ prefix.
148
	 * @since 2.6.0
149
	 * @return array
150
	 */
151
	protected function get_internal_meta_keys() {
152
		return array_merge( array_map( array( $this, 'prefix_key' ), array_keys( $this->_data ) ), $this->_internal_meta_keys );
153
	}
154
155
	/**
156
	 * Get Meta Data by Key.
157
	 * @since 2.6.0
158
	 * @param  string $key
159
	 * @param  bool $single return first found meta with key, or all with $key
160
	 * @return mixed
161
	 */
162
	public function get_meta( $key = '', $single = true ) {
163
		$array_keys = array_keys( wp_list_pluck( $this->get_meta_data(), 'key' ), $key );
164
		$value    = '';
165
166
		if ( ! empty( $array_keys ) ) {
167
			if ( $single ) {
168
				$value = $this->_meta_data[ current( $array_keys ) ]->value;
169
			} else {
170
				$value = array_intersect_key( $this->_meta_data, array_flip( $array_keys ) );
171
			}
172
		}
173
174
		return $value;
175
	}
176
177
	/**
178
	 * Set all meta data from array.
179
	 * @since 2.6.0
180
	 * @param array $data Key/Value pairs
181
	 */
182
	public function set_meta_data( $data ) {
183
		if ( ! empty( $data ) && is_array( $data ) ) {
184
			foreach ( $data as $meta ) {
185
				$meta = (array) $meta;
186
				if ( isset( $meta['key'], $meta['value'], $meta['id'] ) ) {
187
					$this->_meta_data[] = (object) array(
188
						'id'    => $meta['id'],
189
						'key'   => $meta['key'],
190
						'value' => $meta['value'],
191
					);
192
				}
193
			}
194
		}
195
	}
196
197
	/**
198
	 * Add meta data.
199
	 * @since 2.6.0
200
	 * @param string $key Meta key
201
	 * @param string $value Meta value
202
	 * @param bool $unique Should this be a unique key?
203
	 */
204
	public function add_meta_data( $key, $value, $unique = false ) {
205
		if ( $unique ) {
206
			$this->delete_meta_data( $key );
0 ignored issues
show
Documentation introduced by
$key 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...
207
		}
208
		$this->_meta_data[] = (object) array(
209
			'key'   => $key,
210
			'value' => $value,
211
		);
212
	}
213
214
	/**
215
	 * Update meta data by key or ID, if provided.
216
	 * @since 2.6.0
217
	 * @param  string $key
218
	 * @param  string $value
219
	 * @param  int $meta_id
220
	 */
221
	public function update_meta_data( $key, $value, $meta_id = '' ) {
222
		$array_key = '';
223
		if ( $meta_id ) {
224
			$array_key = array_keys( wp_list_pluck( $this->_meta_data, 'id' ), $meta_id );
225
		}
226
		if ( $array_key ) {
227
			$this->_meta_data[ current( $array_key ) ] = (object) array(
228
				'id'    => $meta_id,
229
				'key'   => $key,
230
				'value' => $value,
231
			);
232
		} else {
233
			$this->add_meta_data( $key, $value, true );
234
		}
235
	}
236
237
	/**
238
	 * Delete meta data.
239
	 * @since 2.6.0
240
	 * @param array $key Meta key
241
	 */
242 View Code Duplication
	public function delete_meta_data( $key ) {
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...
243
		$array_keys = array_keys( wp_list_pluck( $this->_meta_data, 'key' ), $key );
244
		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...
245
			foreach ( $array_keys as $array_key ) {
246
				$this->_meta_data[ $array_key ]->value = null;
247
			}
248
		}
249
	}
250
251
	/**
252
	 * Delete meta data.
253
	 * @since 2.6.0
254
	 * @param int $mid Meta ID
255
	 */
256 View Code Duplication
	public function delete_meta_data_by_mid( $mid ) {
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...
257
		$array_keys         = array_keys( wp_list_pluck( $this->_meta_data, 'id' ), $mid );
258
		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...
259
			foreach ( $array_keys as $array_key ) {
260
				$this->_meta_data[ $array_key ]->value = null;
261
			}
262
		}
263
	}
264
265
	/**
266
	 * Read Meta Data from the database. Ignore any internal properties.
267
	 * @since 2.6.0
268
	 */
269
	protected function read_meta_data() {
270
		$this->_meta_data = array();
271
		$cache_loaded     = false;
272
273
		if ( ! $this->get_id() ) {
274
			return;
275
		}
276
277
		if ( ! empty( $this->_cache_group ) ) {
278
			$cache_key   = WC_Cache_Helper::get_cache_prefix( $this->_cache_group ) . $this->get_id();
279
			$cached_meta = wp_cache_get( $cache_key, $this->_cache_group );
280
281
			if ( false !== $cached_meta ) {
282
				$this->_meta_data = $cached_meta;
283
				$cache_loaded = true;
284
			}
285
		}
286
287
		if ( ! $cache_loaded ) {
288
			global $wpdb;
289
			$db_info = $this->_get_db_info();
290
			$raw_meta_data = $wpdb->get_results( $wpdb->prepare( "
291
				SELECT " . $db_info['meta_id_field'] . ", meta_key, meta_value
292
				FROM " . $db_info['table'] . "
293
				WHERE " . $db_info['object_id_field'] . "=%d AND meta_key NOT LIKE 'wp\_%%' ORDER BY " . $db_info['meta_id_field'] . "
294
			", $this->get_id() ) );
295
296
			if ( $raw_meta_data ) {
297
				foreach ( $raw_meta_data as $meta ) {
298
					if ( in_array( $meta->meta_key, $this->get_internal_meta_keys() ) ) {
299
						continue;
300
					}
301
					$this->_meta_data[] = (object) array(
302
						'id'    => (int) $meta->{ $db_info['meta_id_field'] },
303
						'key'   => $meta->meta_key,
304
						'value' => maybe_unserialize( $meta->meta_value ),
305
					);
306
				}
307
			}
308
309
			if ( ! empty( $this->_cache_group ) ) {
310
				wp_cache_set( $cache_key, $this->_meta_data, $this->_cache_group );
311
			}
312
		}
313
	}
314
315
	/**
316
	 * Update Meta Data in the database.
317
	 * @since 2.6.0
318
	 */
319
	protected function save_meta_data() {
320
		foreach ( $this->_meta_data as $array_key => $meta ) {
321
			if ( is_null( $meta->value ) ) {
322
				if ( ! empty( $meta->id ) ) {
323
					delete_metadata_by_mid( $this->_meta_type, $meta->id );
324
				}
325
			} elseif ( empty( $meta->id ) ) {
326
				$new_meta_id = add_metadata( $this->_meta_type, $this->get_id(), $meta->key, $meta->value, false );
327
				$this->_meta_data[ $array_key ]->id = $new_meta_id;
328
			} else {
329
				update_metadata_by_mid( $this->_meta_type, $meta->id, $meta->value, $meta->key );
330
			}
331
		}
332
333
		if ( ! empty( $this->_cache_group ) ) {
334
			WC_Cache_Helper::incr_cache_prefix( $this->_cache_group );
335
		}
336
		$this->read_meta_data();
337
	}
338
339
	/**
340
	 * Table structure is slightly different between meta types, this function will return what we need to know.
341
	 * @since 2.6.0
342
	 * @return array Array elements: table, object_id_field, meta_id_field
343
	 */
344
	protected function _get_db_info() {
345
		global $wpdb;
346
347
		$meta_id_field   = 'meta_id'; // for some reason users calls this umeta_id so we need to track this as well.
348
		$table           = $wpdb->prefix;
349
350
		// If we are dealing with a type of metadata that is not a core type, the table should be prefixed.
351
		if ( ! in_array( $this->_meta_type, array( 'post', 'user', 'comment', 'term' ) ) ) {
352
			$table .= 'woocommerce_';
353
		}
354
355
		$table .= $this->_meta_type . 'meta';
356
		$object_id_field = $this->_meta_type . '_id';
357
358
		// Figure out our field names.
359
		if ( 'user' === $this->_meta_type ) {
360
			$meta_id_field   = 'umeta_id';
361
		}
362
363
		if ( ! empty( $this->object_id_field_for_meta ) ) {
364
			$object_id_field = $this->object_id_field_for_meta;
365
		}
366
367
		return array(
368
			'table'           => $table,
369
			'object_id_field' => $object_id_field,
370
			'meta_id_field'   => $meta_id_field,
371
		);
372
	}
373
374
	/**
375
	 * Set all props to default values.
376
	 */
377
	protected function set_defaults() {
378
		$this->_data = $this->_default_data;
379
	}
380
381
	/**
382
	 * Get internal data prop (raw).
383
	 * @param string ...$param Prop keys to retrieve. Supports multiple keys to get nested values.
384
	 * @return mixed
385
	 */
386
	protected function get_prop() {
387
		$args = func_get_args();
388
		$prop = &$this->_data;
389
390
		foreach ( $args as $arg ) {
391
			if ( ! isset( $prop[ $arg ] ) ) {
392
				return false;
393
			}
394
			$prop = &$prop[ $arg ];
395
		}
396
397
		return $prop;
398
	}
399
400
	/**
401
	 * Set internal data prop to specified value.
402
	 * @param int ...$param Prop keys followed by value to set.
403
	 * @throws WC_Data_Exception
404
	 */
405
	protected function set_prop() {
406
		if ( func_num_args() < 2 ) {
407
			$this->invalid_data( 'invalid_value', 'set_prop requires at least 2 parameters' );
408
		}
409
410
		$args  = func_get_args();
411
		$value = array_pop( $args );
412
		$prop  = &$this->_data;
413
414
		foreach ( $args as $arg ) {
415
			$prop = &$prop[ $arg ];
416
		}
417
418
		$prop = $value;
419
	}
420
421
	/**
422
	 * When invalid data is found, throw an exception unless reading from the DB.
423
	 * @param string $error_code Error code.
424
	 * @param string $data $error_message Error message.
0 ignored issues
show
Bug introduced by
There is no parameter named $data. 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...
425
	 * @throws WC_Data_Exception
426
	 */
427
	protected function invalid_data( $error_code, $error_message ) {
428
		if ( ! $this->_reading ) {
429
			throw new WC_Data_Exception( $error_code, $error_message );
430
		}
431
	}
432
}
433