PluginProbe
Easy Hotel – Powerful Hotel Booking / 2.0.2
Easy Hotel – Powerful Hotel Booking v2.0.2
2.0.8 2.0.7 2.0.6 2.0.5 2.0.4 2.0.3 2.0.2 2.0.1 2.0.0 1.9.9 1.9.8 1.9.7 1.9.6 1.9.5 1.9.4 1.9.3 1.9.2 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 All 110 releases
easy-hotel / admin / includes / native-checkout / account / class-customer.php

class-customer.php in Easy Hotel – Powerful Hotel Booking 2.0.2, at admin/includes/native-checkout/account/class-customer.php

288 lines 10.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Customer ↔ WordPress user bridge for the Native Checkout account area.
4 *
5 * Native checkout is guest-first: a booking only stores the customer's
6 * name/email in post meta. To power a "My Account" dashboard we need a
7 * real WordPress user behind each customer, so this class:
8 *
9 * - Auto-creates (or links) a WP user when a booking is completed,
10 * hooked onto `eshb_native_checkout_booking_created`.
11 * - Stores fast, queryable flat meta on each booking
12 * (`_eshb_customer_user_id`, `_eshb_customer_email`) so the account
13 * pages can list "my bookings" without LIKE-scanning serialized meta.
14 * - Resolves which bookings belong to the current user and answers the
15 * ownership question used by every account/cancellation action.
16 *
17 * @package EasyHotel\NativeCheckout\Account
18 */
19
20 if ( ! defined( 'ABSPATH' ) ) exit;
21
22 class ESHB_Native_Account_Customer {
23
24 /** Flat, queryable booking meta keys. */
25 const META_USER_ID = '_eshb_customer_user_id';
26 const META_EMAIL = '_eshb_customer_email';
27
28 /** Custom role assigned to checkout customers. */
29 const ROLE = 'eshb_customer';
30
31 public function __construct() {
32 // Ensure the customer role exists (for installs already activated).
33 add_action( 'init', [ $this, 'register_role' ] );
34
35 // Link / create the WP user right after a native-checkout booking
36 // is persisted. Priority 5 so the user exists before other
37 // booking_created listeners (e.g. emails) run.
38 add_action( 'eshb_native_checkout_booking_created', [ $this, 'link_booking_to_user' ], 5, 4 );
39 }
40
41 /**
42 * Register the "Easy Hotel Customer" role if it doesn't exist yet.
43 * Customers only need front-end read access.
44 */
45 public function register_role() {
46 if ( null === get_role( self::ROLE ) ) {
47 add_role( self::ROLE, __( 'Easy Hotel Customer', 'easy-hotel' ), [ 'read' => true ] );
48 }
49 }
50
51 /**
52 * Ensure a WordPress user exists for the booking's customer and stamp
53 * the booking with queryable ownership meta.
54 *
55 * @param int $booking_id Booking post id.
56 * @param array $reservation Reservation payload (unused but part of the hook signature).
57 * @param array $customer Customer details captured at checkout.
58 * @param array $pricing Pricing payload (unused).
59 */
60 public function link_booking_to_user( $booking_id, $reservation, $customer, $pricing ) {
61 $email = isset( $customer['email'] ) ? sanitize_email( $customer['email'] ) : '';
62 if ( ! $booking_id ) {
63 return;
64 }
65
66 $user_id = $this->ensure_user( $customer );
67
68 if ( $user_id ) {
69 update_post_meta( $booking_id, self::META_USER_ID, (int) $user_id );
70 // Attribute authorship too, so standard WP tooling sees the link.
71 wp_update_post( [ 'ID' => $booking_id, 'post_author' => (int) $user_id ] );
72 }
73 if ( $email ) {
74 update_post_meta( $booking_id, self::META_EMAIL, strtolower( $email ) );
75 }
76 }
77
78 /**
79 * Find or create the WordPress user for a customer array.
80 *
81 * Order of resolution:
82 * 1. A logged-in user is treated as the booking owner.
83 * 2. An existing account with the same email is reused.
84 * 3. Otherwise a new customer account is created and notified.
85 *
86 * Auto-creation can be disabled via the
87 * `eshb_native_account_auto_create_user` filter.
88 *
89 * @param array $customer Customer details (email, first_name, last_name).
90 * @return int User id, or 0 when none could be resolved.
91 */
92 public function ensure_user( array $customer ) {
93 if ( is_user_logged_in() ) {
94 return get_current_user_id();
95 }
96
97 $email = isset( $customer['email'] ) ? sanitize_email( $customer['email'] ) : '';
98 if ( ! $email || ! is_email( $email ) ) {
99 return 0;
100 }
101
102 $existing = get_user_by( 'email', $email );
103 if ( $existing instanceof WP_User ) {
104 return (int) $existing->ID;
105 }
106
107 /**
108 * Whether to auto-create a customer account at checkout.
109 *
110 * @param bool $create
111 * @param array $customer
112 */
113 if ( ! apply_filters( 'eshb_native_account_auto_create_user', true, $customer ) ) {
114 return 0;
115 }
116
117 return $this->create_customer_user( $customer, $email );
118 }
119
120 /**
121 * Create a new customer WP user from checkout details.
122 *
123 * @return int New user id, or 0 on failure.
124 */
125 private function create_customer_user( array $customer, $email ) {
126 $username = $this->generate_username( $email );
127
128 $userdata = [
129 'user_login' => $username,
130 'user_email' => $email,
131 'user_pass' => wp_generate_password( 24, true, true ),
132 'first_name' => sanitize_text_field( $customer['first_name'] ?? '' ),
133 'last_name' => sanitize_text_field( $customer['last_name'] ?? '' ),
134 'display_name' => trim( ( $customer['first_name'] ?? '' ) . ' ' . ( $customer['last_name'] ?? '' ) ),
135 /**
136 * Role assigned to auto-created customers.
137 *
138 * @param string $role
139 */
140 'role' => apply_filters( 'eshb_native_account_customer_role', self::ROLE ),
141 ];
142
143 $user_id = wp_insert_user( $userdata );
144 if ( is_wp_error( $user_id ) || ! $user_id ) {
145 return 0;
146 }
147
148 // Send WordPress' "set your password" notification so the customer
149 // can log in to manage their booking. Wrapped so a mail failure
150 // never breaks the checkout completion.
151 try {
152 wp_new_user_notification( $user_id, null, 'user' );
153 } catch ( \Throwable $e ) {
154 unset( $e );
155 }
156
157 /**
158 * Fires after a customer account is auto-created at checkout.
159 *
160 * @param int $user_id
161 * @param array $customer
162 */
163 do_action( 'eshb_native_account_user_created', $user_id, $customer );
164
165 return (int) $user_id;
166 }
167
168 /**
169 * Build a unique, readable username from an email local-part.
170 */
171 private function generate_username( $email ) {
172 $base = sanitize_user( current( explode( '@', $email ) ), true );
173 if ( empty( $base ) ) {
174 $base = 'customer';
175 }
176 $username = $base;
177 $i = 1;
178 while ( username_exists( $username ) ) {
179 $username = $base . $i;
180 $i++;
181 }
182 return $username;
183 }
184
185 /* -----------------------------------------------------------------
186 * Ownership / lookup
187 * -------------------------------------------------------------- */
188
189 /**
190 * Booking ids owned by a user, newest first.
191 *
192 * Matches on the fast flat meta first, then falls back to the email
193 * stored inside the serialized customer meta (legacy bookings created
194 * before this feature), back-filling the flat meta as it goes so the
195 * slow path only ever runs once per booking.
196 *
197 * @param WP_User|int|null $user Defaults to the current user.
198 * @return int[] Booking post ids.
199 */
200 public function get_user_booking_ids( $user = null ) {
201 $user = $this->resolve_user( $user );
202 if ( ! $user ) {
203 return [];
204 }
205
206 $email = strtolower( $user->user_email );
207
208 $query = new WP_Query( [
209 'post_type' => 'eshb_booking',
210 'post_status' => 'any',
211 'posts_per_page' => -1,
212 'fields' => 'ids',
213 'no_found_rows' => true,
214 'orderby' => 'date',
215 'order' => 'DESC',
216 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
217 'meta_query' => [
218 'relation' => 'OR',
219 [ 'key' => self::META_USER_ID, 'value' => (int) $user->ID ],
220 [ 'key' => self::META_EMAIL, 'value' => $email ],
221 [
222 'key' => 'eshb_booking_customer_details_metaboxes',
223 'value' => '"' . $email . '"',
224 'compare' => 'LIKE',
225 ],
226 ],
227 ] );
228
229 $ids = array_map( 'intval', $query->posts );
230
231 // Back-fill flat meta for any legacy booking matched via the
232 // serialized-email path so future lookups hit the fast index.
233 foreach ( $ids as $id ) {
234 if ( '' === get_post_meta( $id, self::META_EMAIL, true ) ) {
235 update_post_meta( $id, self::META_EMAIL, $email );
236 }
237 }
238
239 return $ids;
240 }
241
242 /**
243 * Whether the given user owns the booking. Used to gate every
244 * customer-facing booking action server-side.
245 *
246 * @param int $booking_id
247 * @param WP_User|int|null $user Defaults to current user.
248 */
249 public function user_owns_booking( $booking_id, $user = null ) {
250 $booking_id = (int) $booking_id;
251 $user = $this->resolve_user( $user );
252 if ( ! $booking_id || ! $user || get_post_type( $booking_id ) !== 'eshb_booking' ) {
253 return false;
254 }
255
256 $owner_id = (int) get_post_meta( $booking_id, self::META_USER_ID, true );
257 if ( $owner_id && $owner_id === (int) $user->ID ) {
258 return true;
259 }
260
261 // Fall back to an email comparison against the authoritative
262 // serialized customer meta (covers legacy / unlinked bookings).
263 $customer = get_post_meta( $booking_id, 'eshb_booking_customer_details_metaboxes', true );
264 $email = is_array( $customer ) ? strtolower( (string) ( $customer['email'] ?? '' ) ) : '';
265
266 return $email !== '' && $email === strtolower( $user->user_email );
267 }
268
269 /**
270 * Normalize a user argument (id / WP_User / null) to a WP_User.
271 *
272 * @return WP_User|null
273 */
274 private function resolve_user( $user ) {
275 if ( $user instanceof WP_User ) {
276 return $user;
277 }
278 if ( is_numeric( $user ) && $user > 0 ) {
279 $obj = get_user_by( 'id', (int) $user );
280 return $obj instanceof WP_User ? $obj : null;
281 }
282 if ( is_user_logged_in() ) {
283 return wp_get_current_user();
284 }
285 return null;
286 }
287 }
288