Completed
Pull Request — master (#11762)
by Mike
13:05
created

WC_Data::create()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 1
c 0
b 0
f 0
nc 1
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
	 * Get All Meta Data.
120
	 * @since 2.6.0
121
	 * @return array
122
	 */
123
	public function get_meta_data() {
124
		return $this->_meta_data;
125
	}
126
127
	/**
128
	 * Internal meta keys we don't want exposed as part of meta_data. This is in
129
	 * addition to all data props with _ prefix.
130
	 * @since 2.6.0
131
	 * @return array
132
	 */
133
	protected function prefix_key( $key ) {
134
		return '_' === substr( $key, 0, 1 ) ? $key : '_' . $key;
135
	}
136
137
	/**
138
	 * Internal meta keys we don't want exposed as part of meta_data. This is in
139
	 * addition to all data props with _ prefix.
140
	 * @since 2.6.0
141
	 * @return array
142
	 */
143
	protected function get_internal_meta_keys() {
144
		return array_merge( array_map( array( $this, 'prefix_key' ), array_keys( $this->_data ) ), $this->_internal_meta_keys );
145
	}
146
147
	/**
148
	 * Get Meta Data by Key.
149
	 * @since 2.6.0
150
	 * @param  string $key
151
	 * @param  bool $single return first found meta with key, or all with $key
152
	 * @return mixed
153
	 */
154
	public function get_meta( $key = '', $single = true ) {
155
		$array_keys = array_keys( wp_list_pluck( $this->_meta_data, 'key' ), $key );
156
		$value    = '';
157
158
		if ( ! empty( $array_keys ) ) {
159
			if ( $single ) {
160
				$value = $this->_meta_data[ current( $array_keys ) ]->value;
161
			} else {
162
				$value = array_intersect_key( $this->_meta_data, array_flip( $array_keys ) );
163
			}
164
		}
165
166
		return $value;
167
	}
168
169
	/**
170
	 * Set all meta data from array.
171
	 * @since 2.6.0
172
	 * @param array $data Key/Value pairs
173
	 */
174
	public function set_meta_data( $data ) {
175
		if ( ! empty( $data ) && is_array( $data ) ) {
176
			foreach ( $data as $meta ) {
177
				$meta = (array) $meta;
178
				if ( isset( $meta['key'], $meta['value'], $meta['id'] ) ) {
179
					$this->_meta_data[] = (object) array(
180
						'id'    => $meta['id'],
181
						'key'   => $meta['key'],
182
						'value' => $meta['value'],
183
					);
184
				}
185
			}
186
		}
187
	}
188
189
	/**
190
	 * Add meta data.
191
	 * @since 2.6.0
192
	 * @param string $key Meta key
193
	 * @param string $value Meta value
194
	 * @param bool $unique Should this be a unique key?
195
	 */
196
	public function add_meta_data( $key, $value, $unique = false ) {
197
		if ( $unique ) {
198
			$array_keys       = array_keys( wp_list_pluck( $this->_meta_data, 'key' ), $key );
199
			$this->_meta_data = array_diff_key( $this->_meta_data, array_fill_keys( $array_keys, '' ) );
200
		}
201
		$this->_meta_data[] = (object) array(
202
			'key'   => $key,
203
			'value' => $value,
204
		);
205
	}
206
207
	/**
208
	 * Update meta data by key or ID, if provided.
209
	 * @since 2.6.0
210
	 * @param  string $key
211
	 * @param  string $value
212
	 * @param  int $meta_id
213
	 */
214
	public function update_meta_data( $key, $value, $meta_id = '' ) {
215
		$array_key = '';
216
		if ( $meta_id ) {
217
			$array_key = array_keys( wp_list_pluck( $this->_meta_data, 'id' ), $meta_id );
218
		}
219
		if ( $array_key ) {
220
			$this->_meta_data[ current( $array_key ) ] = (object) array(
221
				'id'    => $meta_id,
222
				'key'   => $key,
223
				'value' => $value,
224
			);
225
		} else {
226
			$this->add_meta_data( $key, $value, true );
227
		}
228
	}
229
230
	/**
231
	 * Delete meta data.
232
	 * @since 2.6.0
233
	 * @param array $key Meta key
234
	 */
235
	public function delete_meta_data( $key ) {
236
		$array_keys         = array_keys( wp_list_pluck( $this->_meta_data, 'key' ), $key );
237
		$this->_meta_data   = array_diff_key( $this->_meta_data, array_fill_keys( $array_keys, '' ) );
238
	}
239
240
	/**
241
	 * Delete meta data.
242
	 * @since 2.6.0
243
	 * @param int $mid Meta ID
244
	 */
245
	public function delete_meta_data_by_mid( $mid ) {
246
		$array_keys         = array_keys( wp_list_pluck( $this->_meta_data, 'id' ), $mid );
247
		$this->_meta_data   = array_diff_key( $this->_meta_data, array_fill_keys( $array_keys, '' ) );
248
	}
249
250
	/**
251
	 * Read Meta Data from the database. Ignore any internal properties.
252
	 * @since 2.6.0
253
	 */
254
	protected function read_meta_data() {
255
		$this->_meta_data = array();
256
		$cache_loaded     = false;
257
258
		if ( ! $this->get_id() ) {
259
			return;
260
		}
261
262
		if ( ! empty( $this->_cache_group ) ) {
263
			$cache_key   = WC_Cache_Helper::get_cache_prefix( $this->_cache_group ) . $this->get_id();
264
			$cached_meta = wp_cache_get( $cache_key, $this->_cache_group );
265
266
			if ( false !== $cached_meta ) {
267
				$this->_meta_data = $cached_meta;
268
				$cache_loaded = true;
269
			}
270
		}
271
272
		if ( ! $cache_loaded ) {
273
			global $wpdb;
274
			$db_info = $this->_get_db_info();
275
			$raw_meta_data = $wpdb->get_results( $wpdb->prepare( "
276
				SELECT " . $db_info['meta_id_field'] . ", meta_key, meta_value
277
				FROM " . $db_info['table'] . "
278
				WHERE " . $db_info['object_id_field'] . "=%d AND meta_key NOT LIKE 'wp\_%%' ORDER BY " . $db_info['meta_id_field'] . "
279
			", $this->get_id() ) );
280
281
			if ( $raw_meta_data ) {
282
				foreach ( $raw_meta_data as $meta ) {
283
					if ( in_array( $meta->meta_key, $this->get_internal_meta_keys() ) ) {
284
						continue;
285
					}
286
					$this->_meta_data[] = (object) array(
287
						'id'    => (int) $meta->{ $db_info['meta_id_field'] },
288
						'key'   => $meta->meta_key,
289
						'value' => maybe_unserialize( $meta->meta_value ),
290
					);
291
				}
292
			}
293
294
			if ( ! empty( $this->_cache_group ) ) {
295
				wp_cache_set( $cache_key, $this->_meta_data, $this->_cache_group );
296
			}
297
		}
298
	}
299
300
	/**
301
	 * Update Meta Data in the database.
302
	 * @since 2.6.0
303
	 */
304
	protected function save_meta_data() {
305
		global $wpdb;
306
		$db_info = $this->_get_db_info();
307
		$all_meta_ids = array_map( 'absint', $wpdb->get_col( $wpdb->prepare( "
308
			SELECT " . $db_info['meta_id_field'] . " FROM " . $db_info['table'] . "
309
			WHERE " . $db_info['object_id_field'] . " = %d", $this->get_id() ) . "
310
			AND meta_key NOT IN ('" . implode( "','", array_map( 'esc_sql', $this->get_internal_meta_keys() ) ) . "')
311
			AND meta_key NOT LIKE 'wp\_%%';
312
		" ) );
313
		$set_meta_ids = array();
314
315
		foreach ( $this->_meta_data as $array_key => $meta ) {
316
			if ( empty( $meta->id ) ) {
317
				$new_meta_id    = add_metadata( $this->_meta_type, $this->get_id(), $meta->key, $meta->value, false );
318
				$set_meta_ids[] = $new_meta_id;
319
				$this->_meta_data[ $array_key ]->id = $new_meta_id;
320
			} else {
321
				update_metadata_by_mid( $this->_meta_type, $meta->id, $meta->value, $meta->key );
322
				$set_meta_ids[] = absint( $meta->id );
323
			}
324
		}
325
326
		// Delete no longer set meta data
327
		$delete_meta_ids = array_diff( $all_meta_ids, $set_meta_ids );
328
		foreach ( $delete_meta_ids as $meta_id ) {
329
			delete_metadata_by_mid( $this->_meta_type, $meta_id );
330
		}
331
332
		if ( ! empty( $this->_cache_group ) ) {
333
			WC_Cache_Helper::incr_cache_prefix( $this->_cache_group );
334
		}
335
		$this->read_meta_data();
336
	}
337
338
	/**
339
	 * Table structure is slightly different between meta types, this function will return what we need to know.
340
	 * @since 2.6.0
341
	 * @return array Array elements: table, object_id_field, meta_id_field
342
	 */
343
	protected function _get_db_info() {
344
		global $wpdb;
345
346
		$meta_id_field   = 'meta_id'; // for some reason users calls this umeta_id so we need to track this as well.
347
		$table           = $wpdb->prefix;
348
349
		// If we are dealing with a type of metadata that is not a core type, the table should be prefixed.
350
		if ( ! in_array( $this->_meta_type, array( 'post', 'user', 'comment', 'term' ) ) ) {
351
			$table .= 'woocommerce_';
352
		}
353
354
		$table .= $this->_meta_type . 'meta';
355
		$object_id_field = $this->_meta_type . '_id';
356
357
		// Figure out our field names.
358
		if ( 'user' === $this->_meta_type ) {
359
			$meta_id_field   = 'umeta_id';
360
		}
361
362
		if ( ! empty( $this->object_id_field_for_meta ) ) {
363
			$object_id_field = $this->object_id_field_for_meta;
364
		}
365
366
		return array(
367
			'table'           => $table,
368
			'object_id_field' => $object_id_field,
369
			'meta_id_field'   => $meta_id_field,
370
		);
371
	}
372
373
	/**
374
	 * Set all props to default values.
375
	 */
376
	protected function set_defaults() {
377
		$this->_data = $this->_default_data;
378
	}
379
380
	/**
381
	 * Get internal data prop (raw).
382
	 * @param string ...$param Prop keys to retrieve. Supports multiple keys to get nested values.
383
	 * @return mixed
384
	 */
385
	protected function get_prop() {
386
		$args = func_get_args();
387
		$prop = &$this->_data;
388
389
		foreach ( $args as $arg ) {
390
			if ( ! isset( $prop[ $arg ] ) ) {
391
				return false;
392
			}
393
			$prop = &$prop[ $arg ];
394
		}
395
396
		return $prop;
397
	}
398
399
	/**
400
	 * Set internal data prop to specified value.
401
	 * @param int ...$param Prop keys followed by value to set.
402
	 * @throws WC_Data_Exception
403
	 */
404
	protected function set_prop() {
405
		if ( func_num_args() < 2 ) {
406
			$this->invalid_data( 'invalid_value', 'set_prop requires at least 2 parameters' );
407
		}
408
409
		$args  = func_get_args();
410
		$value = array_pop( $args );
411
		$prop  = &$this->_data;
412
413
		foreach ( $args as $arg ) {
414
			$prop = &$prop[ $arg ];
415
		}
416
417
		$prop = $value;
418
	}
419
420
	/**
421
	 * When invalid data is found, throw an exception unless reading from the DB.
422
	 * @param string $error_code Error code.
423
	 * @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...
424
	 * @throws WC_Data_Exception
425
	 */
426
	protected function invalid_data( $error_code, $error_message ) {
427
		if ( ! $this->_reading ) {
428
			throw new WC_Data_Exception( $error_code, $error_message );
429
		}
430
	}
431
}
432