class-wc-customer-data-store.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. <?php
  2. /**
  3. * Class WC_Customer_Data_Store file.
  4. *
  5. * @package WooCommerce\DataStores
  6. */
  7. if ( ! defined( 'ABSPATH' ) ) {
  8. exit;
  9. }
  10. /**
  11. * WC Customer Data Store.
  12. *
  13. * @version 3.0.0
  14. */
  15. class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Data_Store_Interface, WC_Object_Data_Store_Interface {
  16. /**
  17. * Data stored in meta keys, but not considered "meta".
  18. *
  19. * @since 3.0.0
  20. * @var array
  21. */
  22. protected $internal_meta_keys = array(
  23. 'locale',
  24. 'billing_postcode',
  25. 'billing_city',
  26. 'billing_address_1',
  27. 'billing_address_2',
  28. 'billing_state',
  29. 'billing_country',
  30. 'shipping_postcode',
  31. 'shipping_city',
  32. 'shipping_address_1',
  33. 'shipping_address_2',
  34. 'shipping_state',
  35. 'shipping_country',
  36. 'paying_customer',
  37. 'last_update',
  38. 'first_name',
  39. 'last_name',
  40. 'display_name',
  41. 'show_admin_bar_front',
  42. 'use_ssl',
  43. 'admin_color',
  44. 'rich_editing',
  45. 'comment_shortcuts',
  46. 'dismissed_wp_pointers',
  47. 'show_welcome_panel',
  48. 'session_tokens',
  49. 'nickname',
  50. 'description',
  51. 'billing_first_name',
  52. 'billing_last_name',
  53. 'billing_company',
  54. 'billing_phone',
  55. 'billing_email',
  56. 'shipping_first_name',
  57. 'shipping_last_name',
  58. 'shipping_company',
  59. 'wptests_capabilities',
  60. 'wptests_user_level',
  61. 'syntax_highlighting',
  62. '_order_count',
  63. '_money_spent',
  64. );
  65. /**
  66. * Internal meta type used to store user data.
  67. *
  68. * @var string
  69. */
  70. protected $meta_type = 'user';
  71. /**
  72. * Callback to remove unwanted meta data.
  73. *
  74. * @param object $meta Meta object.
  75. * @return bool
  76. */
  77. protected function exclude_internal_meta_keys( $meta ) {
  78. global $wpdb;
  79. $table_prefix = $wpdb->prefix ? $wpdb->prefix : 'wp_';
  80. return ! in_array( $meta->meta_key, $this->internal_meta_keys, true )
  81. && 0 !== strpos( $meta->meta_key, '_woocommerce_persistent_cart' )
  82. && 0 !== strpos( $meta->meta_key, 'closedpostboxes_' )
  83. && 0 !== strpos( $meta->meta_key, 'metaboxhidden_' )
  84. && 0 !== strpos( $meta->meta_key, 'manageedit-' )
  85. && ! strstr( $meta->meta_key, $table_prefix )
  86. && 0 !== stripos( $meta->meta_key, 'wp_' );
  87. }
  88. /**
  89. * Method to create a new customer in the database.
  90. *
  91. * @since 3.0.0
  92. *
  93. * @param WC_Customer $customer Customer object.
  94. *
  95. * @throws WC_Data_Exception If unable to create new customer.
  96. */
  97. public function create( &$customer ) {
  98. $id = wc_create_new_customer( $customer->get_email(), $customer->get_username(), $customer->get_password() );
  99. if ( is_wp_error( $id ) ) {
  100. throw new WC_Data_Exception( $id->get_error_code(), $id->get_error_message() );
  101. }
  102. $customer->set_id( $id );
  103. $this->update_user_meta( $customer );
  104. // Prevent wp_update_user calls in the same request and customer trigger the 'Notice of Password Changed' email.
  105. $customer->set_password( '' );
  106. wp_update_user(
  107. apply_filters(
  108. 'woocommerce_update_customer_args', array(
  109. 'ID' => $customer->get_id(),
  110. 'role' => $customer->get_role(),
  111. 'display_name' => $customer->get_display_name(),
  112. ), $customer
  113. )
  114. );
  115. $wp_user = new WP_User( $customer->get_id() );
  116. $customer->set_date_created( $wp_user->user_registered );
  117. $customer->set_date_modified( get_user_meta( $customer->get_id(), 'last_update', true ) );
  118. $customer->save_meta_data();
  119. $customer->apply_changes();
  120. do_action( 'woocommerce_new_customer', $customer->get_id() );
  121. }
  122. /**
  123. * Method to read a customer object.
  124. *
  125. * @since 3.0.0
  126. * @param WC_Customer $customer Customer object.
  127. * @throws Exception If invalid customer.
  128. */
  129. public function read( &$customer ) {
  130. $user_object = $customer->get_id() ? get_user_by( 'id', $customer->get_id() ) : false;
  131. // User object is required.
  132. if ( ! $user_object || empty( $user_object->ID ) ) {
  133. throw new Exception( __( 'Invalid customer.', 'woocommerce' ) );
  134. }
  135. // Only users on this site should be read.
  136. if ( is_multisite() && ! is_user_member_of_blog( $customer->get_id() ) ) {
  137. throw new Exception( __( 'Invalid customer.', 'woocommerce' ) );
  138. }
  139. $customer_id = $customer->get_id();
  140. // Load meta but exclude deprecated props.
  141. $user_meta = array_diff_key(
  142. array_map( 'wc_flatten_meta_callback', get_user_meta( $customer_id ) ),
  143. array_flip( array( 'country', 'state', 'postcode', 'city', 'address', 'address_2', 'default', 'location' ) )
  144. );
  145. $customer->set_props( $user_meta );
  146. $customer->set_props(
  147. array(
  148. 'is_paying_customer' => get_user_meta( $customer_id, 'paying_customer', true ),
  149. 'email' => $user_object->user_email,
  150. 'username' => $user_object->user_login,
  151. 'display_name' => $user_object->display_name,
  152. 'date_created' => $user_object->user_registered, // Mysql string in local format.
  153. 'date_modified' => get_user_meta( $customer_id, 'last_update', true ),
  154. 'role' => ! empty( $user_object->roles[0] ) ? $user_object->roles[0] : 'customer',
  155. )
  156. );
  157. $customer->read_meta_data();
  158. $customer->set_object_read( true );
  159. do_action( 'woocommerce_customer_loaded', $customer );
  160. }
  161. /**
  162. * Updates a customer in the database.
  163. *
  164. * @since 3.0.0
  165. * @param WC_Customer $customer Customer object.
  166. */
  167. public function update( &$customer ) {
  168. wp_update_user(
  169. apply_filters(
  170. 'woocommerce_update_customer_args', array(
  171. 'ID' => $customer->get_id(),
  172. 'user_email' => $customer->get_email(),
  173. 'display_name' => $customer->get_display_name(),
  174. ), $customer
  175. )
  176. );
  177. // Only update password if a new one was set with set_password.
  178. if ( $customer->get_password() ) {
  179. wp_update_user(
  180. array(
  181. 'ID' => $customer->get_id(),
  182. 'user_pass' => $customer->get_password(),
  183. )
  184. );
  185. $customer->set_password( '' );
  186. }
  187. $this->update_user_meta( $customer );
  188. $customer->set_date_modified( get_user_meta( $customer->get_id(), 'last_update', true ) );
  189. $customer->save_meta_data();
  190. $customer->apply_changes();
  191. do_action( 'woocommerce_update_customer', $customer->get_id() );
  192. }
  193. /**
  194. * Deletes a customer from the database.
  195. *
  196. * @since 3.0.0
  197. * @param WC_Customer $customer Customer object.
  198. * @param array $args Array of args to pass to the delete method.
  199. */
  200. public function delete( &$customer, $args = array() ) {
  201. if ( ! $customer->get_id() ) {
  202. return;
  203. }
  204. $args = wp_parse_args(
  205. $args, array(
  206. 'reassign' => 0,
  207. )
  208. );
  209. $id = $customer->get_id();
  210. wp_delete_user( $id, $args['reassign'] );
  211. do_action( 'woocommerce_delete_customer', $id );
  212. }
  213. /**
  214. * Helper method that updates all the meta for a customer. Used for update & create.
  215. *
  216. * @since 3.0.0
  217. * @param WC_Customer $customer Customer object.
  218. */
  219. private function update_user_meta( $customer ) {
  220. $updated_props = array();
  221. $changed_props = $customer->get_changes();
  222. $meta_key_to_props = array(
  223. 'paying_customer' => 'is_paying_customer',
  224. 'first_name' => 'first_name',
  225. 'last_name' => 'last_name',
  226. );
  227. foreach ( $meta_key_to_props as $meta_key => $prop ) {
  228. if ( ! array_key_exists( $prop, $changed_props ) ) {
  229. continue;
  230. }
  231. if ( update_user_meta( $customer->get_id(), $meta_key, $customer->{"get_$prop"}( 'edit' ) ) ) {
  232. $updated_props[] = $prop;
  233. }
  234. }
  235. $billing_address_props = array(
  236. 'billing_first_name' => 'billing_first_name',
  237. 'billing_last_name' => 'billing_last_name',
  238. 'billing_company' => 'billing_company',
  239. 'billing_address_1' => 'billing_address_1',
  240. 'billing_address_2' => 'billing_address_2',
  241. 'billing_city' => 'billing_city',
  242. 'billing_state' => 'billing_state',
  243. 'billing_postcode' => 'billing_postcode',
  244. 'billing_country' => 'billing_country',
  245. 'billing_email' => 'billing_email',
  246. 'billing_phone' => 'billing_phone',
  247. );
  248. foreach ( $billing_address_props as $meta_key => $prop ) {
  249. $prop_key = substr( $prop, 8 );
  250. if ( ! isset( $changed_props['billing'] ) || ! array_key_exists( $prop_key, $changed_props['billing'] ) ) {
  251. continue;
  252. }
  253. if ( update_user_meta( $customer->get_id(), $meta_key, $customer->{"get_$prop"}( 'edit' ) ) ) {
  254. $updated_props[] = $prop;
  255. }
  256. }
  257. $shipping_address_props = array(
  258. 'shipping_first_name' => 'shipping_first_name',
  259. 'shipping_last_name' => 'shipping_last_name',
  260. 'shipping_company' => 'shipping_company',
  261. 'shipping_address_1' => 'shipping_address_1',
  262. 'shipping_address_2' => 'shipping_address_2',
  263. 'shipping_city' => 'shipping_city',
  264. 'shipping_state' => 'shipping_state',
  265. 'shipping_postcode' => 'shipping_postcode',
  266. 'shipping_country' => 'shipping_country',
  267. );
  268. foreach ( $shipping_address_props as $meta_key => $prop ) {
  269. $prop_key = substr( $prop, 9 );
  270. if ( ! isset( $changed_props['shipping'] ) || ! array_key_exists( $prop_key, $changed_props['shipping'] ) ) {
  271. continue;
  272. }
  273. if ( update_user_meta( $customer->get_id(), $meta_key, $customer->{"get_$prop"}( 'edit' ) ) ) {
  274. $updated_props[] = $prop;
  275. }
  276. }
  277. do_action( 'woocommerce_customer_object_updated_props', $customer, $updated_props );
  278. }
  279. /**
  280. * Gets the customers last order.
  281. *
  282. * @since 3.0.0
  283. * @param WC_Customer $customer Customer object.
  284. * @return WC_Order|false
  285. */
  286. public function get_last_order( &$customer ) {
  287. global $wpdb;
  288. $last_order = $wpdb->get_var(
  289. // phpcs:disable WordPress.WP.PreparedSQL.NotPrepared
  290. "SELECT posts.ID
  291. FROM $wpdb->posts AS posts
  292. LEFT JOIN {$wpdb->postmeta} AS meta on posts.ID = meta.post_id
  293. WHERE meta.meta_key = '_customer_user'
  294. AND meta.meta_value = '" . esc_sql( $customer->get_id() ) . "'
  295. AND posts.post_type = 'shop_order'
  296. AND posts.post_status IN ( '" . implode( "','", array_map( 'esc_sql', array_keys( wc_get_order_statuses() ) ) ) . "' )
  297. ORDER BY posts.ID DESC"
  298. // phpcs:enable
  299. );
  300. if ( ! $last_order ) {
  301. return false;
  302. }
  303. return wc_get_order( absint( $last_order ) );
  304. }
  305. /**
  306. * Return the number of orders this customer has.
  307. *
  308. * @since 3.0.0
  309. * @param WC_Customer $customer Customer object.
  310. * @return integer
  311. */
  312. public function get_order_count( &$customer ) {
  313. $count = get_user_meta( $customer->get_id(), '_order_count', true );
  314. if ( '' === $count ) {
  315. global $wpdb;
  316. $count = $wpdb->get_var(
  317. // phpcs:disable WordPress.WP.PreparedSQL.NotPrepared
  318. "SELECT COUNT(*)
  319. FROM $wpdb->posts as posts
  320. LEFT JOIN {$wpdb->postmeta} AS meta ON posts.ID = meta.post_id
  321. WHERE meta.meta_key = '_customer_user'
  322. AND posts.post_type = 'shop_order'
  323. AND posts.post_status IN ( '" . implode( "','", array_map( 'esc_sql', array_keys( wc_get_order_statuses() ) ) ) . "' )
  324. AND meta_value = '" . esc_sql( $customer->get_id() ) . "'"
  325. // phpcs:enable
  326. );
  327. update_user_meta( $customer->get_id(), '_order_count', $count );
  328. }
  329. return absint( $count );
  330. }
  331. /**
  332. * Return how much money this customer has spent.
  333. *
  334. * @since 3.0.0
  335. * @param WC_Customer $customer Customer object.
  336. * @return float
  337. */
  338. public function get_total_spent( &$customer ) {
  339. $spent = apply_filters(
  340. 'woocommerce_customer_get_total_spent',
  341. get_user_meta( $customer->get_id(), '_money_spent', true ),
  342. $customer
  343. );
  344. if ( '' === $spent ) {
  345. global $wpdb;
  346. $statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
  347. $spent = $wpdb->get_var(
  348. // phpcs:disable WordPress.WP.PreparedSQL.NotPrepared
  349. apply_filters(
  350. 'woocommerce_customer_get_total_spent_query',
  351. "SELECT SUM(meta2.meta_value)
  352. FROM $wpdb->posts as posts
  353. LEFT JOIN {$wpdb->postmeta} AS meta ON posts.ID = meta.post_id
  354. LEFT JOIN {$wpdb->postmeta} AS meta2 ON posts.ID = meta2.post_id
  355. WHERE meta.meta_key = '_customer_user'
  356. AND meta.meta_value = '" . esc_sql( $customer->get_id() ) . "'
  357. AND posts.post_type = 'shop_order'
  358. AND posts.post_status IN ( 'wc-" . implode( "','wc-", $statuses ) . "' )
  359. AND meta2.meta_key = '_order_total'",
  360. $customer
  361. )
  362. // phpcs:enable
  363. );
  364. if ( ! $spent ) {
  365. $spent = 0;
  366. }
  367. update_user_meta( $customer->get_id(), '_money_spent', $spent );
  368. }
  369. return wc_format_decimal( $spent, 2 );
  370. }
  371. /**
  372. * Search customers and return customer IDs.
  373. *
  374. * @param string $term Search term.
  375. * @param int|string $limit Limit search results.
  376. * @since 3.0.7
  377. *
  378. * @return array
  379. */
  380. public function search_customers( $term, $limit = '' ) {
  381. $results = apply_filters( 'woocommerce_customer_pre_search_customers', false, $term, $limit );
  382. if ( is_array( $results ) ) {
  383. return $results;
  384. }
  385. $query = new WP_User_Query(
  386. apply_filters(
  387. 'woocommerce_customer_search_customers', array(
  388. 'search' => '*' . esc_attr( $term ) . '*',
  389. 'search_columns' => array( 'user_login', 'user_url', 'user_email', 'user_nicename', 'display_name' ),
  390. 'fields' => 'ID',
  391. 'number' => $limit,
  392. ), $term, $limit, 'main_query'
  393. )
  394. );
  395. $query2 = new WP_User_Query(
  396. apply_filters(
  397. 'woocommerce_customer_search_customers', array(
  398. 'fields' => 'ID',
  399. 'number' => $limit,
  400. 'meta_query' => array(
  401. 'relation' => 'OR',
  402. array(
  403. 'key' => 'first_name',
  404. 'value' => $term,
  405. 'compare' => 'LIKE',
  406. ),
  407. array(
  408. 'key' => 'last_name',
  409. 'value' => $term,
  410. 'compare' => 'LIKE',
  411. ),
  412. ),
  413. ), $term, $limit, 'meta_query'
  414. )
  415. );
  416. $results = wp_parse_id_list( array_merge( (array) $query->get_results(), (array) $query2->get_results() ) );
  417. if ( $limit && count( $results ) > $limit ) {
  418. $results = array_slice( $results, 0, $limit );
  419. }
  420. return $results;
  421. }
  422. }