WC_Data

Abstract WC Data Class.

Defined (1)

The class is defined in the following location(s).

/includes/abstracts/abstract-wc-data.php  
  1. abstract class WC_Data { 
  2.  
  3. /** 
  4. * ID for this object. 
  5. * @since 3.0.0 
  6. * @var int 
  7. */ 
  8. protected $id = 0; 
  9.  
  10. /** 
  11. * Core data for this object. Name value pairs (name + default value). 
  12. * @since 3.0.0 
  13. * @var array 
  14. */ 
  15. protected $data = array(); 
  16.  
  17. /** 
  18. * Core data changes for this object. 
  19. * @since 3.0.0 
  20. * @var array 
  21. */ 
  22. protected $changes = array(); 
  23.  
  24. /** 
  25. * This is false until the object is read from the DB. 
  26. * @since 3.0.0 
  27. * @var bool 
  28. */ 
  29. protected $object_read = false; 
  30.  
  31. /** 
  32. * This is the name of this object type. 
  33. * @since 3.0.0 
  34. * @var string 
  35. */ 
  36. protected $object_type = 'data'; 
  37.  
  38. /** 
  39. * Extra data for this object. Name value pairs (name + default value). 
  40. * Used as a standard way for sub classes (like product types) to add 
  41. * additional information to an inherited class. 
  42. * @since 3.0.0 
  43. * @var array 
  44. */ 
  45. protected $extra_data = array(); 
  46.  
  47. /** 
  48. * Set to _data on construct so we can track and reset data if needed. 
  49. * @since 3.0.0 
  50. * @var array 
  51. */ 
  52. protected $default_data = array(); 
  53.  
  54. /** 
  55. * Contains a reference to the data store for this class. 
  56. * @since 3.0.0 
  57. * @var object 
  58. */ 
  59. protected $data_store; 
  60.  
  61. /** 
  62. * Stores meta in cache for future reads. 
  63. * A group must be set to to enable caching. 
  64. * @since 3.0.0 
  65. * @var string 
  66. */ 
  67. protected $cache_group = ''; 
  68.  
  69. /** 
  70. * Stores additional meta data. 
  71. * @since 3.0.0 
  72. * @var array 
  73. */ 
  74. protected $meta_data = null; 
  75.  
  76. /** 
  77. * Default constructor. 
  78. * @param int|object|array $read ID to load from the DB (optional) or already queried data. 
  79. */ 
  80. public function __construct( $read = 0 ) { 
  81. $this->data = array_merge( $this->data, $this->extra_data ); 
  82. $this->default_data = $this->data; 
  83.  
  84. /** 
  85. * Only store the object ID to avoid serializing the data object instance. 
  86. * @return array 
  87. */ 
  88. public function __sleep() { 
  89. return array( 'id' ); 
  90.  
  91. /** 
  92. * Re-run the constructor with the object ID. 
  93. * If the object no longer exists, remove the ID. 
  94. */ 
  95. public function __wakeup() { 
  96. try { 
  97. $this->__construct( absint( $this->id ) ); 
  98. } catch ( Exception $e ) { 
  99. $this->set_id( 0 ); 
  100. $this->set_object_read( true ); 
  101.  
  102. /** 
  103. * When the object is cloned, make sure meta is duplicated correctly. 
  104. * @since 3.0.2 
  105. */ 
  106. public function __clone() { 
  107. $this->maybe_read_meta_data(); 
  108. if ( ! empty( $this->meta_data ) ) { 
  109. foreach ( $this->meta_data as $array_key => $meta ) { 
  110. $this->meta_data[ $array_key ] = clone $meta; 
  111. if ( ! empty( $meta->id ) ) { 
  112. unset( $this->meta_data[ $array_key ]->id ); 
  113.  
  114. /** 
  115. * Get the data store. 
  116. * @since 3.0.0 
  117. * @return object 
  118. */ 
  119. public function get_data_store() { 
  120. return $this->data_store; 
  121.  
  122. /** 
  123. * Returns the unique ID for this object. 
  124. * @since 2.6.0 
  125. * @return int 
  126. */ 
  127. public function get_id() { 
  128. return $this->id; 
  129.  
  130. /** 
  131. * Delete an object, set the ID to 0, and return result. 
  132. * @since 2.6.0 
  133. * @param bool $force_delete 
  134. * @return bool result 
  135. */ 
  136. public function delete( $force_delete = false ) { 
  137. if ( $this->data_store ) { 
  138. $this->data_store->delete( $this, array( 'force_delete' => $force_delete ) ); 
  139. $this->set_id( 0 ); 
  140. return true; 
  141. return false; 
  142.  
  143. /** 
  144. * Save should create or update based on object existence. 
  145. * @since 2.6.0 
  146. * @return int 
  147. */ 
  148. public function save() { 
  149. if ( $this->data_store ) { 
  150. // Trigger action before saving to the DB. Allows you to adjust object props before save. 
  151. do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store ); 
  152.  
  153. if ( $this->get_id() ) { 
  154. $this->data_store->update( $this ); 
  155. } else { 
  156. $this->data_store->create( $this ); 
  157. return $this->get_id(); 
  158.  
  159. /** 
  160. * Change data to JSON format. 
  161. * @since 2.6.0 
  162. * @return string Data in JSON format. 
  163. */ 
  164. public function __toString() { 
  165. return json_encode( $this->get_data() ); 
  166.  
  167. /** 
  168. * Returns all data for this object. 
  169. * @since 2.6.0 
  170. * @return array 
  171. */ 
  172. public function get_data() { 
  173. return array_merge( array( 'id' => $this->get_id() ), $this->data, array( 'meta_data' => $this->get_meta_data() ) ); 
  174.  
  175. /** 
  176. * Returns array of expected data keys for this object. 
  177. * @since 3.0.0 
  178. * @return array 
  179. */ 
  180. public function get_data_keys() { 
  181. return array_keys( $this->data ); 
  182.  
  183. /** 
  184. * Returns all "extra" data keys for an object (for sub objects like product types). 
  185. * @since 3.0.0 
  186. * @return array 
  187. */ 
  188. public function get_extra_data_keys() { 
  189. return array_keys( $this->extra_data ); 
  190.  
  191. /** 
  192. * Filter null meta values from array. 
  193. * @since 3.0.0 
  194. * @return bool 
  195. */ 
  196. protected function filter_null_meta( $meta ) { 
  197. return ! is_null( $meta->value ); 
  198.  
  199. /** 
  200. * Get All Meta Data. 
  201. * @since 2.6.0 
  202. * @return array 
  203. */ 
  204. public function get_meta_data() { 
  205. $this->maybe_read_meta_data(); 
  206. return array_filter( $this->meta_data, array( $this, 'filter_null_meta' ) ); 
  207.  
  208. /** 
  209. * Get Meta Data by Key. 
  210. * @since 2.6.0 
  211. * @param string $key 
  212. * @param bool $single return first found meta with key, or all with $key 
  213. * @param string $context What the value is for. Valid values are view and edit. 
  214. * @return mixed 
  215. */ 
  216. public function get_meta( $key = '', $single = true, $context = 'view' ) { 
  217. $this->maybe_read_meta_data(); 
  218. $meta_data = $this->get_meta_data(); 
  219. $array_keys = array_keys( wp_list_pluck( $meta_data, 'key' ), $key ); 
  220. $value = $single ? '' : array(); 
  221.  
  222. if ( ! empty( $array_keys ) ) { 
  223. // 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()) 
  224. if ( $single ) { 
  225. $value = $meta_data[ current( $array_keys ) ]->value; 
  226. } else { 
  227. $value = array_intersect_key( $meta_data, array_flip( $array_keys ) ); 
  228.  
  229. if ( 'view' === $context ) { 
  230. $value = apply_filters( $this->get_hook_prefix() . $key, $value, $this ); 
  231.  
  232. return $value; 
  233.  
  234. /** 
  235. * See if meta data exists, since get_meta always returns a '' or array(). 
  236. * @since 3.0.0 
  237. * @param string $key 
  238. * @return boolean 
  239. */ 
  240. public function meta_exists( $key = '' ) { 
  241. $this->maybe_read_meta_data(); 
  242. $array_keys = wp_list_pluck( $this->get_meta_data(), 'key' ); 
  243. return in_array( $key, $array_keys ); 
  244.  
  245. /** 
  246. * Set all meta data from array. 
  247. * @since 2.6.0 
  248. * @param array $data Key/Value pairs 
  249. */ 
  250. public function set_meta_data( $data ) { 
  251. if ( ! empty( $data ) && is_array( $data ) ) { 
  252. $this->maybe_read_meta_data(); 
  253. foreach ( $data as $meta ) { 
  254. $meta = (array) $meta; 
  255. if ( isset( $meta['key'], $meta['value'], $meta['id'] ) ) { 
  256. $this->meta_data[] = (object) array( 
  257. 'id' => $meta['id'],  
  258. 'key' => $meta['key'],  
  259. 'value' => $meta['value'],  
  260. ); 
  261.  
  262. /** 
  263. * Add meta data. 
  264. * @since 2.6.0 
  265. * @param string $key Meta key 
  266. * @param string $value Meta value 
  267. * @param bool $unique Should this be a unique key? 
  268. */ 
  269. public function add_meta_data( $key, $value, $unique = false ) { 
  270. $this->maybe_read_meta_data(); 
  271. if ( $unique ) { 
  272. $this->delete_meta_data( $key ); 
  273. $this->meta_data[] = (object) array( 
  274. 'key' => $key,  
  275. 'value' => $value,  
  276. ); 
  277.  
  278. /** 
  279. * Update meta data by key or ID, if provided. 
  280. * @since 2.6.0 
  281. * @param string $key 
  282. * @param string $value 
  283. * @param int $meta_id 
  284. */ 
  285. public function update_meta_data( $key, $value, $meta_id = '' ) { 
  286. $this->maybe_read_meta_data(); 
  287. if ( $array_key = $meta_id ? array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id ) : '' ) { 
  288. $this->meta_data[ current( $array_key ) ] = (object) array( 
  289. 'id' => $meta_id,  
  290. 'key' => $key,  
  291. 'value' => $value,  
  292. ); 
  293. } else { 
  294. $this->add_meta_data( $key, $value, true ); 
  295.  
  296. /** 
  297. * Delete meta data. 
  298. * @since 2.6.0 
  299. * @param array $key Meta key 
  300. */ 
  301. public function delete_meta_data( $key ) { 
  302. $this->maybe_read_meta_data(); 
  303. if ( $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key ) ) { 
  304. foreach ( $array_keys as $array_key ) { 
  305. $this->meta_data[ $array_key ]->value = null; 
  306.  
  307. /** 
  308. * Delete meta data. 
  309. * @since 2.6.0 
  310. * @param int $mid Meta ID 
  311. */ 
  312. public function delete_meta_data_by_mid( $mid ) { 
  313. $this->maybe_read_meta_data(); 
  314. if ( $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), $mid ) ) { 
  315. foreach ( $array_keys as $array_key ) { 
  316. $this->meta_data[ $array_key ]->value = null; 
  317.  
  318. /** 
  319. * Read meta data if null. 
  320. * @since 3.0.0 
  321. */ 
  322. protected function maybe_read_meta_data() { 
  323. if ( is_null( $this->meta_data ) ) { 
  324. $this->read_meta_data(); 
  325.  
  326. /** 
  327. * Read Meta Data from the database. Ignore any internal properties. 
  328. * Uses it's own caches because get_metadata does not provide meta_ids. 
  329. * @since 2.6.0 
  330. * @param bool $force_read True to force a new DB read (and update cache). 
  331. */ 
  332. public function read_meta_data( $force_read = false ) { 
  333. $this->meta_data = array(); 
  334. $cache_loaded = false; 
  335.  
  336. if ( ! $this->get_id() ) { 
  337. return; 
  338.  
  339. if ( ! $this->data_store ) { 
  340. return; 
  341.  
  342. if ( ! empty( $this->cache_group ) ) { 
  343. $cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . 'object_meta_' . $this->get_id(); 
  344.  
  345. if ( ! $force_read ) { 
  346. if ( ! empty( $this->cache_group ) ) { 
  347. $cached_meta = wp_cache_get( $cache_key, $this->cache_group ); 
  348. $cache_loaded = ! empty( $cached_meta ); 
  349.  
  350. $raw_meta_data = $cache_loaded ? $cached_meta : $this->data_store->read_meta( $this ); 
  351. if ( $raw_meta_data ) { 
  352. foreach ( $raw_meta_data as $meta ) { 
  353. $this->meta_data[] = (object) array( 
  354. 'id' => (int) $meta->meta_id,  
  355. 'key' => $meta->meta_key,  
  356. 'value' => maybe_unserialize( $meta->meta_value ),  
  357. ); 
  358.  
  359. if ( ! $cache_loaded && ! empty( $this->cache_group ) ) { 
  360. wp_cache_set( $cache_key, $raw_meta_data, $this->cache_group ); 
  361.  
  362. /** 
  363. * Update Meta Data in the database. 
  364. * @since 2.6.0 
  365. */ 
  366. public function save_meta_data() { 
  367. if ( ! $this->data_store || is_null( $this->meta_data ) ) { 
  368. return; 
  369. foreach ( $this->meta_data as $array_key => $meta ) { 
  370. if ( is_null( $meta->value ) ) { 
  371. if ( ! empty( $meta->id ) ) { 
  372. $this->data_store->delete_meta( $this, $meta ); 
  373. unset( $this->meta_data[ $array_key ] ); 
  374. } elseif ( empty( $meta->id ) ) { 
  375. $new_meta_id = $this->data_store->add_meta( $this, $meta ); 
  376. $this->meta_data[ $array_key ]->id = $new_meta_id; 
  377. } else { 
  378. $this->data_store->update_meta( $this, $meta ); 
  379.  
  380. if ( ! empty( $this->cache_group ) ) { 
  381. WC_Cache_Helper::incr_cache_prefix( $this->cache_group ); 
  382.  
  383. /** 
  384. * Set ID. 
  385. * @since 3.0.0 
  386. * @param int $id 
  387. */ 
  388. public function set_id( $id ) { 
  389. $this->id = absint( $id ); 
  390.  
  391. /** 
  392. * Set all props to default values. 
  393. * @since 3.0.0 
  394. */ 
  395. public function set_defaults() { 
  396. $this->data = $this->default_data; 
  397. $this->changes = array(); 
  398. $this->set_object_read( false ); 
  399.  
  400. /** 
  401. * Set object read property. 
  402. * @since 3.0.0 
  403. * @param boolean $read 
  404. */ 
  405. public function set_object_read( $read = true ) { 
  406. $this->object_read = (bool) $read; 
  407.  
  408. /** 
  409. * Get object read property. 
  410. * @since 3.0.0 
  411. * @return boolean 
  412. */ 
  413. public function get_object_read() { 
  414. return (bool) $this->object_read; 
  415.  
  416. /** 
  417. * Set a collection of props in one go, collect any errors, and return the result. 
  418. * Only sets using public methods. 
  419. * @since 3.0.0 
  420. * @param array $props Key value pairs to set. Key is the prop and should map to a setter function name. 
  421. * @return WP_Error|bool 
  422. */ 
  423. public function set_props( $props, $context = 'set' ) { 
  424. $errors = new WP_Error(); 
  425.  
  426. foreach ( $props as $prop => $value ) { 
  427. try { 
  428. if ( 'meta_data' === $prop ) { 
  429. continue; 
  430. $setter = "set_$prop"; 
  431. if ( ! is_null( $value ) && is_callable( array( $this, $setter ) ) ) { 
  432. $reflection = new ReflectionMethod( $this, $setter ); 
  433.  
  434. if ( $reflection->isPublic() ) { 
  435. $this->{$setter}( $value ); 
  436. } catch ( WC_Data_Exception $e ) { 
  437. $errors->add( $e->getErrorCode(), $e->getMessage() ); 
  438.  
  439. return sizeof( $errors->get_error_codes() ) ? $errors : true; 
  440.  
  441. /** 
  442. * Sets a prop for a setter method. 
  443. * This stores changes in a special array so we can track what needs saving 
  444. * the the DB later. 
  445. * @since 3.0.0 
  446. * @param string $prop Name of prop to set. 
  447. * @param mixed $value Value of the prop. 
  448. */ 
  449. protected function set_prop( $prop, $value ) { 
  450. if ( array_key_exists( $prop, $this->data ) ) { 
  451. if ( true === $this->object_read ) { 
  452. if ( $value !== $this->data[ $prop ] || array_key_exists( $prop, $this->changes ) ) { 
  453. $this->changes[ $prop ] = $value; 
  454. } else { 
  455. $this->data[ $prop ] = $value; 
  456.  
  457. /** 
  458. * Return data changes only. 
  459. * @since 3.0.0 
  460. * @return array 
  461. */ 
  462. public function get_changes() { 
  463. return $this->changes; 
  464.  
  465. /** 
  466. * Merge changes with data and clear. 
  467. * @since 3.0.0 
  468. */ 
  469. public function apply_changes() { 
  470. $this->data = array_replace_recursive( $this->data, $this->changes ); 
  471. $this->changes = array(); 
  472.  
  473. /** 
  474. * Prefix for action and filter hooks on data. 
  475. * @since 3.0.0 
  476. * @return string 
  477. */ 
  478. protected function get_hook_prefix() { 
  479. return 'woocommerce_' . $this->object_type . '_get_'; 
  480.  
  481. /** 
  482. * Gets a prop for a getter method. 
  483. * Gets the value from either current pending changes, or the data itself. 
  484. * Context controls what happens to the value before it's returned. 
  485. * @since 3.0.0 
  486. * @param string $prop Name of prop to get. 
  487. * @param string $context What the value is for. Valid values are view and edit. 
  488. * @return mixed 
  489. */ 
  490. protected function get_prop( $prop, $context = 'view' ) { 
  491. $value = null; 
  492.  
  493. if ( array_key_exists( $prop, $this->data ) ) { 
  494. $value = array_key_exists( $prop, $this->changes ) ? $this->changes[ $prop ] : $this->data[ $prop ]; 
  495.  
  496. if ( 'view' === $context ) { 
  497. $value = apply_filters( $this->get_hook_prefix() . $prop, $value, $this ); 
  498.  
  499. return $value; 
  500.  
  501. /** 
  502. * Sets a date prop whilst handling formatting and datatime objects. 
  503. * @since 3.0.0 
  504. * @param string $prop Name of prop to set. 
  505. * @param string|integer $value Value of the prop. 
  506. */ 
  507. protected function set_date_prop( $prop, $value ) { 
  508. try { 
  509. if ( empty( $value ) ) { 
  510. $this->set_prop( $prop, null ); 
  511. return; 
  512.  
  513. if ( is_a( $value, 'WC_DateTime' ) ) { 
  514. $datetime = $value; 
  515. } elseif ( is_numeric( $value ) ) { 
  516. // Timestamps are handled as UTC timestamps in all cases. 
  517. $datetime = new WC_DateTime( "@{$value}", new DateTimeZone( 'UTC' ) ); 
  518. } else { 
  519. // Strings are defined in local WP timezone. Convert to UTC. 
  520. 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 ) ) { 
  521. $offset = ! empty( $date_bits[7] ) ? iso8601_timezone_to_offset( $date_bits[7] ) : wc_timezone_offset(); 
  522. $timestamp = gmmktime( $date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1] ) - $offset; 
  523. } else { 
  524. $timestamp = wc_string_to_timestamp( get_gmt_from_date( gmdate( 'Y-m-d H:i:s', wc_string_to_timestamp( $value ) ) ) ); 
  525. $datetime = new WC_DateTime( "@{$timestamp}", new DateTimeZone( 'UTC' ) ); 
  526.  
  527. // Set local timezone or offset. 
  528. if ( get_option( 'timezone_string' ) ) { 
  529. $datetime->setTimezone( new DateTimeZone( wc_timezone_string() ) ); 
  530. } else { 
  531. $datetime->set_utc_offset( wc_timezone_offset() ); 
  532.  
  533. $this->set_prop( $prop, $datetime ); 
  534. } catch ( Exception $e ) {} 
  535.  
  536. /** 
  537. * When invalid data is found, throw an exception unless reading from the DB. 
  538. * @throws WC_Data_Exception 
  539. * @since 3.0.0 
  540. * @param string $code Error code. 
  541. * @param string $message Error message. 
  542. * @param int $http_status_code HTTP status code. 
  543. * @param array $data Extra error data. 
  544. */ 
  545. protected function error( $code, $message, $http_status_code = 400, $data = array() ) { 
  546. throw new WC_Data_Exception( $code, $message, $http_status_code, $data );