PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.4
WooCommerce v10.9.4
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 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-address-provider.php 9 months ago abstract-wc-data.php 4 months ago abstract-wc-deprecated-hooks.php 6 years ago abstract-wc-integration.php 5 years ago abstract-wc-log-handler.php 2 years ago abstract-wc-object-query.php 5 years ago abstract-wc-order.php 1 month ago abstract-wc-payment-gateway.php 1 month ago abstract-wc-payment-token.php 5 years ago abstract-wc-privacy.php 5 years ago abstract-wc-product.php 1 month ago abstract-wc-session.php 10 months ago abstract-wc-settings-api.php 1 year ago abstract-wc-shipping-method.php 4 months ago abstract-wc-widget.php 5 years ago class-wc-background-process.php 5 years ago
abstract-wc-data.php
1004 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 * @since 2.6.0
23 * @version 10.2.0
24 * @package WooCommerce\Abstracts
25 */
26 abstract class WC_Data {
27
28 /**
29 * Clone mode constant: Duplicate mode clears meta IDs (default, for backward compatibility).
30 *
31 * @since 10.4.0
32 */
33 const CLONE_MODE_DUPLICATE = 'duplicate';
34
35 /**
36 * Clone mode constant: Cache mode preserves meta IDs.
37 *
38 * @since 10.4.0
39 */
40 const CLONE_MODE_CACHE = 'cache';
41
42 /**
43 * ID for this object.
44 *
45 * @since 3.0.0
46 * @var int
47 */
48 protected $id = 0;
49
50 /**
51 * Core data for this object. Name value pairs (name + default value).
52 *
53 * @since 3.0.0
54 * @var array
55 */
56 protected $data = array();
57
58 /**
59 * Core data changes for this object.
60 *
61 * @since 3.0.0
62 * @var array
63 */
64 protected $changes = array();
65
66 /**
67 * This is false until the object is read from the DB.
68 *
69 * @since 3.0.0
70 * @var bool
71 */
72 protected $object_read = false;
73
74 /**
75 * This is the name of this object type.
76 *
77 * @since 3.0.0
78 * @var string
79 */
80 protected $object_type = 'data';
81
82 /**
83 * Extra data for this object. Name value pairs (name + default value).
84 * Used as a standard way for sub classes (like product types) to add
85 * additional information to an inherited class.
86 *
87 * @since 3.0.0
88 * @var array
89 */
90 protected $extra_data = array();
91
92 /**
93 * Set to _data on construct so we can track and reset data if needed.
94 *
95 * @since 3.0.0
96 * @var array
97 */
98 protected $default_data = array();
99
100 /**
101 * Contains a reference to the data store for this class.
102 *
103 * @since 3.0.0
104 * @var object
105 */
106 protected $data_store;
107
108 /**
109 * Stores meta in cache for future reads.
110 * A group must be set to to enable caching.
111 *
112 * @since 3.0.0
113 * @var string
114 */
115 protected $cache_group = '';
116
117 /**
118 * Stores additional meta data.
119 *
120 * @since 3.0.0
121 * @var WC_Meta_Data[]|null
122 */
123 protected $meta_data = null;
124
125 /**
126 * Clone mode for controlling meta ID handling during clone operations.
127 *
128 * @since 10.4.0
129 * @var string Either CLONE_MODE_DUPLICATE (default, clears meta IDs) or CLONE_MODE_CACHE (preserves meta IDs).
130 */
131 protected $clone_mode = self::CLONE_MODE_DUPLICATE;
132
133 /**
134 * 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.
135 * 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.
136 *
137 * 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.
138 *
139 * @var array
140 */
141 protected $legacy_datastore_props = array();
142
143 /**
144 * Default constructor.
145 *
146 * @param int|object|array $read ID to load from the DB (optional) or already queried data.
147 */
148 public function __construct( $read = 0 ) {
149 $this->data = array_merge( $this->data, $this->extra_data );
150 $this->default_data = $this->data;
151 }
152
153 /**
154 * Only store the object ID to avoid serializing the data object instance.
155 *
156 * @return array
157 */
158 public function __sleep() {
159 return array( 'id' );
160 }
161
162 /**
163 * Re-run the constructor with the object ID.
164 *
165 * If the object no longer exists, remove the ID.
166 */
167 public function __wakeup() {
168 try {
169 $this->__construct( absint( $this->id ) );
170 } catch ( Exception $e ) {
171 $this->set_id( 0 );
172 $this->set_object_read( true );
173 }
174 }
175
176 /**
177 * When the object is cloned, make sure meta is cloned correctly.
178 *
179 * Meta ID handling depends on the clone mode:
180 * - CLONE_MODE_DUPLICATE (default): Forces reading of Meta and clears meta IDs for duplication (backward compatible).
181 * - CLONE_MODE_CACHE: Preserves meta IDs for caching purposes.
182 *
183 * @since 3.0.2
184 */
185 public function __clone() {
186 if ( self::CLONE_MODE_DUPLICATE === $this->clone_mode ) {
187 $this->maybe_read_meta_data();
188 }
189 if ( ! empty( $this->meta_data ) ) {
190 foreach ( $this->meta_data as $array_key => $meta ) {
191 $this->meta_data[ $array_key ] = clone $meta;
192
193 // Only clear meta IDs in duplicate mode (maintains backward compatibility).
194 if ( self::CLONE_MODE_DUPLICATE === $this->clone_mode && ! empty( $meta->id ) ) {
195 $this->meta_data[ $array_key ]->id = null;
196 }
197 }
198 }
199 }
200
201 /**
202 * Set the clone mode.
203 *
204 * This controls how meta IDs are handled when the object is cloned:
205 * - CLONE_MODE_DUPLICATE (default): Clears meta IDs for duplication workflows
206 * - CLONE_MODE_CACHE: Preserves meta IDs for caching workflows
207 *
208 * @since 10.4.0
209 * @param string $mode One of the CLONE_MODE_* constants.
210 * @throws InvalidArgumentException If an invalid mode is provided.
211 */
212 public function set_clone_mode( $mode ) {
213 if ( ! in_array( $mode, array( self::CLONE_MODE_DUPLICATE, self::CLONE_MODE_CACHE ), true ) ) {
214 throw new InvalidArgumentException( 'Clone mode must be either WC_Data::CLONE_MODE_DUPLICATE or WC_Data::CLONE_MODE_CACHE' );
215 }
216 $this->clone_mode = $mode;
217 }
218
219 /**
220 * Get the current clone mode.
221 *
222 * @since 10.4.0
223 * @return string The current clone mode (one of the CLONE_MODE_* constants).
224 */
225 public function get_clone_mode() {
226 return $this->clone_mode;
227 }
228
229 /**
230 * Get the data store.
231 *
232 * @since 3.0.0
233 * @return object
234 */
235 public function get_data_store() {
236 return $this->data_store;
237 }
238
239 /**
240 * Returns the unique ID for this object.
241 *
242 * @since 2.6.0
243 * @return int
244 */
245 public function get_id() {
246 return $this->id;
247 }
248
249 /**
250 * Delete an object, set the ID to 0, and return result.
251 *
252 * @since 2.6.0
253 * @param bool $force_delete Should the date be deleted permanently.
254 * @return bool result
255 */
256 public function delete( $force_delete = false ) {
257 /**
258 * Filters whether an object deletion should take place. Equivalent to `pre_delete_post`.
259 *
260 * @param mixed $check Whether to go ahead with deletion.
261 * @param WC_Data $this The data object being deleted.
262 * @param bool $force_delete Whether to bypass the trash.
263 *
264 * @since 8.1.0.
265 */
266 $check = apply_filters( "woocommerce_pre_delete_$this->object_type", null, $this, $force_delete );
267 if ( null !== $check ) {
268 return $check;
269 }
270 if ( $this->data_store ) {
271 $this->data_store->delete( $this, array( 'force_delete' => $force_delete ) );
272 $this->set_id( 0 );
273 return true;
274 }
275 return false;
276 }
277
278 /**
279 * Save should create or update based on object existence.
280 *
281 * @since 2.6.0
282 * @return int
283 */
284 public function save() {
285 if ( ! $this->data_store ) {
286 return $this->get_id();
287 }
288
289 /**
290 * Trigger action before saving to the DB. Allows you to adjust object props before save.
291 *
292 * @param WC_Data $this The object being saved.
293 * @param WC_Data_Store_WP $data_store THe data store persisting the data.
294 */
295 do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
296
297 if ( $this->get_id() ) {
298 $this->data_store->update( $this );
299 } else {
300 $this->data_store->create( $this );
301 }
302
303 /**
304 * Trigger action after saving to the DB.
305 *
306 * @param WC_Data $this The object being saved.
307 * @param WC_Data_Store_WP $data_store THe data store persisting the data.
308 */
309 do_action( 'woocommerce_after_' . $this->object_type . '_object_save', $this, $this->data_store );
310
311 return $this->get_id();
312 }
313
314 /**
315 * Change data to JSON format.
316 *
317 * @since 2.6.0
318 * @return string Data in JSON format.
319 */
320 public function __toString() {
321 return wp_json_encode( $this->get_data() );
322 }
323
324 /**
325 * Returns all data for this object.
326 *
327 * @since 2.6.0
328 * @return array
329 */
330 public function get_data() {
331 return array_merge( array( 'id' => $this->get_id() ), $this->data, array( 'meta_data' => $this->get_meta_data() ) );
332 }
333
334 /**
335 * Returns array of expected data keys for this object.
336 *
337 * @since 3.0.0
338 * @return array
339 */
340 public function get_data_keys() {
341 return array_keys( $this->data );
342 }
343
344 /**
345 * Returns all "extra" data keys for an object (for sub objects like product types).
346 *
347 * @since 3.0.0
348 * @return array
349 */
350 public function get_extra_data_keys() {
351 return array_keys( $this->extra_data );
352 }
353
354 /**
355 * Filter null meta values from array.
356 *
357 * @since 3.0.0
358 * @param mixed $meta Meta value to check.
359 * @return bool
360 */
361 protected function filter_null_meta( $meta ) {
362 return ! is_null( $meta->value );
363 }
364
365 /**
366 * Get All Meta Data.
367 *
368 * @since 2.6.0
369 * @return array of objects.
370 */
371 public function get_meta_data() {
372 $this->maybe_read_meta_data();
373 return array_values( array_filter( $this->meta_data, array( $this, 'filter_null_meta' ) ) );
374 }
375
376 /**
377 * Check if the key is an internal one.
378 *
379 * @since 3.2.0
380 * @param string $key Key to check.
381 * @return bool true if it's an internal key, false otherwise
382 */
383 protected function is_internal_meta_key( $key ) {
384 $internal_meta_key = ! empty( $key ) && $this->data_store && in_array( $key, $this->data_store->get_internal_meta_keys(), true );
385
386 if ( ! $internal_meta_key ) {
387 return false;
388 }
389
390 $has_setter_or_getter = is_callable( array( $this, 'set_' . ltrim( $key, '_' ) ) ) || is_callable( array( $this, 'get_' . ltrim( $key, '_' ) ) );
391
392 if ( ! $has_setter_or_getter ) {
393 return false;
394 }
395
396 if ( in_array( $key, $this->legacy_datastore_props, true ) ) {
397 return true; // return without warning because we don't want to break legacy code which was calling add/get/update/delete meta.
398 }
399
400 /* translators: %s: $key Key to check */
401 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' );
402
403 return true;
404 }
405
406 /**
407 * Get Meta Data by Key.
408 *
409 * @since 2.6.0
410 * @param string $key Meta Key.
411 * @param bool $single return first found meta with key, or all with $key.
412 * @param string $context What the value is for. Valid values are view and edit.
413 * @return mixed
414 */
415 public function get_meta( $key = '', $single = true, $context = 'view' ) {
416 if ( $this->is_internal_meta_key( $key ) ) {
417 $function = 'get_' . ltrim( $key, '_' );
418
419 if ( is_callable( array( $this, $function ) ) ) {
420 return $this->{$function}();
421 }
422 }
423
424 $meta_data = $this->get_meta_data();
425 $array_keys = array_keys( wp_list_pluck( $meta_data, 'key' ), $key, true );
426 $value = $single ? '' : array();
427
428 if ( ! empty( $array_keys ) ) {
429 // 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()).
430 if ( $single ) {
431 $value = $meta_data[ current( $array_keys ) ]->value;
432 } else {
433 $value = array_intersect_key( $meta_data, array_flip( $array_keys ) );
434 }
435 }
436
437 if ( 'view' === $context ) {
438 $value = apply_filters( $this->get_hook_prefix() . $key, $value, $this );
439 }
440
441 return $value;
442 }
443
444 /**
445 * See if meta data exists, since get_meta always returns a '' or array().
446 *
447 * @since 3.0.0
448 * @param string $key Meta Key.
449 * @return boolean
450 */
451 public function meta_exists( $key = '' ) {
452 $array_keys = wp_list_pluck( $this->get_meta_data(), 'key' );
453 return in_array( $key, $array_keys, true );
454 }
455
456 /**
457 * Set all meta data from array.
458 *
459 * @since 2.6.0
460 * @param array $data Key/Value pairs.
461 */
462 public function set_meta_data( $data ) {
463 if ( ! empty( $data ) && is_array( $data ) ) {
464 $this->maybe_read_meta_data();
465 foreach ( $data as $meta ) {
466 $meta = (array) $meta;
467 if ( isset( $meta['key'], $meta['value'], $meta['id'] ) ) {
468 $this->meta_data[] = new WC_Meta_Data(
469 array(
470 'id' => $meta['id'],
471 'key' => $meta['key'],
472 'value' => $meta['value'],
473 )
474 );
475 }
476 }
477 }
478 }
479
480 /**
481 * Add meta data.
482 *
483 * @since 2.6.0
484 *
485 * @param string $key Meta key.
486 * @param string|array $value Meta value.
487 * @param bool $unique Should this be a unique key?.
488 */
489 public function add_meta_data( $key, $value, $unique = false ) {
490 if ( $this->is_internal_meta_key( $key ) ) {
491 $function = 'set_' . ltrim( $key, '_' );
492
493 if ( is_callable( array( $this, $function ) ) ) {
494 return $this->{$function}( $value );
495 }
496 }
497
498 $this->maybe_read_meta_data();
499 if ( $unique ) {
500 $this->delete_meta_data( $key );
501 }
502 $this->meta_data[] = new WC_Meta_Data(
503 array(
504 'key' => $key,
505 'value' => $value,
506 )
507 );
508 }
509
510 /**
511 * Update meta data by key or ID, if provided.
512 *
513 * @since 2.6.0
514 *
515 * @param string $key Meta key.
516 * @param string|array $value Meta value.
517 * @param int $meta_id Meta ID.
518 */
519 public function update_meta_data( $key, $value, $meta_id = 0 ) {
520 if ( $this->is_internal_meta_key( $key ) ) {
521 $function = 'set_' . ltrim( $key, '_' );
522
523 if ( is_callable( array( $this, $function ) ) ) {
524 return $this->{$function}( $value );
525 }
526 }
527
528 $this->maybe_read_meta_data();
529
530 $array_key = false;
531
532 if ( $meta_id ) {
533 $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id, true );
534 $array_key = $array_keys ? current( $array_keys ) : false;
535 } else {
536 // Find matches by key.
537 $matches = array();
538 foreach ( $this->meta_data as $meta_data_array_key => $meta ) {
539 if ( $meta->key === $key ) {
540 $matches[] = $meta_data_array_key;
541 }
542 }
543
544 if ( ! empty( $matches ) ) {
545 // Update first match and delete the rest.
546 $array_key = array_shift( $matches );
547 foreach ( $matches as $meta_data_array_key ) {
548 $this->meta_data[ $meta_data_array_key ]->value = null;
549 }
550 }
551 }
552
553 if ( false !== $array_key ) {
554 $meta = $this->meta_data[ $array_key ];
555 $meta->key = $key;
556 $meta->value = $value;
557 } else {
558 $this->add_meta_data( $key, $value, true );
559 }
560 }
561
562 /**
563 * Delete meta data.
564 *
565 * @since 2.6.0
566 * @param string $key Meta key.
567 */
568 public function delete_meta_data( $key ) {
569 $this->maybe_read_meta_data();
570 $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key, true );
571
572 if ( $array_keys ) {
573 foreach ( $array_keys as $array_key ) {
574 $this->meta_data[ $array_key ]->value = null;
575 }
576 }
577 }
578
579 /**
580 * Delete meta data with a matching value.
581 *
582 * @since 7.7.0
583 * @param string $key Meta key.
584 * @param mixed $value Meta value. Entries will only be removed that match the value.
585 */
586 public function delete_meta_data_value( $key, $value ) {
587 $this->maybe_read_meta_data();
588 $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key, true );
589
590 if ( $array_keys ) {
591 foreach ( $array_keys as $array_key ) {
592 if ( $value === $this->meta_data[ $array_key ]->value ) {
593 $this->meta_data[ $array_key ]->value = null;
594 }
595 }
596 }
597 }
598
599 /**
600 * Delete meta data.
601 *
602 * @since 2.6.0
603 * @param int $mid Meta ID.
604 */
605 public function delete_meta_data_by_mid( $mid ) {
606 $this->maybe_read_meta_data();
607 $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), (int) $mid, true );
608
609 if ( $array_keys ) {
610 foreach ( $array_keys as $array_key ) {
611 $this->meta_data[ $array_key ]->value = null;
612 }
613 }
614 }
615
616 /**
617 * Read meta data if null.
618 *
619 * @since 3.0.0
620 */
621 protected function maybe_read_meta_data() {
622 if ( is_null( $this->meta_data ) ) {
623 $this->read_meta_data();
624 }
625 }
626
627 /**
628 * 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.
629 *
630 * @since 4.7.0
631 *
632 * @return string
633 */
634 public function get_meta_cache_key() {
635 if ( ! $this->get_id() ) {
636 wc_doing_it_wrong( 'get_meta_cache_key', 'ID needs to be set before fetching a cache key.', '4.7.0' );
637 return false;
638 }
639 return self::generate_meta_cache_key( $this->get_id(), $this->cache_group );
640 }
641
642 /**
643 * Generate cache key from id and group.
644 *
645 * @since 4.7.0
646 *
647 * @param int|string $id Object ID.
648 * @param string $cache_group Group name use to store cache. Whole group cache can be invalidated in one go.
649 *
650 * @return string Meta cache key.
651 */
652 public static function generate_meta_cache_key( $id, $cache_group ) {
653 return WC_Cache_Helper::get_cache_prefix( $cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $id ) . 'object_meta_' . $id;
654 }
655
656 /**
657 * Prime caches for raw meta data. This includes meta_id column as well, which is not included by default in WP meta data.
658 *
659 * @since 4.7.0
660 *
661 * @param array $raw_meta_data_collection Array of objects of { object_id => array( meta_row_1, meta_row_2, ... }.
662 * @param string $cache_group Name of cache group.
663 */
664 public static function prime_raw_meta_data_cache( $raw_meta_data_collection, $cache_group ) {
665 foreach ( $raw_meta_data_collection as $object_id => $raw_meta_data_array ) {
666 $cache_key = self::generate_meta_cache_key( $object_id, $cache_group );
667 wp_cache_set( $cache_key, $raw_meta_data_array, $cache_group );
668 }
669 }
670
671 /**
672 * Read Meta Data from the database. Ignore any internal properties.
673 * Uses it's own caches because get_metadata does not provide meta_ids.
674 *
675 * @since 2.6.0
676 * @param bool $force_read True to force a new DB read (and update cache).
677 */
678 public function read_meta_data( $force_read = false ) {
679 $this->meta_data = array();
680 $cache_loaded = false;
681
682 if ( ! $this->get_id() ) {
683 return;
684 }
685
686 if ( ! $this->data_store ) {
687 return;
688 }
689
690 if ( ! empty( $this->cache_group ) ) {
691 // Prefix by group allows invalidation by group until https://core.trac.wordpress.org/ticket/4476 is implemented.
692 $cache_key = $this->get_meta_cache_key();
693 }
694
695 if ( ! $force_read ) {
696 if ( ! empty( $this->cache_group ) ) {
697 $cached_meta = wp_cache_get( $cache_key, $this->cache_group );
698 $cache_loaded = is_array( $cached_meta );
699 }
700 }
701
702 // We filter the raw meta data again when loading from cache, in case we cached in an earlier version where filter conditions were different.
703 $raw_meta_data = $cache_loaded ? $this->data_store->filter_raw_meta_data( $this, $cached_meta ) : $this->data_store->read_meta( $this );
704
705 if ( is_array( $raw_meta_data ) ) {
706 $this->init_meta_data( $raw_meta_data );
707 if ( ! $cache_loaded && ! empty( $this->cache_group ) ) {
708 wp_cache_set( $cache_key, $raw_meta_data, $this->cache_group );
709 }
710 }
711 }
712
713 /**
714 * Helper function to initialize metadata entries from filtered raw meta data.
715 *
716 * @param array $filtered_meta_data Filtered metadata fetched from DB.
717 */
718 public function init_meta_data( array $filtered_meta_data = array() ) {
719 $this->meta_data = array();
720 foreach ( $filtered_meta_data as $meta ) {
721 $this->meta_data[] = new WC_Meta_Data(
722 array(
723 'id' => (int) $meta->meta_id,
724 'key' => $meta->meta_key,
725 'value' => maybe_unserialize( $meta->meta_value ),
726 )
727 );
728 }
729 }
730
731 /**
732 * Update Meta Data in the database.
733 *
734 * @since 2.6.0
735 */
736 public function save_meta_data() {
737 if ( ! $this->data_store || is_null( $this->meta_data ) ) {
738 return;
739 }
740 foreach ( $this->meta_data as $array_key => $meta ) {
741 if ( is_null( $meta->value ) ) {
742 if ( ! empty( $meta->id ) ) {
743 $this->data_store->delete_meta( $this, $meta );
744 /**
745 * Fires immediately after deleting metadata.
746 *
747 * @param int $meta_id ID of deleted metadata entry.
748 * @param int $object_id Object ID.
749 * @param string $meta_key Metadata key.
750 * @param mixed $meta_value Metadata value (will be empty for delete).
751 */
752 do_action( "deleted_{$this->object_type}_meta", $meta->id, $this->get_id(), $meta->key, $meta->value );
753
754 unset( $this->meta_data[ $array_key ] );
755 }
756 } elseif ( empty( $meta->id ) ) {
757 $meta->id = $this->data_store->add_meta( $this, $meta );
758 /**
759 * Fires immediately after adding metadata.
760 *
761 * @param int $meta_id ID of added metadata entry.
762 * @param int $object_id Object ID.
763 * @param string $meta_key Metadata key.
764 * @param mixed $meta_value Metadata value.
765 */
766 do_action( "added_{$this->object_type}_meta", $meta->id, $this->get_id(), $meta->key, $meta->value );
767
768 $meta->apply_changes();
769 } elseif ( $meta->get_changes() ) {
770 $this->data_store->update_meta( $this, $meta );
771 /**
772 * Fires immediately after updating metadata.
773 *
774 * @param int $meta_id ID of updated metadata entry.
775 * @param int $object_id Object ID.
776 * @param string $meta_key Metadata key.
777 * @param mixed $meta_value Metadata value.
778 */
779 do_action( "updated_{$this->object_type}_meta", $meta->id, $this->get_id(), $meta->key, $meta->value );
780
781 $meta->apply_changes();
782 }
783 }
784 if ( ! empty( $this->cache_group ) ) {
785 $cache_key = self::generate_meta_cache_key( $this->get_id(), $this->cache_group );
786 wp_cache_delete( $cache_key, $this->cache_group );
787 }
788 }
789
790 /**
791 * Set ID.
792 *
793 * @since 3.0.0
794 * @param int $id ID.
795 */
796 public function set_id( $id ) {
797 $this->id = absint( $id );
798 }
799
800 /**
801 * Set all props to default values.
802 *
803 * @since 3.0.0
804 */
805 public function set_defaults() {
806 $this->data = $this->default_data;
807 $this->changes = array();
808 $this->set_object_read( false );
809 }
810
811 /**
812 * Set object read property.
813 *
814 * @since 3.0.0
815 * @param boolean $read Should read?.
816 */
817 public function set_object_read( $read = true ) {
818 $this->object_read = (bool) $read;
819 }
820
821 /**
822 * Get object read property.
823 *
824 * @since 3.0.0
825 * @return boolean
826 */
827 public function get_object_read() {
828 return (bool) $this->object_read;
829 }
830
831 /**
832 * Set a collection of props in one go, collect any errors, and return the result.
833 * Only sets using public methods.
834 *
835 * @since 3.0.0
836 *
837 * @param array $props Key value pairs to set. Key is the prop and should map to a setter function name.
838 * @param string $context In what context to run this.
839 *
840 * @return bool|WP_Error
841 */
842 public function set_props( $props, $context = 'set' ) {
843 $errors = false;
844
845 foreach ( $props as $prop => $value ) {
846 try {
847 /**
848 * Checks if the prop being set is allowed, and the value is not null.
849 */
850 if ( is_null( $value ) || in_array( $prop, array( 'prop', 'date_prop', 'meta_data' ), true ) ) {
851 continue;
852 }
853 $setter = "set_$prop";
854
855 if ( is_callable( array( $this, $setter ) ) ) {
856 $this->{$setter}( $value );
857 }
858 } catch ( WC_Data_Exception $e ) {
859 if ( ! $errors ) {
860 $errors = new WP_Error();
861 }
862 $errors->add( $e->getErrorCode(), $e->getMessage(), array( 'property_name' => $prop ) );
863 }
864 }
865
866 return $errors && count( $errors->get_error_codes() ) ? $errors : true;
867 }
868
869 /**
870 * Sets a prop for a setter method.
871 *
872 * This stores changes in a special array so we can track what needs saving
873 * the DB later.
874 *
875 * @since 3.0.0
876 * @param string $prop Name of prop to set.
877 * @param mixed $value Value of the prop.
878 */
879 protected function set_prop( $prop, $value ) {
880 if ( array_key_exists( $prop, $this->data ) ) {
881 if ( true === $this->object_read ) {
882 if ( $value !== $this->data[ $prop ] || array_key_exists( $prop, $this->changes ) ) {
883 $this->changes[ $prop ] = $value;
884 }
885 } else {
886 $this->data[ $prop ] = $value;
887 }
888 }
889 }
890
891 /**
892 * Return data changes only.
893 *
894 * @since 3.0.0
895 * @return array
896 */
897 public function get_changes() {
898 return $this->changes;
899 }
900
901 /**
902 * Merge changes with data and clear.
903 *
904 * @since 3.0.0
905 */
906 public function apply_changes() {
907 $this->data = array_replace_recursive( $this->data, $this->changes ); // @codingStandardsIgnoreLine
908 $this->changes = array();
909 }
910
911 /**
912 * Prefix for action and filter hooks on data.
913 *
914 * @since 3.0.0
915 * @return string
916 */
917 protected function get_hook_prefix() {
918 return 'woocommerce_' . $this->object_type . '_get_';
919 }
920
921 /**
922 * Gets a prop for a getter method.
923 *
924 * Gets the value from either current pending changes, or the data itself.
925 * Context controls what happens to the value before it's returned.
926 *
927 * @since 3.0.0
928 * @param string $prop Name of prop to get.
929 * @param string $context What the value is for. Valid values are view and edit.
930 * @return mixed
931 */
932 protected function get_prop( $prop, $context = 'view' ) {
933 $value = null;
934
935 if ( array_key_exists( $prop, $this->data ) ) {
936 $value = array_key_exists( $prop, $this->changes ) ? $this->changes[ $prop ] : $this->data[ $prop ];
937
938 if ( 'view' === $context ) {
939 $value = apply_filters( $this->get_hook_prefix() . $prop, $value, $this );
940 }
941 }
942
943 return $value;
944 }
945
946 /**
947 * Sets a date prop whilst handling formatting and datetime objects.
948 *
949 * @since 3.0.0
950 * @param string $prop Name of prop to set.
951 * @param string|integer $value Value of the prop.
952 */
953 protected function set_date_prop( $prop, $value ) {
954 try {
955 if ( empty( $value ) || '0000-00-00 00:00:00' === $value ) {
956 $this->set_prop( $prop, null );
957 return;
958 }
959
960 if ( is_a( $value, 'WC_DateTime' ) ) {
961 $datetime = $value;
962 } elseif ( is_numeric( $value ) ) {
963 // Timestamps are handled as UTC timestamps in all cases.
964 $datetime = new WC_DateTime( "@{$value}", new DateTimeZone( 'UTC' ) );
965 } elseif ( is_string( $value ) ) {
966 // Strings are defined in local WP timezone. Convert to UTC.
967 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 ) ) {
968 $offset = ! empty( $date_bits[7] ) ? iso8601_timezone_to_offset( $date_bits[7] ) : wc_timezone_offset();
969 $timestamp = gmmktime( $date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1] ) - $offset;
970 } else {
971 $timestamp = wc_string_to_timestamp( get_gmt_from_date( gmdate( 'Y-m-d H:i:s', wc_string_to_timestamp( $value ) ) ) );
972 }
973 $datetime = new WC_DateTime( "@{$timestamp}", new DateTimeZone( 'UTC' ) );
974 } else {
975 // If we get here, the value is not a valid date type.
976 $this->error( 'invalid_date', __( 'Invalid date provided.', 'woocommerce' ) );
977 }
978
979 // Set local timezone or offset.
980 if ( get_option( 'timezone_string' ) ) {
981 $datetime->setTimezone( new DateTimeZone( wc_timezone_string() ) );
982 } else {
983 $datetime->set_utc_offset( wc_timezone_offset() );
984 }
985
986 $this->set_prop( $prop, $datetime );
987 } catch ( Exception $e ) {} // @codingStandardsIgnoreLine.
988 }
989
990 /**
991 * When invalid data is found, throw an exception unless reading from the DB.
992 *
993 * @throws WC_Data_Exception Data Exception.
994 * @since 3.0.0
995 * @param string $code Error code.
996 * @param string $message Error message.
997 * @param int $http_status_code HTTP status code.
998 * @param array $data Extra error data.
999 */
1000 protected function error( $code, $message, $http_status_code = 400, $data = array() ) {
1001 throw new WC_Data_Exception( $code, $message, $http_status_code, $data );
1002 }
1003 }
1004