PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.2.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.2.0
2.3.0 2.2.0 2.1.1 2.1.0 2.0.0 1.10.0 1.9.1 1.9.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 All 59 releases
storeengine / includes / post / dashboard.php

dashboard.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.2.0, at includes/post/dashboard.php

335 lines 10.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace StoreEngine\Post;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 use StoreEngine\Classes\AbstractPostHandler;
10 use StoreEngine\Classes\Countries;
11 use StoreEngine\Utils\Formatting;
12 use StoreEngine\Utils\Helper;
13 use StoreEngine\Utils\Validation;
14 use WP_Error;
15
16 class Dashboard extends AbstractPostHandler {
17 public function __construct() {
18 $fields = [ 'address_type' => 'string' ];
19 foreach ( Countries::init()->get_default_address_fields() as $field => $data ) {
20 $type = $data['type'] ?? 'string';
21 if ( ! $type || 'country' === $field || 'state' === $field || 'tel' === $type ) {
22 $type = 'string';
23 }
24
25 if ( 'checkbox' === $type ) {
26 $type = 'boolean';
27 }
28
29 $fields[ 'billing_' . $field ] = $type;
30 $fields[ 'shipping_' . $field ] = $type;
31 }
32
33 $this->actions = [
34 'frontend_dashboard_change_profile_account_details' => [
35 'callback' => [ $this, 'change_profile_account_details' ],
36 'capability' => 'read',
37 'fields' => [
38 'first_name' => 'string',
39 'last_name' => 'string',
40 'email' => 'email',
41 ],
42 ],
43 'frontend_dashboard_change_password' => [
44 'callback' => [ $this, 'change_password' ],
45 'capability' => 'read',
46 'fields' => [
47 'current_password' => 'string',
48 'new_password' => 'string',
49 'confirm_password' => 'string',
50 ],
51 ],
52 'frontend_dashboard_edit_address' => [
53 'callback' => [ $this, 'change_address' ],
54 'capability' => 'read',
55 'fields' => $fields,
56 ],
57 'frontend_dashboard_save_notifications' => [
58 'callback' => [ $this, 'save_notifications' ],
59 'capability' => 'read',
60 'fields' => [
61 'marketing' => 'boolean',
62 'vendor_new_order' => 'boolean',
63 ],
64 ],
65 'frontend_dashboard_request_data_export' => [
66 'callback' => [ $this, 'request_data_export' ],
67 'capability' => 'read',
68 'fields' => [],
69 ],
70 'frontend_dashboard_request_account_erasure' => [
71 'callback' => [ $this, 'request_account_erasure' ],
72 'capability' => 'read',
73 'fields' => [],
74 ],
75 ];
76 }
77
78 public function change_profile_account_details( $payload ) {
79 $customer = Helper::get_customer();
80
81 if ( empty( $payload['email'] ) ) {
82 wp_die( esc_html__( 'Email address is required.', 'storeengine' ), esc_html__( 'Error', 'storeengine' ), 400 );
83 }
84
85 $customer->set_email( $payload['email'] );
86
87 if ( ! empty( $payload['first_name'] ) ) {
88 $customer->set_first_name( $payload['first_name'] );
89 }
90 if ( ! empty( $payload['last_name'] ) ) {
91 $customer->set_last_name( $payload['last_name'] );
92 }
93
94 $customer->save();
95
96 wp_safe_redirect( Helper::sanitize_referer_url( wp_get_referer() ) );
97 }
98
99 public function change_password( $payload ) {
100 $user = wp_get_current_user();
101
102 if ( empty( $payload['current_password'] ) ) {
103 wp_die( esc_html__( 'Your password is required.', 'storeengine' ), esc_html__( 'Error', 'storeengine' ), 403 );
104 }
105
106 if ( ! wp_check_password( $payload['current_password'], $user->user_pass, $user->ID ) ) {
107 wp_die( esc_html__( 'Invalid Password!', 'storeengine' ), esc_html__( 'Error', 'storeengine' ), 403 );
108 }
109
110 if ( empty( $payload['new_password'] ) || empty( $payload['confirm_password'] ) ) {
111 wp_die( esc_html__( 'Both new password & confirm password is required.', 'storeengine' ), esc_html__( 'Error', 'storeengine' ), 400 );
112 }
113
114 // Check if the new password and confirm password are the same
115 if ( $payload['new_password'] !== $payload['confirm_password'] ) {
116 wp_die( esc_html__( 'Password Mismatch!', 'storeengine' ), esc_html__( 'Error', 'storeengine' ), 400 );
117 }
118
119 wp_update_user( [
120 'ID' => $user->ID,
121 'user_pass' => $payload['new_password'],
122 ] );
123
124 wp_safe_redirect( Helper::sanitize_referer_url( wp_get_referer() ) );
125 }
126
127 /**
128 * Persist the customer's email-notification opt-ins as user_meta. Read
129 * at email send-time via Helper::should_send_notification(). Order-status
130 * is intentionally not in the form — it's transactional and always on.
131 */
132 public function save_notifications( $payload ) {
133 $user_id = get_current_user_id();
134 if ( ! $user_id ) {
135 wp_die( esc_html__( 'You must be logged in to save preferences.', 'storeengine' ), esc_html__( 'Error', 'storeengine' ), 403 );
136 }
137
138 $marketing = ! empty( $payload['marketing'] ) ? '1' : '0';
139 $vendor_new_order = ! empty( $payload['vendor_new_order'] ) ? '1' : '0';
140
141 update_user_meta( $user_id, '_storeengine_notif_marketing', $marketing );
142
143 // Only persist the vendor flag for users with the vendor role —
144 // avoids polluting customer profiles with a meta they can't toggle.
145 $user = wp_get_current_user();
146 if ( $user && in_array( 'storeengine_vendor', (array) $user->roles, true ) ) {
147 update_user_meta( $user_id, '_storeengine_notif_vendor_new_order', $vendor_new_order );
148 }
149
150 wp_safe_redirect(
151 add_query_arg( 'notif_saved', '1', Helper::sanitize_referer_url( wp_get_referer() ) )
152 );
153 exit;
154 }
155
156 /**
157 * Trigger WP core's privacy export flow for the current user. WP sends
158 * a confirmation email; once the user confirms, WP cron generates the
159 * ZIP and emails the download link. We don't reimplement any of that.
160 */
161 public function request_data_export( $payload ) {
162 $user_id = get_current_user_id();
163 $user = $user_id ? get_user_by( 'id', $user_id ) : null;
164 if ( ! $user ) {
165 wp_die( esc_html__( 'You must be logged in.', 'storeengine' ), esc_html__( 'Error', 'storeengine' ), 403 );
166 }
167
168 $result = wp_create_user_request( $user->user_email, 'export_personal_data' );
169 $msg = is_wp_error( $result ) ? 'error' : 'export_requested';
170 if ( ! is_wp_error( $result ) ) {
171 wp_send_user_request( $result );
172 }
173
174 wp_safe_redirect(
175 add_query_arg( 'privacy_msg', $msg, Helper::sanitize_referer_url( wp_get_referer() ) )
176 );
177 exit;
178 }
179
180 /**
181 * Trigger WP core's account erasure flow. Same shape as the export —
182 * WP handles the confirmation email + cron-driven erasure.
183 */
184 public function request_account_erasure( $payload ) {
185 $user_id = get_current_user_id();
186 $user = $user_id ? get_user_by( 'id', $user_id ) : null;
187 if ( ! $user ) {
188 wp_die( esc_html__( 'You must be logged in.', 'storeengine' ), esc_html__( 'Error', 'storeengine' ), 403 );
189 }
190
191 $result = wp_create_user_request( $user->user_email, 'remove_personal_data' );
192 $msg = is_wp_error( $result ) ? 'error' : 'erasure_requested';
193 if ( ! is_wp_error( $result ) ) {
194 wp_send_user_request( $result );
195 }
196
197 wp_safe_redirect(
198 add_query_arg( 'privacy_msg', $msg, Helper::sanitize_referer_url( wp_get_referer() ) )
199 );
200 exit;
201 }
202
203 /**
204 * @param string $field
205 *
206 * @return never-returns
207 */
208 private function die_required( string $field ) {
209 wp_die(
210 sprintf(
211 // translators: %s. Field name/label.
212 esc_html__( '%s is required.', 'storeengine' ),
213 '<strong>' . esc_html( $field ) . '</strong>'
214 ),
215 [
216 'code' => 400,
217 'back_link' => true,
218 ]
219 );
220 }
221
222 public function change_address( $payload ) {
223 $valid_types = [ 'billing', 'shipping' ];
224
225 if ( empty( $payload['address_type'] ) ) {
226 $this->die_required( __( 'Address Type', 'storeengine' ) );
227 }
228
229 if ( ! in_array( $payload['address_type'], $valid_types, true ) ) {
230 wp_die(
231 sprintf(
232 /* translators: %s: Address type in form data. */
233 esc_html__( 'Invalid address type! Address type must be one of billing or shipping, %s given', 'storeengine' ),
234 esc_html( $payload['address_type'] )
235 ),
236 esc_html__( 'Error! Invalid request!', 'storeengine' ),
237 400
238 );
239 }
240
241 $address_type = $payload['address_type'];
242 $type_label = ( 'billing' === $address_type ) ? __( 'Billing', 'storeengine' ) : __( 'Shipping', 'storeengine' );
243 $country = $payload[ $address_type . '_country' ] ?? '';
244
245 if ( ! $country ) {
246 /* translators: %s Address type (Billing/Shipping) */
247 $this->die_required( sprintf( __( '%s Country', 'storeengine' ), $type_label ) );
248 }
249
250 $customer = Helper::get_customer();
251 $fields = Countries::init()->get_address_fields( $country, $address_type . '_' );
252 $errors = new WP_Error();
253
254 foreach ( $fields as $key => $field ) {
255 if ( ! isset( $field['type'] ) ) {
256 $field['type'] = 'text';
257 }
258
259 // Get Value.
260 if ( 'checkbox' === $field['type'] ) {
261 $value = (int) isset( $payload[ $key ] );
262 } else {
263 $value = $payload[ $key ] ?? null;
264 }
265
266 // Hook to allow modification of value.
267 $value = apply_filters( 'storeengine/frontend/dashboard/edit-account/process_field_' . $key, $value );
268
269 if ( isset( $field['required'] ) && $field['required'] && empty( $value ) ) {
270 // translators: %s. Field name/label.
271 $errors->add( 'missing-' . $key . '-field', sprintf( esc_html__( '%s is required.', 'storeengine' ), $field['label'] ), [ 'id' => $key ] );
272 }
273
274 if ( ! empty( $value ) ) {
275 if ( ! empty( $field['validate'] ) && is_array( $field['validate'] ) ) {
276 foreach ( $field['validate'] as $rule ) {
277 switch ( $rule ) {
278 case 'postcode':
279 $value = Formatting::format_postcode( $value, $country );
280 if ( '' !== $value && ! Validation::is_postcode( $value, $country ) ) {
281 switch ( $country ) {
282 case 'IE':
283 $postcode_validation_notice = __( 'Please enter a valid Eircode.', 'storeengine' );
284 break;
285 default:
286 $postcode_validation_notice = __( 'Please enter a valid postcode / ZIP.', 'storeengine' );
287 }
288
289 $errors->add( 'invalid-' . $key, $postcode_validation_notice );
290 }
291 break;
292 case 'phone':
293 if ( '' !== $value && ! Validation::is_phone( $value ) ) {
294 /* translators: %s: Phone number. */
295 $errors->add( 'invalid-' . $key, sprintf( __( '%s is not a valid phone number.', 'storeengine' ), '<strong>' . $field['label'] . '</strong>' ), [ 'id' => $key ] );
296 }
297 break;
298 case 'email':
299 $value = strtolower( $value );
300
301 if ( ! is_email( $value ) ) {
302 /* translators: %s: Email address. */
303 $errors->add( 'invalid-' . $key, sprintf( __( '%s is not a valid email address.', 'storeengine' ), '<strong>' . $field['label'] . '</strong>' ), [ 'id' => $key ] );
304 }
305 break;
306 }
307 }
308 }
309 }
310
311 if ( is_callable( [ $customer, "set_$key" ] ) ) {
312 $customer->{"set_$key"}( $value );
313 } else {
314 update_user_meta( $customer->get_id(), $key, $value );
315 }
316 }
317
318 if ( $errors->has_errors() ) {
319 wp_die(
320 $errors, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- WP_Error object.
321 esc_html__( 'Missing required fields', 'storeengine' ),
322 [
323 'code' => 400,
324 'back_link' => true,
325 ]
326 );
327 }
328
329 $customer->save();
330
331 wp_safe_redirect( Helper::get_account_endpoint_url( 'edit-address' ) );
332 die();
333 }
334 }
335