Completed
Pull Request — master (#11755)
by Mike
09:03
created

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