PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 7.9.0-rc.3
WooCommerce v7.9.0-rc.3
10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / includes / abstracts / abstract-wc-data.php
woocommerce / includes / abstracts Last commit date
abstract-wc-data.php 3 years ago abstract-wc-deprecated-hooks.php 6 years ago abstract-wc-integration.php 5 years ago abstract-wc-log-handler.php 5 years ago abstract-wc-object-query.php 5 years ago abstract-wc-order.php 2 years ago abstract-wc-payment-gateway.php 3 years ago abstract-wc-payment-token.php 5 years ago abstract-wc-privacy.php 5 years ago abstract-wc-product.php 2 years ago abstract-wc-session.php 5 years ago abstract-wc-settings-api.php 2 years ago abstract-wc-shipping-method.php 2 years ago abstract-wc-widget.php 4 years ago class-wc-background-process.php 5 years ago
abstract-wc-data.php
903 lines
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 * List of properties that were earlier managed by data store. However, since DataStore is a not a stored entity in itself, they used to store data in metadata of the data object.
112 * With custom tables, some of these are moved from metadata to their own columns, but existing code will still try to add them to metadata. This array is used to keep track of such properties.
113 *
114 * Only reason to add a property here is that you are moving properties from DataStore instance to data object. If you are adding a new property, consider adding it to to $data array instead.
115 *
116 * @var array
117 */
118 protected $legacy_datastore_props = array();
119
120 /**
121 * Default constructor.
122 *
123 * @param int|object|array $read ID to load from the DB (optional) or already queried data.
124 */
125 public function __construct( $read = 0 ) {
126 $this->data = array_merge( $this->data, $this->extra_data );
127 $this->default_data = $this->data;
128 }
129
130 /**
131 * Only store the object ID to avoid serializing the data object instance.
132 *
133 * @return array
134 */
135 public function __sleep() {
136 return array( 'id' );
137 }
138
139 /**
140 * Re-run the constructor with the object ID.
141 *
142 * If the object no longer exists, remove the ID.
143 */
144 public function __wakeup() {
145 try {
146 $this->__construct( absint( $this->id ) );
147 } catch ( Exception $e ) {
148 $this->set_id( 0 );
149 $this->set_object_read( true );
150 }
151 }
152
153 /**
154 * When the object is cloned, make sure meta is duplicated correctly.
155 *
156 * @since 3.0.2
157 */
158 public function __clone() {
159 $this->maybe_read_meta_data();
160 if ( ! empty( $this->meta_data ) ) {
161 foreach ( $this->meta_data as $array_key => $meta ) {
162 $this->meta_data[ $array_key ] = clone $meta;
163 if ( ! empty( $meta->id ) ) {
164 $this->meta_data[ $array_key ]->id = null;
165 }
166 }
167 }
168 }
169
170 /**
171 * Get the data store.
172 *
173 * @since 3.0.0
174 * @return object
175 */
176 public function get_data_store() {
177 return $this->data_store;
178 }
179
180 /**
181 * Returns the unique ID for this object.
182 *
183 * @since 2.6.0
184 * @return int
185 */
186 public function get_id() {
187 return $this->id;
188 }
189
190 /**
191 * Delete an object, set the ID to 0, and return result.
192 *
193 * @since 2.6.0
194 * @param bool $force_delete Should the date be deleted permanently.
195 * @return bool result
196 */
197 public function delete( $force_delete = false ) {
198 if ( $this->data_store ) {
199 $this->data_store->delete( $this, array( 'force_delete' => $force_delete ) );
200 $this->set_id( 0 );
201 return true;
202 }
203 return false;
204 }
205
206 /**
207 * Save should create or update based on object existence.
208 *
209 * @since 2.6.0
210 * @return int
211 */
212 public function save() {
213 if ( ! $this->data_store ) {
214 return $this->get_id();
215 }
216
217 /**
218 * Trigger action before saving to the DB. Allows you to adjust object props before save.
219 *
220 * @param WC_Data $this The object being saved.
221 * @param WC_Data_Store_WP $data_store THe data store persisting the data.
222 */
223 do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
224
225 if ( $this->get_id() ) {
226 $this->data_store->update( $this );
227 } else {
228 $this->data_store->create( $this );
229 }
230
231 /**
232 * Trigger action after saving to the DB.
233 *
234 * @param WC_Data $this The object being saved.
235 * @param WC_Data_Store_WP $data_store THe data store persisting the data.
236 */
237 do_action( 'woocommerce_after_' . $this->object_type . '_object_save', $this, $this->data_store );
238
239 return $this->get_id();
240 }
241
242 /**
243 * Change data to JSON format.
244 *
245 * @since 2.6.0
246 * @return string Data in JSON format.
247 */
248 public function __toString() {
249 return wp_json_encode( $this->get_data() );
250 }
251
252 /**
253 * Returns all data for this object.
254 *
255 * @since 2.6.0
256 * @return array
257 */
258 public function get_data() {
259 return array_merge( array( 'id' => $this->get_id() ), $this->data, array( 'meta_data' => $this->get_meta_data() ) );
260 }
261
262 /**
263 * Returns array of expected data keys for this object.
264 *
265 * @since 3.0.0
266 * @return array
267 */
268 public function get_data_keys() {
269 return array_keys( $this->data );
270 }
271
272 /**
273 * Returns all "extra" data keys for an object (for sub objects like product types).
274 *
275 * @since 3.0.0
276 * @return array
277 */
278 public function get_extra_data_keys() {
279 return array_keys( $this->extra_data );
280 }
281
282 /**
283 * Filter null meta values from array.
284 *
285 * @since 3.0.0
286 * @param mixed $meta Meta value to check.
287 * @return bool
288 */
289 protected function filter_null_meta( $meta ) {
290 return ! is_null( $meta->value );
291 }
292
293 /**
294 * Get All Meta Data.
295 *
296 * @since 2.6.0
297 * @return array of objects.
298 */
299 public function get_meta_data() {
300 $this->maybe_read_meta_data();
301 return array_values( array_filter( $this->meta_data, array( $this, 'filter_null_meta' ) ) );
302 }
303
304 /**
305 * Check if the key is an internal one.
306 *
307 * @since 3.2.0
308 * @param string $key Key to check.
309 * @return bool true if it's an internal key, false otherwise
310 */
311 protected function is_internal_meta_key( $key ) {
312 $internal_meta_key = ! empty( $key ) && $this->data_store && in_array( $key, $this->data_store->get_internal_meta_keys(), true );
313
314 if ( ! $internal_meta_key ) {
315 return false;
316 }
317
318 $has_setter_or_getter = is_callable( array( $this, 'set_' . ltrim( $key, '_' ) ) ) || is_callable( array( $this, 'get_' . ltrim( $key, '_' ) ) );
319
320 if ( ! $has_setter_or_getter ) {
321 return false;
322 }
323
324 if ( in_array( $key, $this->legacy_datastore_props, true ) ) {
325 return true; // return without warning because we don't want to break legacy code which was calling add/get/update/delete meta.
326 }
327
328 /* translators: %s: $key Key to check */
329 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' );
330
331 return true;
332 }
333
334 /**
335 * Get Meta Data by Key.
336 *
337 * @since 2.6.0
338 * @param string $key Meta Key.
339 * @param bool $single return first found meta with key, or all with $key.
340 * @param string $context What the value is for. Valid values are view and edit.
341 * @return mixed
342 */
343 public function get_meta( $key = '', $single = true, $context = 'view' ) {
344 if ( $this->is_internal_meta_key( $key ) ) {
345 $function = 'get_' . ltrim( $key, '_' );
346
347 if ( is_callable( array( $this, $function ) ) ) {
348 return $this->{$function}();
349 }
350 }
351
352 $this->maybe_read_meta_data();
353 $meta_data = $this->get_meta_data();
354 $array_keys = array_keys( wp_list_pluck( $meta_data, 'key' ), $key, true );
355 $value = $single ? '' : array();
356
357 if ( ! empty( $array_keys ) ) {
358 // 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()).
359 if ( $single ) {
360 $value = $meta_data[ current( $array_keys ) ]->value;
361 } else {
362 $value = array_intersect_key( $meta_data, array_flip( $array_keys ) );
363 }
364 }
365
366 if ( 'view' === $context ) {
367 $value = apply_filters( $this->get_hook_prefix() . $key, $value, $this );
368 }
369
370 return $value;
371 }
372
373 /**
374 * See if meta data exists, since get_meta always returns a '' or array().
375 *
376 * @since 3.0.0
377 * @param string $key Meta Key.
378 * @return boolean
379 */
380 public function meta_exists( $key = '' ) {
381 $this->maybe_read_meta_data();
382 $array_keys = wp_list_pluck( $this->get_meta_data(), 'key' );
383 return in_array( $key, $array_keys, true );
384 }
385
386 /**
387 * Set all meta data from array.
388 *
389 * @since 2.6.0
390 * @param array $data Key/Value pairs.
391 */
392 public function set_meta_data( $data ) {
393 if ( ! empty( $data ) && is_array( $data ) ) {
394 $this->maybe_read_meta_data();
395 foreach ( $data as $meta ) {
396 $meta = (array) $meta;
397 if ( isset( $meta['key'], $meta['value'], $meta['id'] ) ) {
398 $this->meta_data[] = new WC_Meta_Data(
399 array(
400 'id' => $meta['id'],
401 'key' => $meta['key'],
402 'value' => $meta['value'],
403 )
404 );
405 }
406 }
407 }
408 }
409
410 /**
411 * Add meta data.
412 *
413 * @since 2.6.0
414 *
415 * @param string $key Meta key.
416 * @param string|array $value Meta value.
417 * @param bool $unique Should this be a unique key?.
418 */
419 public function add_meta_data( $key, $value, $unique = false ) {
420 if ( $this->is_internal_meta_key( $key ) ) {
421 $function = 'set_' . ltrim( $key, '_' );
422
423 if ( is_callable( array( $this, $function ) ) ) {
424 return $this->{$function}( $value );
425 }
426 }
427
428 $this->maybe_read_meta_data();
429 if ( $unique ) {
430 $this->delete_meta_data( $key );
431 }
432 $this->meta_data[] = new WC_Meta_Data(
433 array(
434 'key' => $key,
435 'value' => $value,
436 )
437 );
438 }
439
440 /**
441 * Update meta data by key or ID, if provided.
442 *
443 * @since 2.6.0
444 *
445 * @param string $key Meta key.
446 * @param string|array $value Meta value.
447 * @param int $meta_id Meta ID.
448 */
449 public function update_meta_data( $key, $value, $meta_id = 0 ) {
450 if ( $this->is_internal_meta_key( $key ) ) {
451 $function = 'set_' . ltrim( $key, '_' );
452
453 if ( is_callable( array( $this, $function ) ) ) {
454 return $this->{$function}( $value );
455 }
456 }
457
458 $this->maybe_read_meta_data();
459
460 $array_key = false;
461
462 if ( $meta_id ) {
463 $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id, true );
464 $array_key = $array_keys ? current( $array_keys ) : false;
465 } else {
466 // Find matches by key.
467 $matches = array();
468 foreach ( $this->meta_data as $meta_data_array_key => $meta ) {
469 if ( $meta->key === $key ) {
470 $matches[] = $meta_data_array_key;
471 }
472 }
473
474 if ( ! empty( $matches ) ) {
475 // Set matches to null so only one key gets the new value.
476 foreach ( $matches as $meta_data_array_key ) {
477 $this->meta_data[ $meta_data_array_key ]->value = null;
478 }
479 $array_key = current( $matches );
480 }
481 }
482
483 if ( false !== $array_key ) {
484 $meta = $this->meta_data[ $array_key ];
485 $meta->key = $key;
486 $meta->value = $value;
487 } else {
488 $this->add_meta_data( $key, $value, true );
489 }
490 }
491
492 /**
493 * Delete meta data.
494 *
495 * @since 2.6.0
496 * @param string $key Meta key.
497 */
498 public function delete_meta_data( $key ) {
499 $this->maybe_read_meta_data();
500 $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key, true );
501
502 if ( $array_keys ) {
503 foreach ( $array_keys as $array_key ) {
504 $this->meta_data[ $array_key ]->value = null;
505 }
506 }
507 }
508
509 /**
510 * Delete meta data with a matching value.
511 *
512 * @since 7.7.0
513 * @param string $key Meta key.
514 * @param mixed $value Meta value. Entries will only be removed that match the value.
515 */
516 public function delete_meta_data_value( $key, $value ) {
517 $this->maybe_read_meta_data();
518 $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key, true );
519
520 if ( $array_keys ) {
521 foreach ( $array_keys as $array_key ) {
522 if ( $value === $this->meta_data[ $array_key ]->value ) {
523 $this->meta_data[ $array_key ]->value = null;
524 }
525 }
526 }
527 }
528
529 /**
530 * Delete meta data.
531 *
532 * @since 2.6.0
533 * @param int $mid Meta ID.
534 */
535 public function delete_meta_data_by_mid( $mid ) {
536 $this->maybe_read_meta_data();
537 $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), (int) $mid, true );
538
539 if ( $array_keys ) {
540 foreach ( $array_keys as $array_key ) {
541 $this->meta_data[ $array_key ]->value = null;
542 }
543 }
544 }
545
546 /**
547 * Read meta data if null.
548 *
549 * @since 3.0.0
550 */
551 protected function maybe_read_meta_data() {
552 if ( is_null( $this->meta_data ) ) {
553 $this->read_meta_data();
554 }
555 }
556
557 /**
558 * Helper method to compute meta cache key. Different from WP Meta cache key in that meta data cached using this key also contains meta_id column.
559 *
560 * @since 4.7.0
561 *
562 * @return string
563 */
564 public function get_meta_cache_key() {
565 if ( ! $this->get_id() ) {
566 wc_doing_it_wrong( 'get_meta_cache_key', 'ID needs to be set before fetching a cache key.', '4.7.0' );
567 return false;
568 }
569 return self::generate_meta_cache_key( $this->get_id(), $this->cache_group );
570 }
571
572 /**
573 * Generate cache key from id and group.
574 *
575 * @since 4.7.0
576 *
577 * @param int|string $id Object ID.
578 * @param string $cache_group Group name use to store cache. Whole group cache can be invalidated in one go.
579 *
580 * @return string Meta cache key.
581 */
582 public static function generate_meta_cache_key( $id, $cache_group ) {
583 return WC_Cache_Helper::get_cache_prefix( $cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $id ) . 'object_meta_' . $id;
584 }
585
586 /**
587 * Prime caches for raw meta data. This includes meta_id column as well, which is not included by default in WP meta data.
588 *
589 * @since 4.7.0
590 *
591 * @param array $raw_meta_data_collection Array of objects of { object_id => array( meta_row_1, meta_row_2, ... }.
592 * @param string $cache_group Name of cache group.
593 */
594 public static function prime_raw_meta_data_cache( $raw_meta_data_collection, $cache_group ) {
595 foreach ( $raw_meta_data_collection as $object_id => $raw_meta_data_array ) {
596 $cache_key = self::generate_meta_cache_key( $object_id, $cache_group );
597 wp_cache_set( $cache_key, $raw_meta_data_array, $cache_group );
598 }
599 }
600
601 /**
602 * Read Meta Data from the database. Ignore any internal properties.
603 * Uses it's own caches because get_metadata does not provide meta_ids.
604 *
605 * @since 2.6.0
606 * @param bool $force_read True to force a new DB read (and update cache).
607 */
608 public function read_meta_data( $force_read = false ) {
609 $this->meta_data = array();
610 $cache_loaded = false;
611
612 if ( ! $this->get_id() ) {
613 return;
614 }
615
616 if ( ! $this->data_store ) {
617 return;
618 }
619
620 if ( ! empty( $this->cache_group ) ) {
621 // Prefix by group allows invalidation by group until https://core.trac.wordpress.org/ticket/4476 is implemented.
622 $cache_key = $this->get_meta_cache_key();
623 }
624
625 if ( ! $force_read ) {
626 if ( ! empty( $this->cache_group ) ) {
627 $cached_meta = wp_cache_get( $cache_key, $this->cache_group );
628 $cache_loaded = is_array( $cached_meta );
629 }
630 }
631
632 // We filter the raw meta data again when loading from cache, in case we cached in an earlier version where filter conditions were different.
633 $raw_meta_data = $cache_loaded ? $this->data_store->filter_raw_meta_data( $this, $cached_meta ) : $this->data_store->read_meta( $this );
634
635 if ( is_array( $raw_meta_data ) ) {
636 $this->init_meta_data( $raw_meta_data );
637 if ( ! $cache_loaded && ! empty( $this->cache_group ) ) {
638 wp_cache_set( $cache_key, $raw_meta_data, $this->cache_group );
639 }
640 }
641 }
642
643 /**
644 * Helper function to initialize metadata entries from filtered raw meta data.
645 *
646 * @param array $filtered_meta_data Filtered metadata fetched from DB.
647 */
648 public function init_meta_data( array $filtered_meta_data = array() ) {
649 $this->meta_data = array();
650 foreach ( $filtered_meta_data as $meta ) {
651 $this->meta_data[] = new WC_Meta_Data(
652 array(
653 'id' => (int) $meta->meta_id,
654 'key' => $meta->meta_key,
655 'value' => maybe_unserialize( $meta->meta_value ),
656 )
657 );
658 }
659 }
660
661 /**
662 * Update Meta Data in the database.
663 *
664 * @since 2.6.0
665 */
666 public function save_meta_data() {
667 if ( ! $this->data_store || is_null( $this->meta_data ) ) {
668 return;
669 }
670 foreach ( $this->meta_data as $array_key => $meta ) {
671 if ( is_null( $meta->value ) ) {
672 if ( ! empty( $meta->id ) ) {
673 $this->data_store->delete_meta( $this, $meta );
674 unset( $this->meta_data[ $array_key ] );
675 }
676 } elseif ( empty( $meta->id ) ) {
677 $meta->id = $this->data_store->add_meta( $this, $meta );
678 $meta->apply_changes();
679 } else {
680 if ( $meta->get_changes() ) {
681 $this->data_store->update_meta( $this, $meta );
682 $meta->apply_changes();
683 }
684 }
685 }
686 if ( ! empty( $this->cache_group ) ) {
687 $cache_key = self::generate_meta_cache_key( $this->get_id(), $this->cache_group );
688 wp_cache_delete( $cache_key, $this->cache_group );
689 }
690 }
691
692 /**
693 * Set ID.
694 *
695 * @since 3.0.0
696 * @param int $id ID.
697 */
698 public function set_id( $id ) {
699 $this->id = absint( $id );
700 }
701
702 /**
703 * Set all props to default values.
704 *
705 * @since 3.0.0
706 */
707 public function set_defaults() {
708 $this->data = $this->default_data;
709 $this->changes = array();
710 $this->set_object_read( false );
711 }
712
713 /**
714 * Set object read property.
715 *
716 * @since 3.0.0
717 * @param boolean $read Should read?.
718 */
719 public function set_object_read( $read = true ) {
720 $this->object_read = (bool) $read;
721 }
722
723 /**
724 * Get object read property.
725 *
726 * @since 3.0.0
727 * @return boolean
728 */
729 public function get_object_read() {
730 return (bool) $this->object_read;
731 }
732
733 /**
734 * Set a collection of props in one go, collect any errors, and return the result.
735 * Only sets using public methods.
736 *
737 * @since 3.0.0
738 *
739 * @param array $props Key value pairs to set. Key is the prop and should map to a setter function name.
740 * @param string $context In what context to run this.
741 *
742 * @return bool|WP_Error
743 */
744 public function set_props( $props, $context = 'set' ) {
745 $errors = false;
746
747 foreach ( $props as $prop => $value ) {
748 try {
749 /**
750 * Checks if the prop being set is allowed, and the value is not null.
751 */
752 if ( is_null( $value ) || in_array( $prop, array( 'prop', 'date_prop', 'meta_data' ), true ) ) {
753 continue;
754 }
755 $setter = "set_$prop";
756
757 if ( is_callable( array( $this, $setter ) ) ) {
758 $this->{$setter}( $value );
759 }
760 } catch ( WC_Data_Exception $e ) {
761 if ( ! $errors ) {
762 $errors = new WP_Error();
763 }
764 $errors->add( $e->getErrorCode(), $e->getMessage() );
765 }
766 }
767
768 return $errors && count( $errors->get_error_codes() ) ? $errors : true;
769 }
770
771 /**
772 * Sets a prop for a setter method.
773 *
774 * This stores changes in a special array so we can track what needs saving
775 * the the DB later.
776 *
777 * @since 3.0.0
778 * @param string $prop Name of prop to set.
779 * @param mixed $value Value of the prop.
780 */
781 protected function set_prop( $prop, $value ) {
782 if ( array_key_exists( $prop, $this->data ) ) {
783 if ( true === $this->object_read ) {
784 if ( $value !== $this->data[ $prop ] || array_key_exists( $prop, $this->changes ) ) {
785 $this->changes[ $prop ] = $value;
786 }
787 } else {
788 $this->data[ $prop ] = $value;
789 }
790 }
791 }
792
793 /**
794 * Return data changes only.
795 *
796 * @since 3.0.0
797 * @return array
798 */
799 public function get_changes() {
800 return $this->changes;
801 }
802
803 /**
804 * Merge changes with data and clear.
805 *
806 * @since 3.0.0
807 */
808 public function apply_changes() {
809 $this->data = array_replace_recursive( $this->data, $this->changes ); // @codingStandardsIgnoreLine
810 $this->changes = array();
811 }
812
813 /**
814 * Prefix for action and filter hooks on data.
815 *
816 * @since 3.0.0
817 * @return string
818 */
819 protected function get_hook_prefix() {
820 return 'woocommerce_' . $this->object_type . '_get_';
821 }
822
823 /**
824 * Gets a prop for a getter method.
825 *
826 * Gets the value from either current pending changes, or the data itself.
827 * Context controls what happens to the value before it's returned.
828 *
829 * @since 3.0.0
830 * @param string $prop Name of prop to get.
831 * @param string $context What the value is for. Valid values are view and edit.
832 * @return mixed
833 */
834 protected function get_prop( $prop, $context = 'view' ) {
835 $value = null;
836
837 if ( array_key_exists( $prop, $this->data ) ) {
838 $value = array_key_exists( $prop, $this->changes ) ? $this->changes[ $prop ] : $this->data[ $prop ];
839
840 if ( 'view' === $context ) {
841 $value = apply_filters( $this->get_hook_prefix() . $prop, $value, $this );
842 }
843 }
844
845 return $value;
846 }
847
848 /**
849 * Sets a date prop whilst handling formatting and datetime objects.
850 *
851 * @since 3.0.0
852 * @param string $prop Name of prop to set.
853 * @param string|integer $value Value of the prop.
854 */
855 protected function set_date_prop( $prop, $value ) {
856 try {
857 if ( empty( $value ) || '0000-00-00 00:00:00' === $value ) {
858 $this->set_prop( $prop, null );
859 return;
860 }
861
862 if ( is_a( $value, 'WC_DateTime' ) ) {
863 $datetime = $value;
864 } elseif ( is_numeric( $value ) ) {
865 // Timestamps are handled as UTC timestamps in all cases.
866 $datetime = new WC_DateTime( "@{$value}", new DateTimeZone( 'UTC' ) );
867 } else {
868 // Strings are defined in local WP timezone. Convert to UTC.
869 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 ) ) {
870 $offset = ! empty( $date_bits[7] ) ? iso8601_timezone_to_offset( $date_bits[7] ) : wc_timezone_offset();
871 $timestamp = gmmktime( $date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1] ) - $offset;
872 } else {
873 $timestamp = wc_string_to_timestamp( get_gmt_from_date( gmdate( 'Y-m-d H:i:s', wc_string_to_timestamp( $value ) ) ) );
874 }
875 $datetime = new WC_DateTime( "@{$timestamp}", new DateTimeZone( 'UTC' ) );
876 }
877
878 // Set local timezone or offset.
879 if ( get_option( 'timezone_string' ) ) {
880 $datetime->setTimezone( new DateTimeZone( wc_timezone_string() ) );
881 } else {
882 $datetime->set_utc_offset( wc_timezone_offset() );
883 }
884
885 $this->set_prop( $prop, $datetime );
886 } catch ( Exception $e ) {} // @codingStandardsIgnoreLine.
887 }
888
889 /**
890 * When invalid data is found, throw an exception unless reading from the DB.
891 *
892 * @throws WC_Data_Exception Data Exception.
893 * @since 3.0.0
894 * @param string $code Error code.
895 * @param string $message Error message.
896 * @param int $http_status_code HTTP status code.
897 * @param array $data Extra error data.
898 */
899 protected function error( $code, $message, $http_status_code = 400, $data = array() ) {
900 throw new WC_Data_Exception( $code, $message, $http_status_code, $data );
901 }
902 }
903