class-wc-payment-token-data-store.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. <?php
  2. /**
  3. * Class WC_Payment_Token_Data_Store file.
  4. *
  5. * @package WooCommerce\DataStores
  6. */
  7. if ( ! defined( 'ABSPATH' ) ) {
  8. exit;
  9. }
  10. /**
  11. * WC Payment Token Data Store: Custom Table.
  12. *
  13. * @version 3.0.0
  14. */
  15. class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment_Token_Data_Store_Interface, WC_Object_Data_Store_Interface {
  16. /**
  17. * Meta type. Payment tokens are a new object type.
  18. *
  19. * @var string
  20. */
  21. protected $meta_type = 'payment_token';
  22. /**
  23. * If we have already saved our extra data, don't do automatic / default handling.
  24. *
  25. * @var bool
  26. */
  27. protected $extra_data_saved = false;
  28. /**
  29. * Create a new payment token in the database.
  30. *
  31. * @since 3.0.0
  32. *
  33. * @param WC_Payment_Token $token Payment token object.
  34. *
  35. * @throws Exception Throw exception if invalid or missing payment token fields.
  36. */
  37. public function create( &$token ) {
  38. if ( false === $token->validate() ) {
  39. throw new Exception( __( 'Invalid or missing payment token fields.', 'woocommerce' ) );
  40. }
  41. global $wpdb;
  42. if ( ! $token->is_default() && $token->get_user_id() > 0 ) {
  43. $default_token = WC_Payment_Tokens::get_customer_default_token( $token->get_user_id() );
  44. if ( is_null( $default_token ) ) {
  45. $token->set_default( true );
  46. }
  47. }
  48. $payment_token_data = array(
  49. 'gateway_id' => $token->get_gateway_id( 'edit' ),
  50. 'token' => $token->get_token( 'edit' ),
  51. 'user_id' => $token->get_user_id( 'edit' ),
  52. 'type' => $token->get_type( 'edit' ),
  53. );
  54. $wpdb->insert( $wpdb->prefix . 'woocommerce_payment_tokens', $payment_token_data );
  55. $token_id = $wpdb->insert_id;
  56. $token->set_id( $token_id );
  57. $this->save_extra_data( $token, true );
  58. $token->save_meta_data();
  59. $token->apply_changes();
  60. // Make sure all other tokens are not set to default.
  61. if ( $token->is_default() && $token->get_user_id() > 0 ) {
  62. WC_Payment_Tokens::set_users_default( $token->get_user_id(), $token_id );
  63. }
  64. do_action( 'woocommerce_new_payment_token', $token_id );
  65. }
  66. /**
  67. * Update a payment token.
  68. *
  69. * @since 3.0.0
  70. *
  71. * @param WC_Payment_Token $token Payment token object.
  72. *
  73. * @throws Exception Throw exception if invalid or missing payment token fields.
  74. */
  75. public function update( &$token ) {
  76. if ( false === $token->validate() ) {
  77. throw new Exception( __( 'Invalid or missing payment token fields.', 'woocommerce' ) );
  78. }
  79. global $wpdb;
  80. $updated_props = array();
  81. $core_props = array( 'gateway_id', 'token', 'user_id', 'type' );
  82. $changed_props = array_keys( $token->get_changes() );
  83. foreach ( $changed_props as $prop ) {
  84. if ( ! in_array( $prop, $core_props, true ) ) {
  85. continue;
  86. }
  87. $updated_props[] = $prop;
  88. $payment_token_data[ $prop ] = $token->{'get_' . $prop}( 'edit' );
  89. }
  90. if ( ! empty( $payment_token_data ) ) {
  91. $wpdb->update(
  92. $wpdb->prefix . 'woocommerce_payment_tokens',
  93. $payment_token_data,
  94. array( 'token_id' => $token->get_id( 'edit' ) )
  95. );
  96. }
  97. $updated_extra_props = $this->save_extra_data( $token );
  98. $updated_props = array_merge( $updated_props, $updated_extra_props );
  99. $token->save_meta_data();
  100. $token->apply_changes();
  101. // Make sure all other tokens are not set to default.
  102. if ( $token->is_default() && $token->get_user_id() > 0 ) {
  103. WC_Payment_Tokens::set_users_default( $token->get_user_id(), $token->get_id() );
  104. }
  105. do_action( 'woocommerce_payment_token_object_updated_props', $token, $updated_props );
  106. do_action( 'woocommerce_payment_token_updated', $token->get_id() );
  107. }
  108. /**
  109. * Remove a payment token from the database.
  110. *
  111. * @since 3.0.0
  112. * @param WC_Payment_Token $token Payment token object.
  113. * @param bool $force_delete Unused param.
  114. */
  115. public function delete( &$token, $force_delete = false ) {
  116. global $wpdb;
  117. $wpdb->delete( $wpdb->prefix . 'woocommerce_payment_tokens', array( 'token_id' => $token->get_id() ), array( '%d' ) );
  118. $wpdb->delete( $wpdb->prefix . 'woocommerce_payment_tokenmeta', array( 'payment_token_id' => $token->get_id() ), array( '%d' ) );
  119. do_action( 'woocommerce_payment_token_deleted', $token->get_id(), $token );
  120. }
  121. /**
  122. * Read a token from the database.
  123. *
  124. * @since 3.0.0
  125. *
  126. * @param WC_Payment_Token $token Payment token object.
  127. *
  128. * @throws Exception Throw exception if invalid payment token.
  129. */
  130. public function read( &$token ) {
  131. global $wpdb;
  132. $data = $wpdb->get_row(
  133. $wpdb->prepare(
  134. "SELECT token, user_id, gateway_id, is_default FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE token_id = %d LIMIT 1",
  135. $token->get_id()
  136. )
  137. );
  138. if ( $data ) {
  139. $token->set_props(
  140. array(
  141. 'token' => $data->token,
  142. 'user_id' => $data->user_id,
  143. 'gateway_id' => $data->gateway_id,
  144. 'default' => $data->is_default,
  145. )
  146. );
  147. $this->read_extra_data( $token );
  148. $token->read_meta_data();
  149. $token->set_object_read( true );
  150. do_action( 'woocommerce_payment_token_loaded', $token );
  151. } else {
  152. throw new Exception( __( 'Invalid payment token.', 'woocommerce' ) );
  153. }
  154. }
  155. /**
  156. * Read extra data associated with the token (like last4 digits of a card for expiry dates).
  157. *
  158. * @param WC_Payment_Token $token Payment token object.
  159. * @since 3.0.0
  160. */
  161. protected function read_extra_data( &$token ) {
  162. foreach ( $token->get_extra_data_keys() as $key ) {
  163. $function = 'set_' . $key;
  164. if ( is_callable( array( $token, $function ) ) ) {
  165. $token->{$function}( get_metadata( 'payment_token', $token->get_id(), $key, true ) );
  166. }
  167. }
  168. }
  169. /**
  170. * Saves extra token data as meta.
  171. *
  172. * @since 3.0.0
  173. * @param WC_Payment_Token $token Payment token object.
  174. * @param bool $force By default, only changed props are updated. When this param is true all props are updated.
  175. * @return array List of updated props.
  176. */
  177. protected function save_extra_data( &$token, $force = false ) {
  178. if ( $this->extra_data_saved ) {
  179. return array();
  180. }
  181. $updated_props = array();
  182. $extra_data_keys = $token->get_extra_data_keys();
  183. $meta_key_to_props = ! empty( $extra_data_keys ) ? array_combine( $extra_data_keys, $extra_data_keys ) : array();
  184. $props_to_update = $force ? $meta_key_to_props : $this->get_props_to_update( $token, $meta_key_to_props );
  185. foreach ( $extra_data_keys as $key ) {
  186. if ( ! array_key_exists( $key, $props_to_update ) ) {
  187. continue;
  188. }
  189. $function = 'get_' . $key;
  190. if ( is_callable( array( $token, $function ) ) ) {
  191. if ( update_metadata( 'payment_token', $token->get_id(), $key, $token->{$function}( 'edit' ) ) ) {
  192. $updated_props[] = $key;
  193. }
  194. }
  195. }
  196. return $updated_props;
  197. }
  198. /**
  199. * Returns an array of objects (stdObject) matching specific token criteria.
  200. * Accepts token_id, user_id, gateway_id, and type.
  201. * Each object should contain the fields token_id, gateway_id, token, user_id, type, is_default.
  202. *
  203. * @since 3.0.0
  204. * @param array $args List of accepted args: token_id, gateway_id, user_id, type.
  205. * @return array
  206. */
  207. public function get_tokens( $args ) {
  208. global $wpdb;
  209. $args = wp_parse_args(
  210. $args, array(
  211. 'token_id' => '',
  212. 'user_id' => '',
  213. 'gateway_id' => '',
  214. 'type' => '',
  215. )
  216. );
  217. $sql = "SELECT * FROM {$wpdb->prefix}woocommerce_payment_tokens";
  218. $where = array( '1=1' );
  219. if ( $args['token_id'] ) {
  220. $token_ids = array_map( 'absint', is_array( $args['token_id'] ) ? $args['token_id'] : array( $args['token_id'] ) );
  221. $where[] = "token_id IN ('" . implode( "','", array_map( 'esc_sql', $token_ids ) ) . "')";
  222. }
  223. if ( $args['user_id'] ) {
  224. $where[] = $wpdb->prepare( 'user_id = %d', absint( $args['user_id'] ) );
  225. }
  226. if ( $args['gateway_id'] ) {
  227. $gateway_ids = array( $args['gateway_id'] );
  228. } else {
  229. $gateways = WC_Payment_Gateways::instance();
  230. $gateway_ids = $gateways->get_payment_gateway_ids();
  231. }
  232. $page = isset( $args['page'] ) ? absint( $args['page'] ) : 1;
  233. $posts_per_page = isset( $args['limit'] ) ? absint( $args['limit'] ) : get_option( 'posts_per_page' );
  234. $pgstrt = absint( ( $page - 1 ) * $posts_per_page ) . ', ';
  235. $limits = 'LIMIT ' . $pgstrt . $posts_per_page;
  236. $gateway_ids[] = '';
  237. $where[] = "gateway_id IN ('" . implode( "','", array_map( 'esc_sql', $gateway_ids ) ) . "')";
  238. if ( $args['type'] ) {
  239. $where[] = $wpdb->prepare( 'type = %s', $args['type'] );
  240. }
  241. // phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared
  242. $token_results = $wpdb->get_results( $sql . ' WHERE ' . implode( ' AND ', $where ) . ' ' . $limits );
  243. return $token_results;
  244. }
  245. /**
  246. * Returns an stdObject of a token for a user's default token.
  247. * Should contain the fields token_id, gateway_id, token, user_id, type, is_default.
  248. *
  249. * @since 3.0.0
  250. * @param id $user_id User ID.
  251. * @return object
  252. */
  253. public function get_users_default_token( $user_id ) {
  254. global $wpdb;
  255. return $wpdb->get_row(
  256. $wpdb->prepare(
  257. "SELECT * FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE user_id = %d AND is_default = 1",
  258. $user_id
  259. )
  260. );
  261. }
  262. /**
  263. * Returns an stdObject of a token.
  264. * Should contain the fields token_id, gateway_id, token, user_id, type, is_default.
  265. *
  266. * @since 3.0.0
  267. * @param id $token_id Token ID.
  268. * @return object
  269. */
  270. public function get_token_by_id( $token_id ) {
  271. global $wpdb;
  272. return $wpdb->get_row(
  273. $wpdb->prepare(
  274. "SELECT * FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE token_id = %d",
  275. $token_id
  276. )
  277. );
  278. }
  279. /**
  280. * Returns metadata for a specific payment token.
  281. *
  282. * @since 3.0.0
  283. * @param id $token_id Token ID.
  284. * @return array
  285. */
  286. public function get_metadata( $token_id ) {
  287. return get_metadata( 'payment_token', $token_id );
  288. }
  289. /**
  290. * Get a token's type by ID.
  291. *
  292. * @since 3.0.0
  293. * @param id $token_id Token ID.
  294. * @return string
  295. */
  296. public function get_token_type_by_id( $token_id ) {
  297. global $wpdb;
  298. return $wpdb->get_var(
  299. $wpdb->prepare(
  300. "SELECT type FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE token_id = %d",
  301. $token_id
  302. )
  303. );
  304. }
  305. /**
  306. * Update's a tokens default status in the database. Used for quickly
  307. * looping through tokens and setting their statuses instead of creating a bunch
  308. * of objects.
  309. *
  310. * @since 3.0.0
  311. *
  312. * @param id $token_id Token ID.
  313. * @param bool $status Whether given payment token is the default payment token or not.
  314. *
  315. * @return void
  316. */
  317. public function set_default_status( $token_id, $status = true ) {
  318. global $wpdb;
  319. $wpdb->update(
  320. $wpdb->prefix . 'woocommerce_payment_tokens',
  321. array( 'is_default' => $status ),
  322. array(
  323. 'token_id' => $token_id,
  324. )
  325. );
  326. }
  327. }