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

WC_Data::get_db_info()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 29
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 16
c 1
b 0
f 0
nc 8
nop 0
dl 0
loc 29
rs 8.5806
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
	 * ID for this object.
20
	 * @var int
21
	 */
22
	protected $id = 0;
23
24
	/**
25
	 * Core data for this object. Name value pairs (name + default value).
26
	 * @var array
27
	 */
28
	protected $data = array();
29
30
	/**
31
	 * Set to _data on construct so we can track and reset data if needed.
32
	 * @var array
33
	 */
34
	protected $default_data = array();
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
	 * Default constructor.
72
	 * @param int|object|array $read ID to load from the DB (optional) or already queried data.
73
	 */
74
	public function __construct( $read = 0 ) {
75
		$this->default_data = $this->data;
76
	}
77
78
	/**
79
	 * Returns the unique ID for this object.
80
	 * @return int
81
	 */
82
	public function get_id() {
83
		return $this->id;
84
	}
85
86
	/**
87
	 * Creates new object in the database.
88
	 */
89
	public function create() {}
90
91
	/**
92
	 * Read object from the database.
93
	 * @param int ID of the object to load.
94
	 */
95
	public function read( $id ) {}
96
97
	/**
98
	 * Updates object data in the database.
99
	 */
100
	public function update() {}
101
102
	/**
103
	 * Updates object data in the database.
104
	 */
105
	public function delete() {}
106
107
	/**
108
	 * Save should create or update based on object existance.
109
	 */
110
	public function save() {}
111
112
	/**
113
	 * Change data to JSON format.
114
	 * @return string Data in JSON format.
115
	 */
116
	public function __toString() {
117
		return json_encode( $this->get_data() );
118
	}
119
120
	/**
121
	 * Returns all data for this object.
122
	 * @return array
123
	 */
124
	public function get_data() {
125
		return array_merge( array( 'id' => $this->get_id() ), $this->data, array( 'meta_data' => $this->get_meta_data() ) );
126
	}
127
128
	/**
129
	 * Filter null meta values from array.
130
	 * @return bool
131
	 */
132
	protected function filter_null_meta( $meta ) {
133
		return ! is_null( $meta->value );
134
	}
135
136
	/**
137
	 * Get All Meta Data.
138
	 * @since 2.6.0
139
	 * @return array
140
	 */
141
	public function get_meta_data() {
142
		return array_filter( $this->meta_data, array( $this, 'filter_null_meta' ) );
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 prefix_key( $key ) {
152
		return '_' === substr( $key, 0, 1 ) ? $key : '_' . $key;
153
	}
154
155
	/**
156
	 * Internal meta keys we don't want exposed as part of meta_data. This is in
157
	 * addition to all data props with _ prefix.
158
	 * @since 2.6.0
159
	 * @return array
160
	 */
161
	protected function get_internal_meta_keys() {
162
		return array_merge( array_map( array( $this, 'prefix_key' ), array_keys( $this->data ) ), $this->internal_meta_keys );
163
	}
164
165
	/**
166
	 * Get Meta Data by Key.
167
	 * @since 2.6.0
168
	 * @param  string $key
169
	 * @param  bool $single return first found meta with key, or all with $key
170
	 * @return mixed
171
	 */
172
	public function get_meta( $key = '', $single = true ) {
173
		$array_keys = array_keys( wp_list_pluck( $this->get_meta_data(), 'key' ), $key );
174
		$value    = '';
175
176
		if ( ! empty( $array_keys ) ) {
177
			if ( $single ) {
178
				$value = $this->meta_data[ current( $array_keys ) ]->value;
179
			} else {
180
				$value = array_intersect_key( $this->meta_data, array_flip( $array_keys ) );
181
			}
182
		}
183
184
		return $value;
185
	}
186
187
	/**
188
	 * Set all meta data from array.
189
	 * @since 2.6.0
190
	 * @param array $data Key/Value pairs
191
	 */
192
	public function set_meta_data( $data ) {
193
		if ( ! empty( $data ) && is_array( $data ) ) {
194
			foreach ( $data as $meta ) {
195
				$meta = (array) $meta;
196
				if ( isset( $meta['key'], $meta['value'], $meta['id'] ) ) {
197
					$this->meta_data[] = (object) array(
198
						'id'    => $meta['id'],
199
						'key'   => $meta['key'],
200
						'value' => $meta['value'],
201
					);
202
				}
203
			}
204
		}
205
	}
206
207
	/**
208
	 * Add meta data.
209
	 * @since 2.6.0
210
	 * @param string $key Meta key
211
	 * @param string $value Meta value
212
	 * @param bool $unique Should this be a unique key?
213
	 */
214
	public function add_meta_data( $key, $value, $unique = false ) {
215
		if ( $unique ) {
216
			$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...
217
		}
218
		$this->meta_data[] = (object) array(
219
			'key'   => $key,
220
			'value' => $value,
221
		);
222
	}
223
224
	/**
225
	 * Update meta data by key or ID, if provided.
226
	 * @since 2.6.0
227
	 * @param  string $key
228
	 * @param  string $value
229
	 * @param  int $meta_id
230
	 */
231
	public function update_meta_data( $key, $value, $meta_id = '' ) {
232
		$array_key = '';
233
		if ( $meta_id ) {
234
			$array_key = array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id );
235
		}
236
		if ( $array_key ) {
237
			$this->meta_data[ current( $array_key ) ] = (object) array(
238
				'id'    => $meta_id,
239
				'key'   => $key,
240
				'value' => $value,
241
			);
242
		} else {
243
			$this->add_meta_data( $key, $value, true );
244
		}
245
	}
