Nenhuma Descrição

abstract-wc-data.php 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  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. if ( ! defined( 'ABSPATH' ) ) {
  13. exit;
  14. }
  15. /**
  16. * Abstract WC Data Class
  17. *
  18. * Implemented by classes using the same CRUD(s) pattern.
  19. *
  20. * @version 2.6.0
  21. * @package WooCommerce\Abstracts
  22. */
  23. abstract class WC_Data {
  24. /**
  25. * ID for this object.
  26. *
  27. * @since 3.0.0
  28. * @var int
  29. */
  30. protected $id = 0;
  31. /**
  32. * Core data for this object. Name value pairs (name + default value).
  33. *
  34. * @since 3.0.0
  35. * @var array
  36. */
  37. protected $data = array();
  38. /**
  39. * Core data changes for this object.
  40. *
  41. * @since 3.0.0
  42. * @var array
  43. */
  44. protected $changes = array();
  45. /**
  46. * This is false until the object is read from the DB.
  47. *
  48. * @since 3.0.0
  49. * @var bool
  50. */
  51. protected $object_read = false;
  52. /**
  53. * This is the name of this object type.
  54. *
  55. * @since 3.0.0
  56. * @var string
  57. */
  58. protected $object_type = 'data';
  59. /**
  60. * Extra data for this object. Name value pairs (name + default value).
  61. * Used as a standard way for sub classes (like product types) to add
  62. * additional information to an inherited class.
  63. *
  64. * @since 3.0.0
  65. * @var array
  66. */
  67. protected $extra_data = array();
  68. /**
  69. * Set to _data on construct so we can track and reset data if needed.
  70. *
  71. * @since 3.0.0
  72. * @var array
  73. */
  74. protected $default_data = array();
  75. /**
  76. * Contains a reference to the data store for this class.
  77. *
  78. * @since 3.0.0
  79. * @var object
  80. */
  81. protected $data_store;
  82. /**
  83. * Stores meta in cache for future reads.
  84. * A group must be set to to enable caching.
  85. *
  86. * @since 3.0.0
  87. * @var string
  88. */
  89. protected $cache_group = '';
  90. /**
  91. * Stores additional meta data.
  92. *
  93. * @since 3.0.0
  94. * @var array
  95. */
  96. protected $meta_data = null;
  97. /**
  98. * Default constructor.
  99. *
  100. * @param int|object|array $read ID to load from the DB (optional) or already queried data.
  101. */
  102. public function __construct( $read = 0 ) {
  103. $this->data = array_merge( $this->data, $this->extra_data );
  104. $this->default_data = $this->data;
  105. }
  106. /**
  107. * Only store the object ID to avoid serializing the data object instance.
  108. *
  109. * @return array
  110. */
  111. public function __sleep() {
  112. return array( 'id' );
  113. }
  114. /**
  115. * Re-run the constructor with the object ID.
  116. *
  117. * If the object no longer exists, remove the ID.
  118. */
  119. public function __wakeup() {
  120. try {
  121. $this->__construct( absint( $this->id ) );
  122. } catch ( Exception $e ) {
  123. $this->set_id( 0 );
  124. $this->set_object_read( true );
  125. }
  126. }
  127. /**
  128. * When the object is cloned, make sure meta is duplicated correctly.
  129. *
  130. * @since 3.0.2
  131. */
  132. public function __clone() {
  133. $this->maybe_read_meta_data();
  134. if ( ! empty( $this->meta_data ) ) {
  135. foreach ( $this->meta_data as $array_key => $meta ) {
  136. $this->meta_data[ $array_key ] = clone $meta;
  137. if ( ! empty( $meta->id ) ) {
  138. $this->meta_data[ $array_key ]->id = null;
  139. }
  140. }
  141. }
  142. }
  143. /**
  144. * Get the data store.
  145. *
  146. * @since 3.0.0
  147. * @return object
  148. */
  149. public function get_data_store() {
  150. return $this->data_store;
  151. }
  152. /**
  153. * Returns the unique ID for this object.
  154. *
  155. * @since 2.6.0
  156. * @return int
  157. */
  158. public function get_id() {
  159. return $this->id;
  160. }
  161. /**
  162. * Delete an object, set the ID to 0, and return result.
  163. *
  164. * @since 2.6.0
  165. * @param bool $force_delete Should the date be deleted permanently.
  166. * @return bool result
  167. */
  168. public function delete( $force_delete = false ) {
  169. if ( $this->data_store ) {
  170. $this->data_store->delete( $this, array( 'force_delete' => $force_delete ) );
  171. $this->set_id( 0 );
  172. return true;
  173. }
  174. return false;
  175. }
  176. /**
  177. * Save should create or update based on object existence.
  178. *
  179. * @since 2.6.0
  180. * @return int
  181. */
  182. public function save() {
  183. if ( ! $this->data_store ) {
  184. return $this->get_id();
  185. }
  186. /**
  187. * Trigger action before saving to the DB. Allows you to adjust object props before save.
  188. *
  189. * @param WC_Data $this The object being saved.
  190. * @param WC_Data_Store_WP $data_store THe data store persisting the data.
  191. */
  192. do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
  193. if ( $this->get_id() ) {
  194. $this->data_store->update( $this );
  195. } else {
  196. $this->data_store->create( $this );
  197. }
  198. /**
  199. * Trigger action after saving to the DB.
  200. *
  201. * @param WC_Data $this The object being saved.
  202. * @param WC_Data_Store_WP $data_store THe data store persisting the data.
  203. */
  204. do_action( 'woocommerce_after_' . $this->object_type . '_object_save', $this, $this->data_store );
  205. return $this->get_id();
  206. }
  207. /**
  208. * Change data to JSON format.
  209. *
  210. * @since 2.6.0
  211. * @return string Data in JSON format.
  212. */
  213. public function __toString() {
  214. return wp_json_encode( $this->get_data() );
  215. }
  216. /**
  217. * Returns all data for this object.
  218. *
  219. * @since 2.6.0
  220. * @return array
  221. */
  222. public function get_data() {
  223. return array_merge( array( 'id' => $this->get_id() ), $this->data, array( 'meta_data' => $this->get_meta_data() ) );
  224. }
  225. /**
  226. * Returns array of expected data keys for this object.
  227. *
  228. * @since 3.0.0
  229. * @return array
  230. */
  231. public function get_data_keys() {
  232. return array_keys( $this->data );
  233. }
  234. /**
  235. * Returns all "extra" data keys for an object (for sub objects like product types).
  236. *
  237. * @since 3.0.0
  238. * @return array
  239. */
  240. public function get_extra_data_keys() {
  241. return array_keys( $this->extra_data );
  242. }
  243. /**
  244. * Filter null meta values from array.
  245. *
  246. * @since 3.0.0
  247. * @param mixed $meta Meta value to check.
  248. * @return bool
  249. */
  250. protected function filter_null_meta( $meta ) {
  251. return ! is_null( $meta->value );
  252. }
  253. /**
  254. * Get All Meta Data.
  255. *
  256. * @since 2.6.0
  257. * @return array of objects.
  258. */
  259. public function get_meta_data() {
  260. $this->maybe_read_meta_data();
  261. return array_values( array_filter( $this->meta_data, array( $this, 'filter_null_meta' ) ) );
  262. }
  263. /**
  264. * Check if the key is an internal one.
  265. *
  266. * @since 3.2.0
  267. * @param string $key Key to check.
  268. * @return bool true if it's an internal key, false otherwise
  269. */
  270. protected function is_internal_meta_key( $key ) {
  271. $internal_meta_key = ! empty( $key ) && $this->data_store && in_array( $key, $this->data_store->get_internal_meta_keys(), true );
  272. if ( ! $internal_meta_key ) {
  273. return false;
  274. }
  275. $has_setter_or_getter = is_callable( array( $this, 'set_' . $key ) ) || is_callable( array( $this, 'get_' . $key ) );
  276. if ( ! $has_setter_or_getter ) {
  277. return false;
  278. }
  279. /* translators: %s: $key Key to check */
  280. 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' );
  281. return true;
  282. }
  283. /**
  284. * Get Meta Data by Key.
  285. *
  286. * @since 2.6.0
  287. * @param string $key Meta Key.
  288. * @param bool $single return first found meta with key, or all with $key.
  289. * @param string $context What the value is for. Valid values are view and edit.
  290. * @return mixed
  291. */
  292. public function get_meta( $key = '', $single = true, $context = 'view' ) {
  293. if ( $this->is_internal_meta_key( $key ) ) {
  294. $function = 'get_' . $key;
  295. if ( is_callable( array( $this, $function ) ) ) {
  296. return $this->{$function}();
  297. }
  298. }
  299. $this->maybe_read_meta_data();
  300. $meta_data = $this->get_meta_data();
  301. $array_keys = array_keys( wp_list_pluck( $meta_data, 'key' ), $key, true );
  302. $value = $single ? '' : array();
  303. if ( ! empty( $array_keys ) ) {
  304. // 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()).
  305. if ( $single ) {
  306. $value = $meta_data[ current( $array_keys ) ]->value;
  307. } else {
  308. $value = array_intersect_key( $meta_data, array_flip( $array_keys ) );
  309. }
  310. }
  311. if ( 'view' === $context ) {
  312. $value = apply_filters( $this->get_hook_prefix() . $key, $value, $this );
  313. }
  314. return $value;
  315. }
  316. /**
  317. * See if meta data exists, since get_meta always returns a '' or array().
  318. *
  319. * @since 3.0.0
  320. * @param string $key Meta Key.
  321. * @return boolean
  322. */
  323. public function meta_exists( $key = '' ) {
  324. $this->maybe_read_meta_data();
  325. $array_keys = wp_list_pluck( $this->get_meta_data(), 'key' );
  326. return in_array( $key, $array_keys, true );
  327. }
  328. /**
  329. * Set all meta data from array.
  330. *
  331. * @since 2.6.0
  332. * @param array $data Key/Value pairs.
  333. */
  334. public function set_meta_data( $data ) {
  335. if ( ! empty( $data ) && is_array( $data ) ) {
  336. $this->maybe_read_meta_data();
  337. foreach ( $data as $meta ) {
  338. $meta = (array) $meta;
  339. if ( isset( $meta['key'], $meta['value'], $meta['id'] ) ) {
  340. $this->meta_data[] = new WC_Meta_Data(
  341. array(
  342. 'id' => $meta['id'],
  343. 'key' => $meta['key'],
  344. 'value' => $meta['value'],
  345. )
  346. );
  347. }
  348. }
  349. }
  350. }
  351. /**
  352. * Add meta data.
  353. *
  354. * @since 2.6.0
  355. *
  356. * @param string $key Meta key.
  357. * @param string|array $value Meta value.
  358. * @param bool $unique Should this be a unique key?.
  359. */
  360. public function add_meta_data( $key, $value, $unique = false ) {
  361. if ( $this->is_internal_meta_key( $key ) ) {
  362. $function = 'set_' . $key;
  363. if ( is_callable( array( $this, $function ) ) ) {
  364. return $this->{$function}( $value );
  365. }
  366. }
  367. $this->maybe_read_meta_data();
  368. if ( $unique ) {
  369. $this->delete_meta_data( $key );
  370. }
  371. $this->meta_data[] = new WC_Meta_Data(
  372. array(
  373. 'key' => $key,
  374. 'value' => $value,
  375. )
  376. );
  377. }
  378. /**
  379. * Update meta data by key or ID, if provided.
  380. *
  381. * @since 2.6.0
  382. *
  383. * @param string $key Meta key.
  384. * @param string|array $value Meta value.
  385. * @param int $meta_id Meta ID.
  386. */
  387. public function update_meta_data( $key, $value, $meta_id = 0 ) {
  388. if ( $this->is_internal_meta_key( $key ) ) {
  389. $function = 'set_' . $key;
  390. if ( is_callable( array( $this, $function ) ) ) {
  391. return $this->{$function}( $value );
  392. }
  393. }
  394. $this->maybe_read_meta_data();
  395. $array_key = false;
  396. if ( $meta_id ) {
  397. $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id, true );
  398. $array_key = $array_keys ? current( $array_keys ) : false;
  399. } else {
  400. // Find matches by key.
  401. $matches = array();
  402. foreach ( $this->meta_data as $meta_data_array_key => $meta ) {
  403. if ( $meta->key === $key ) {
  404. $matches[] = $meta_data_array_key;
  405. }
  406. }
  407. if ( ! empty( $matches ) ) {
  408. // Set matches to null so only one key gets the new value.
  409. foreach ( $matches as $meta_data_array_key ) {
  410. $this->meta_data[ $meta_data_array_key ]->value = null;
  411. }
  412. $array_key = current( $matches );
  413. }
  414. }
  415. if ( false !== $array_key ) {
  416. $meta = $this->meta_data[ $array_key ];
  417. $meta->key = $key;
  418. $meta->value = $value;
  419. } else {
  420. $this->add_meta_data( $key, $value, true );
  421. }
  422. }
  423. /**
  424. * Delete meta data.
  425. *
  426. * @since 2.6.0
  427. * @param string $key Meta key.
  428. */
  429. public function delete_meta_data( $key ) {
  430. $this->maybe_read_meta_data();
  431. $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key, true );
  432. if ( $array_keys ) {
  433. foreach ( $array_keys as $array_key ) {
  434. $this->meta_data[ $array_key ]->value = null;
  435. }
  436. }
  437. }
  438. /**
  439. * Delete meta data.
  440. *
  441. * @since 2.6.0
  442. * @param int $mid Meta ID.
  443. */
  444. public function delete_meta_data_by_mid( $mid ) {
  445. $this->maybe_read_meta_data();
  446. $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), (int) $mid, true );
  447. if ( $array_keys ) {
  448. foreach ( $array_keys as $array_key ) {
  449. $this->meta_data[ $array_key ]->value = null;
  450. }
  451. }
  452. }
  453. /**
  454. * Read meta data if null.
  455. *
  456. * @since 3.0.0
  457. */
  458. protected function maybe_read_meta_data() {
  459. if ( is_null( $this->meta_data ) ) {
  460. $this->read_meta_data();
  461. }
  462. }
  463. /**
  464. * 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.
  465. *
  466. * @since 4.7.0
  467. *
  468. * @return string
  469. */
  470. public function get_meta_cache_key() {
  471. if ( ! $this->get_id() ) {
  472. wc_doing_it_wrong( 'get_meta_cache_key', 'ID needs to be set before fetching a cache key.', '4.7.0' );
  473. return false;
  474. }
  475. return self::generate_meta_cache_key( $this->get_id(), $this->cache_group );
  476. }
  477. /**
  478. * Generate cache key from id and group.
  479. *
  480. * @since 4.7.0
  481. *
  482. * @param int|string $id Object ID.
  483. * @param string $cache_group Group name use to store cache. Whole group cache can be invalidated in one go.
  484. *
  485. * @return string Meta cache key.
  486. */
  487. public static function generate_meta_cache_key( $id, $cache_group ) {
  488. return WC_Cache_Helper::get_cache_prefix( $cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $id ) . 'object_meta_' . $id;
  489. }
  490. /**
  491. * Prime caches for raw meta data. This includes meta_id column as well, which is not included by default in WP meta data.
  492. *
  493. * @since 4.7.0
  494. *
  495. * @param array $raw_meta_data_collection Array of objects of { object_id => array( meta_row_1, meta_row_2, ... }.
  496. * @param string $cache_group Name of cache group.
  497. */
  498. public static function prime_raw_meta_data_cache( $raw_meta_data_collection, $cache_group ) {
  499. foreach ( $raw_meta_data_collection as $object_id => $raw_meta_data_array ) {
  500. $cache_key = self::generate_meta_cache_key( $object_id, $cache_group );
  501. wp_cache_set( $cache_key, $raw_meta_data_array, $cache_group );
  502. }
  503. }
  504. /**
  505. * Read Meta Data from the database. Ignore any internal properties.
  506. * Uses it's own caches because get_metadata does not provide meta_ids.
  507. *
  508. * @since 2.6.0
  509. * @param bool $force_read True to force a new DB read (and update cache).
  510. */
  511. public function read_meta_data( $force_read = false ) {
  512. $this->meta_data = array();
  513. $cache_loaded = false;
  514. if ( ! $this->get_id() ) {
  515. return;
  516. }
  517. if ( ! $this->data_store ) {
  518. return;
  519. }
  520. if ( ! empty( $this->cache_group ) ) {
  521. // Prefix by group allows invalidation by group until https://core.trac.wordpress.org/ticket/4476 is implemented.
  522. $cache_key = $this->get_meta_cache_key();
  523. }
  524. if ( ! $force_read ) {
  525. if ( ! empty( $this->cache_group ) ) {
  526. $cached_meta = wp_cache_get( $cache_key, $this->cache_group );
  527. $cache_loaded = ! empty( $cached_meta );
  528. }
  529. }
  530. // We filter the raw meta data again when loading from cache, in case we cached in an earlier version where filter conditions were different.
  531. $raw_meta_data = $cache_loaded ? $this->data_store->filter_raw_meta_data( $this, $cached_meta ) : $this->data_store->read_meta( $this );
  532. if ( $raw_meta_data ) {
  533. foreach ( $raw_meta_data as $meta ) {
  534. $this->meta_data[] = new WC_Meta_Data(
  535. array(
  536. 'id' => (int) $meta->meta_id,
  537. 'key' => $meta->meta_key,
  538. 'value' => maybe_unserialize( $meta->meta_value ),
  539. )
  540. );
  541. }
  542. if ( ! $cache_loaded && ! empty( $this->cache_group ) ) {
  543. wp_cache_set( $cache_key, $raw_meta_data, $this->cache_group );
  544. }
  545. }
  546. }
  547. /**
  548. * Update Meta Data in the database.
  549. *
  550. * @since 2.6.0
  551. */
  552. public function save_meta_data() {
  553. if ( ! $this->data_store || is_null( $this->meta_data ) ) {
  554. return;
  555. }
  556. foreach ( $this->meta_data as $array_key => $meta ) {
  557. if ( is_null( $meta->value ) ) {
  558. if ( ! empty( $meta->id ) ) {
  559. $this->data_store->delete_meta( $this, $meta );
  560. unset( $this->meta_data[ $array_key ] );
  561. }
  562. } elseif ( empty( $meta->id ) ) {
  563. $meta->id = $this->data_store->add_meta( $this, $meta );
  564. $meta->apply_changes();
  565. } else {
  566. if ( $meta->get_changes() ) {
  567. $this->data_store->update_meta( $this, $meta );
  568. $meta->apply_changes();
  569. }
  570. }
  571. }
  572. if ( ! empty( $this->cache_group ) ) {
  573. $cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $this->get_id() ) . 'object_meta_' . $this->get_id();
  574. wp_cache_delete( $cache_key, $this->cache_group );
  575. }
  576. }
  577. /**
  578. * Set ID.
  579. *
  580. * @since 3.0.0
  581. * @param int $id ID.
  582. */
  583. public function set_id( $id ) {
  584. $this->id = absint( $id );
  585. }
  586. /**
  587. * Set all props to default values.
  588. *
  589. * @since 3.0.0
  590. */
  591. public function set_defaults() {
  592. $this->data = $this->default_data;
  593. $this->changes = array();
  594. $this->set_object_read( false );
  595. }
  596. /**
  597. * Set object read property.
  598. *
  599. * @since 3.0.0
  600. * @param boolean $read Should read?.
  601. */
  602. public function set_object_read( $read = true ) {
  603. $this->object_read = (bool) $read;
  604. }
  605. /**
  606. * Get object read property.
  607. *
  608. * @since 3.0.0
  609. * @return boolean
  610. */
  611. public function get_object_read() {
  612. return (bool) $this->object_read;
  613. }
  614. /**
  615. * Set a collection of props in one go, collect any errors, and return the result.
  616. * Only sets using public methods.
  617. *
  618. * @since 3.0.0
  619. *
  620. * @param array $props Key value pairs to set. Key is the prop and should map to a setter function name.
  621. * @param string $context In what context to run this.
  622. *
  623. * @return bool|WP_Error
  624. */
  625. public function set_props( $props, $context = 'set' ) {
  626. $errors = false;
  627. foreach ( $props as $prop => $value ) {
  628. try {
  629. /**
  630. * Checks if the prop being set is allowed, and the value is not null.
  631. */
  632. if ( is_null( $value ) || in_array( $prop, array( 'prop', 'date_prop', 'meta_data' ), true ) ) {
  633. continue;
  634. }
  635. $setter = "set_$prop";
  636. if ( is_callable( array( $this, $setter ) ) ) {
  637. $this->{$setter}( $value );
  638. }
  639. } catch ( WC_Data_Exception $e ) {
  640. if ( ! $errors ) {
  641. $errors = new WP_Error();
  642. }
  643. $errors->add( $e->getErrorCode(), $e->getMessage() );
  644. }
  645. }
  646. return $errors && count( $errors->get_error_codes() ) ? $errors : true;
  647. }
  648. /**
  649. * Sets a prop for a setter method.
  650. *
  651. * This stores changes in a special array so we can track what needs saving
  652. * the the DB later.
  653. *
  654. * @since 3.0.0
  655. * @param string $prop Name of prop to set.
  656. * @param mixed $value Value of the prop.
  657. */
  658. protected function set_prop( $prop, $value ) {
  659. if ( array_key_exists( $prop, $this->data ) ) {
  660. if ( true === $this->object_read ) {
  661. if ( $value !== $this->data[ $prop ] || array_key_exists( $prop, $this->changes ) ) {
  662. $this->changes[ $prop ] = $value;
  663. }
  664. } else {
  665. $this->data[ $prop ] = $value;
  666. }
  667. }
  668. }
  669. /**
  670. * Return data changes only.
  671. *
  672. * @since 3.0.0
  673. * @return array
  674. */
  675. public function get_changes() {
  676. return $this->changes;
  677. }
  678. /**
  679. * Merge changes with data and clear.
  680. *
  681. * @since 3.0.0
  682. */
  683. public function apply_changes() {
  684. $this->data = array_replace_recursive( $this->data, $this->changes ); // @codingStandardsIgnoreLine
  685. $this->changes = array();
  686. }
  687. /**
  688. * Prefix for action and filter hooks on data.
  689. *
  690. * @since 3.0.0
  691. * @return string
  692. */
  693. protected function get_hook_prefix() {
  694. return 'woocommerce_' . $this->object_type . '_get_';
  695. }
  696. /**
  697. * Gets a prop for a getter method.
  698. *
  699. * Gets the value from either current pending changes, or the data itself.
  700. * Context controls what happens to the value before it's returned.
  701. *
  702. * @since 3.0.0
  703. * @param string $prop Name of prop to get.
  704. * @param string $context What the value is for. Valid values are view and edit.
  705. * @return mixed
  706. */
  707. protected function get_prop( $prop, $context = 'view' ) {
  708. $value = null;
  709. if ( array_key_exists( $prop, $this->data ) ) {
  710. $value = array_key_exists( $prop, $this->changes ) ? $this->changes[ $prop ] : $this->data[ $prop ];
  711. if ( 'view' === $context ) {
  712. $value = apply_filters( $this->get_hook_prefix() . $prop, $value, $this );
  713. }
  714. }
  715. return $value;
  716. }
  717. /**
  718. * Sets a date prop whilst handling formatting and datetime objects.
  719. *
  720. * @since 3.0.0
  721. * @param string $prop Name of prop to set.
  722. * @param string|integer $value Value of the prop.
  723. */
  724. protected function set_date_prop( $prop, $value ) {
  725. try {
  726. if ( empty( $value ) ) {
  727. $this->set_prop( $prop, null );
  728. return;
  729. }
  730. if ( is_a( $value, 'WC_DateTime' ) ) {
  731. $datetime = $value;
  732. } elseif ( is_numeric( $value ) ) {
  733. // Timestamps are handled as UTC timestamps in all cases.
  734. $datetime = new WC_DateTime( "@{$value}", new DateTimeZone( 'UTC' ) );
  735. } else {
  736. // Strings are defined in local WP timezone. Convert to UTC.
  737. 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 ) ) {
  738. $offset = ! empty( $date_bits[7] ) ? iso8601_timezone_to_offset( $date_bits[7] ) : wc_timezone_offset();
  739. $timestamp = gmmktime( $date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1] ) - $offset;
  740. } else {
  741. $timestamp = wc_string_to_timestamp( get_gmt_from_date( gmdate( 'Y-m-d H:i:s', wc_string_to_timestamp( $value ) ) ) );
  742. }
  743. $datetime = new WC_DateTime( "@{$timestamp}", new DateTimeZone( 'UTC' ) );
  744. }
  745. // Set local timezone or offset.
  746. if ( get_option( 'timezone_string' ) ) {
  747. $datetime->setTimezone( new DateTimeZone( wc_timezone_string() ) );
  748. } else {
  749. $datetime->set_utc_offset( wc_timezone_offset() );
  750. }
  751. $this->set_prop( $prop, $datetime );
  752. } catch ( Exception $e ) {} // @codingStandardsIgnoreLine.
  753. }
  754. /**
  755. * When invalid data is found, throw an exception unless reading from the DB.
  756. *
  757. * @throws WC_Data_Exception Data Exception.
  758. * @since 3.0.0
  759. * @param string $code Error code.
  760. * @param string $message Error message.
  761. * @param int $http_status_code HTTP status code.
  762. * @param array $data Extra error data.
  763. */
  764. protected function error( $code, $message, $http_status_code = 400, $data = array() ) {
  765. throw new WC_Data_Exception( $code, $message, $http_status_code, $data );
  766. }
  767. }