説明なし

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

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