246
247
	/**
248
	 * Delete meta data.
249
	 * @since 2.6.0
250
	 * @param array $key Meta key
251
	 */
252 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...
253
		$array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key );
254
		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...
255
			foreach ( $array_keys as $array_key ) {
256
				$this->meta_data[ $array_key ]->value = null;
257
			}
258
		}
259
	}
260
261
	/**
262
	 * Delete meta data.
263
	 * @since 2.6.0
264
	 * @param int $mid Meta ID
265
	 */
266 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...
267
		$array_keys         = array_keys( wp_list_pluck( $this->meta_data, 'id' ), $mid );
268
		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...
269
			foreach ( $array_keys as $array_key ) {
270
				$this->meta_data[ $array_key ]->value = null;
271
			}
272
		}
273
	}
274
275
	/**
276
	 * Callback to remove unwanted meta data.
277
	 *
278
	 * @param object $meta
279
	 * @return bool
280
	 */
281
	protected function exclude_internal_meta_keys( $meta ) {
282
		return ! in_array( $meta->meta_key, $this->get_internal_meta_keys() );
283
	}
284
285
	/**
286
	 * Read Meta Data from the database. Ignore any internal properties.
287
	 * @since 2.6.0
288
	 */
289
	protected function read_meta_data() {
290
		$this->meta_data = array();
291
		$cache_loaded     = false;
292
293
		if ( ! $this->get_id() ) {
294
			return;
295
		}
296
297
		if ( ! empty( $this->cache_group ) ) {
298
			$cache_key   = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . $this->get_id();
299
			$cached_meta = wp_cache_get( $cache_key, $this->cache_group );
300
301
			if ( false !== $cached_meta ) {
302
				$this->meta_data = $cached_meta;
303
				$cache_loaded = true;
304
			}
305
		}
306
307
		if ( ! $cache_loaded ) {
308
			global $wpdb;
309
			$db_info = $this->get_db_info();
310
			$raw_meta_data = $wpdb->get_results( $wpdb->prepare( "
311
				SELECT " . $db_info['meta_id_field'] . ", meta_key, meta_value
312
				FROM " . $db_info['table'] . "
313
				WHERE " . $db_info['object_id_field'] . "=%d AND meta_key NOT LIKE 'wp\_%%' ORDER BY " . $db_info['meta_id_field'] . "
314
			", $this->get_id() ) );
315
316
			if ( $raw_meta_data ) {
317
				$raw_meta_data = array_filter( $raw_meta_data, array( $this, 'exclude_internal_meta_keys' ) );
318
				foreach ( $raw_meta_data as $meta ) {
319
					$this->meta_data[] = (object) array(
320
						'id'    => (int) $meta->{ $db_info['meta_id_field'] },
321
						'key'   => $meta->meta_key,
322
						'value' => maybe_unserialize( $meta->meta_value ),
323
					);
324
				}
325
			}
326
327
			if ( ! empty( $this->cache_group ) ) {
328
				wp_cache_set( $cache_key, $this->meta_data, $this->cache_group );
329
			}
330
		}
331
	}
332
333
	/**
334
	 * Update Meta Data in the database.
335
	 * @since 2.6.0
336
	 */
337
	protected function save_meta_data() {
338
		foreach ( $this->meta_data as $array_key => $meta ) {
339
			if ( is_null( $meta->value ) ) {
340
				if ( ! empty( $meta->id ) ) {
341
					delete_metadata_by_mid( $this->meta_type, $meta->id );
342
				}
343
			} elseif ( empty( $meta->id ) ) {
344
				$new_meta_id = add_metadata( $this->meta_type, $this->get_id(), $meta->key, $meta->value, false );
345
				$this->meta_data[ $array_key ]->id = $new_meta_id;
346
			} else {
347
				update_metadata_by_mid( $this->meta_type, $meta->id, $meta->value, $meta->key );
348
			}
349
		}
350
351
		if ( ! empty( $this->cache_group ) ) {
352
			WC_Cache_Helper::incr_cache_prefix( $this->cache_group );
353
		}
354
		$this->read_meta_data();
355
	}
356
357
	/**
358
	 * Table structure is slightly different between meta types, this function will return what we need to know.
359
	 * @since 2.6.0
360
	 * @return array Array elements: table, object_id_field, meta_id_field
361
	 */
362
	protected function get_db_info() {
363
		global $wpdb;
364
365
		$meta_id_field   = 'meta_id'; // for some reason users calls this umeta_id so we need to track this as well.
366
		$table           = $wpdb->prefix;
367
368
		// If we are dealing with a type of metadata that is not a core type, the table should be prefixed.
369
		if ( ! in_array( $this->meta_type, array( 'post', 'user', 'comment', 'term' ) ) ) {
370
			$table .= 'woocommerce_';
371
		}
372
373
		$table .= $this->meta_type . 'meta';
374
		$object_id_field = $this->meta_type . '_id';
375
376
		// Figure out our field names.
377
		if ( 'user' === $this->meta_type ) {
378
			$meta_id_field   = 'umeta_id';
379
		}
380
381
		if ( ! empty( $this->object_id_field_for_meta ) ) {
382
			$object_id_field = $this->object_id_field_for_meta;
383
		}
384
385
		return array(
386
			'table'           => $table,
387
			'object_id_field' => $object_id_field,
388
			'meta_id_field'   => $meta_id_field,
389
		);
390
	}
391
392
	/**
393
	 * Set ID.
394
	 * @param int $id
395
	 */
396
	public function set_id( $id ) {
397
		$this->id = absint( $id );
398
	}
399
400
	/**
401
	 * Set all props to default values.
402
	 */
403
	protected function set_defaults() {
404
		$this->data = $this->default_data;
405
	}
406
407
	/**
408
	 * Set a collection of props in one go, collect any errors, and return the result.
409
	 * @param array $props Key value pairs to set. Key is the prop and should map to a setter function name.
410
	 * @return WP_Error|bool
411
	 */
412
	public function set_props( $props ) {
413
		$errors = new WP_Error();
414
415
		foreach ( $props as $prop => $value ) {
416
			try {
417
				if ( 'meta_data' === $prop ) {
418
					continue;
419
				}
420
				$setter = "set_$prop";
421
				if ( ! is_null( $value ) && is_callable( array( $this, $setter ) ) ) {
422
					$this->{$setter}( $value );
423
				}
424
			} catch ( WC_Data_Exception $e ) {
425
				$errors->add( $e->getErrorCode(), $e->getMessage() );
426
			}
427
		}
428
429
		return sizeof( $errors->get_error_codes() ) ? $errors : true;
430
	}
431
432
	/**
433
	 * When invalid data is found, throw an exception unless reading from the DB.
434
	 * @param string $error_code Error code.
435
	 * @param string $error_message Error message.
436
	 * @throws WC_Data_Exception
437
	 */
438
	protected function error( $error_code, $error_message ) {
439
		throw new WC_Data_Exception( $error_code, $error_message );
440
	}
441
}
442