PluginProbe ʕ •ᴥ•ʔ
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress / 5.2.10
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress v5.2.10
5.6.9 5.6.8 5.6.7 5.6.6 5.6.5 5.6.4 5.6.3 5.6.2 5.6.1 5.6.0 5.5.2 5.5.1 5.5.0 5.4.2 trunk 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.1.91 5.1.92 5.1.93 5.1.94 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1
latepoint / lib / controllers / customers_controller.php
latepoint / lib / controllers Last commit date
activities_controller.php 4 months ago auth_controller.php 4 months ago booking_form_settings_controller.php 5 months ago bookings_controller.php 1 year ago calendars_controller.php 9 months ago carts_controller.php 1 year ago controller.php 9 months ago customer_cabinet_controller.php 4 months ago customers_controller.php 4 months ago dashboard_controller.php 9 months ago default_agent_controller.php 1 year ago events_controller.php 1 year ago form_fields_controller.php 9 months ago integrations_controller.php 9 months ago invoices_controller.php 4 months ago manage_booking_by_key_controller.php 4 months ago manage_order_by_key_controller.php 1 year ago notifications_controller.php 1 year ago orders_controller.php 4 months ago pro_controller.php 1 year ago process_jobs_controller.php 4 months ago processes_controller.php 4 months ago search_controller.php 1 year ago services_controller.php 9 months ago settings_controller.php 4 months ago steps_controller.php 9 months ago stripe_connect_controller.php 9 months ago support_topics_controller.php 1 year ago todos_controller.php 1 year ago transactions_controller.php 4 months ago wizard_controller.php 4 months ago
customers_controller.php
507 lines
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 exit; // Exit if accessed directly.
4 }
5
6
7 if ( ! class_exists( 'OsCustomersController' ) ) :
8
9
10 class OsCustomersController extends OsController {
11
12 function __construct() {
13 parent::__construct();
14
15
16 $this->views_folder = LATEPOINT_VIEWS_ABSPATH . 'customers/';
17 $this->vars['page_header'] = OsMenuHelper::get_menu_items_by_id( 'customers' );
18 $this->vars['breadcrumbs'][] = array( 'label' => __( 'Customers', 'latepoint' ), 'link' => OsRouterHelper::build_link( OsRouterHelper::build_route_name( 'customers', 'index' ) ) );
19 }
20
21 public function destroy() {
22 if ( filter_var( $this->params['id'], FILTER_VALIDATE_INT ) ) {
23 $this->check_nonce( 'destroy_customer_' . $this->params['id'] );
24 $customer = new OsCustomerModel( $this->params['id'] );
25 if ( $customer->delete() ) {
26 $status = LATEPOINT_STATUS_SUCCESS;
27 $response_html = __( 'Customer Removed', 'latepoint' );
28 } else {
29 $status = LATEPOINT_STATUS_ERROR;
30 $response_html = __( 'Error Removing Customer', 'latepoint' );
31 }
32 } else {
33 $status = LATEPOINT_STATUS_ERROR;
34 $response_html = __( 'Error Removing Customer', 'latepoint' );
35 }
36
37 if ( $this->get_return_format() == 'json' ) {
38 $this->send_json( array( 'status' => $status, 'message' => $response_html ) );
39 }
40 }
41
42
43 public function view_customer_log() {
44
45 $activities = new OsActivityModel();
46 $activities = $activities->where( [ 'customer_id' => absint( $this->params['customer_id'] ) ] )->order_by( 'id desc' )->get_results_as_models();
47
48 $customer = new OsCustomerModel( $this->params['customer_id'] );
49
50 $this->vars['customer'] = $customer;
51 $this->vars['activities'] = $activities;
52
53 $this->format_render( __FUNCTION__ );
54 }
55
56
57 public function quick_new() {
58 $customer = new OsCustomerModel();
59
60 $this->vars['customer'] = $customer;
61
62 $this->format_render( 'quick_edit' );
63 }
64
65 public function quick_edit() {
66 if ( ! filter_var( $this->params['customer_id'], FILTER_VALIDATE_INT ) ) {
67 $this->access_not_allowed();
68 }
69 $customer = new OsCustomerModel( $this->params['customer_id'] );
70
71 $this->vars['customer'] = $customer;
72
73 $this->format_render( __FUNCTION__ );
74 }
75
76
77 public function inline_edit_form() {
78 $selected_customer = new OsCustomerModel();
79 if ( isset( $this->params['customer_id'] ) ) {
80 $selected_customer->load_by_id( $this->params['customer_id'] );
81 }
82 $this->vars['default_fields_for_customer'] = OsSettingsHelper::get_default_fields_for_customer();
83 $this->vars['selected_customer'] = $selected_customer;
84 $this->format_render( __FUNCTION__ );
85 }
86
87 public function set_as_guest() {
88 // CSRF protection
89 $this->check_nonce( 'set_customer_as_guest_' . $this->params['id'] );
90
91 if ( filter_var( $this->params['id'], FILTER_VALIDATE_INT ) ) {
92 $customer = new OsCustomerModel( $this->params['id'] );
93 if ( $customer->update_attributes( [ 'is_guest' => true ] ) ) {
94 $status = LATEPOINT_STATUS_SUCCESS;
95 $response_html = __( 'Customer is now allowed to book without password', 'latepoint' );
96 } else {
97 $status = LATEPOINT_STATUS_ERROR;
98 $response_html = $customer->get_error_messages();
99 }
100 } else {
101 $status = LATEPOINT_STATUS_ERROR;
102 $response_html = __( 'Error setting customer as guest', 'latepoint' );
103 }
104
105 if ( $this->get_return_format() == 'json' ) {
106 $this->send_json( array( 'status' => $status, 'message' => $response_html ) );
107 }
108 }
109
110 /*
111 Edit customer
112 */
113
114 public function edit_form() {
115 $this->vars['page_header'] = __( 'Edit Customer', 'latepoint' );
116 $this->vars['breadcrumbs'][] = array( 'label' => __( 'Edit Customer', 'latepoint' ), 'link' => false );
117
118 if ( filter_var( $this->params['id'], FILTER_VALIDATE_INT ) ) {
119 // check if allowed to access
120 $customer = new OsCustomerModel();
121 $customer = $customer->where( [ LATEPOINT_TABLE_CUSTOMERS . '.id' => absint( $this->params['id'] ) ] )->filter_allowed_records()->set_limit( 1 )->get_results_as_models();
122 $this->vars['customer'] = $customer;
123 $this->vars['wp_users_for_select'] = OsWpUserHelper::get_wp_users_for_select();
124 }
125
126 $this->format_render( __FUNCTION__ );
127 }
128
129
130 public function query_for_booking_form() {
131 $query = trim( $this->params['query'] );
132 $sql_query = '%' . $query . '%';
133 $query = $this->params['query'];
134 $customers = new OsCustomerModel();
135 $this->vars['query'] = $query;
136 $this->vars['customers'] = $customers->where( array(
137 'OR' => array(
138 'CONCAT (first_name, " ", last_name) LIKE ' => $sql_query,
139 'email LIKE' => $sql_query,
140 'phone LIKE' => $sql_query
141 )
142 ) )->set_limit( 20 )->order_by( 'first_name asc, last_name asc' )->get_results_as_models();
143
144 $this->format_render( __FUNCTION__ );
145 }
146
147
148 /*
149 Create customer
150 */
151
152 public function create() {
153 $this->check_nonce( 'new_customer' );
154 $customer = new OsCustomerModel();
155 // Security fix: Prevent mass assignment of wordpress_user_id by non-admin users.
156 // Use admin scope if user is authenticated as admin, otherwise restrict to public fields.
157 $customer->set_data( $this->params['customer'], OsAuthHelper::is_admin_logged_in() ? LATEPOINT_PARAMS_SCOPE_ADMIN : LATEPOINT_PARAMS_SCOPE_PUBLIC );
158 if ( $customer->save() ) {
159 // translators: %s is the html of a customer edit link
160 $response_html = sprintf( __( 'Customer Created ID: %s', 'latepoint' ), '<span class="os-notification-link" ' . OsCustomerHelper::quick_customer_btn_html( $customer->id ) . '>' . $customer->id . '</span>' );
161 $status = LATEPOINT_STATUS_SUCCESS;
162 do_action( 'latepoint_customer_created', $customer );
163 } else {
164 $response_html = $customer->get_error_messages();
165 $status = LATEPOINT_STATUS_ERROR;
166 }
167 if ( $this->get_return_format() == 'json' ) {
168 $this->send_json( array( 'status' => $status, 'message' => $response_html ) );
169 }
170 }
171
172
173 /*
174 Update customer
175 */
176
177 public function update() {
178 if ( isset( $this->params['customer']['id'] ) && filter_var( $this->params['customer']['id'], FILTER_VALIDATE_INT ) ) {
179 $this->check_nonce( 'edit_customer_' . $this->params['customer']['id'] );
180 $customer = new OsCustomerModel( $this->params['customer']['id'] );
181 if ( ! $customer || ! OsRolesHelper::can_user_make_action_on_model_record( $customer, 'edit' ) ) {
182 $response_html = __( 'Access Restricted', 'latepoint' );
183 $status = LATEPOINT_STATUS_ERROR;
184 } else {
185 $old_customer_data = $customer->get_data_vars();
186 // Security fix: Prevent mass assignment of wordpress_user_id by non-admin users.
187 // Use admin scope if user is authenticated as admin, otherwise restrict to public fields.
188 $customer->set_data( $this->params['customer'], OsAuthHelper::is_admin_logged_in() ? LATEPOINT_PARAMS_SCOPE_ADMIN : LATEPOINT_PARAMS_SCOPE_PUBLIC );
189 if ( $customer->save() ) {
190 // translators: %s is the html of a customer edit link
191 $response_html = sprintf( __( 'Customer Updated ID: %s', 'latepoint' ), '<span class="os-notification-link" ' . OsCustomerHelper::quick_customer_btn_html( $customer->id ) . '>' . $customer->id . '</span>' );
192 $status = LATEPOINT_STATUS_SUCCESS;
193 do_action( 'latepoint_customer_updated', $customer, $old_customer_data );
194 } else {
195 $response_html = $customer->get_error_messages();
196 $status = LATEPOINT_STATUS_ERROR;
197 }
198 }
199 } else {
200 $response_html = __( 'Invalid customer ID', 'latepoint' );
201 $status = LATEPOINT_STATUS_ERROR;
202 }
203 if ( $this->get_return_format() == 'json' ) {
204 $this->send_json( array( 'status' => $status, 'message' => $response_html ) );
205 }
206 }
207
208 public function mini_profile() {
209 if ( filter_var( $this->params['customer_id'], FILTER_VALIDATE_INT ) ) {
210 $customer = new OsCustomerModel( $this->params['customer_id'] );
211 $this->vars['upcoming_booking'] = $customer->get_future_bookings( 1, true );
212 $this->vars['customer'] = $customer;
213
214
215 $pie_labels = [];
216 $pie_colors = [];
217 $pie_values = [];
218 $pie_chart_data = OsBookingHelper::get_stat( 'bookings', [ 'group_by' => 'status', 'customer_id' => $customer->id ] );
219 $colors = [ '#2752E4', '#C066F1', '#26B7DD', '#E8C634', '#19CED6', '#2FEAA3', '#252a3e', '#8d87a5', '#b9b784' ];
220 $status_colors = [
221 LATEPOINT_BOOKING_STATUS_APPROVED => '#35d893',
222 LATEPOINT_BOOKING_STATUS_PENDING => '#e6b935',
223 LATEPOINT_BOOKING_STATUS_PAYMENT_PENDING => '#4ca4ef',
224 LATEPOINT_BOOKING_STATUS_CANCELLED => '#f1585d'
225 ];
226 $i = 0;
227 foreach ( $pie_chart_data as $pie_data ) {
228 $pie_labels[] = $pie_data['status'];
229 $pie_colors[] = isset( $status_colors[ $pie_data['status'] ] ) ? $status_colors[ $pie_data['status'] ] : $colors[ $i ];
230 $pie_values[] = $pie_data['stat'];
231 $i ++;
232 }
233
234 $this->vars['pie_chart_data'] = [ 'labels' => $pie_labels, 'colors' => $pie_colors, 'values' => $pie_values ];
235
236
237 $this->set_layout( 'none' );
238 $response_html = $this->format_render_return( __FUNCTION__ );
239 } else {
240 $status = LATEPOINT_STATUS_ERROR;
241 $response_html = __( 'Error Accessing Customer', 'latepoint' );
242 }
243
244 if ( $this->get_return_format() == 'json' ) {
245 $this->send_json( array( 'status' => $status, 'message' => $response_html ) );
246 }
247 }
248
249 public function connect_all_to_wp_users() {
250 // CSRF protection
251 $this->check_nonce( 'connect_all_customers_to_wp_users' );
252
253 $customers = new OsCustomerModel();
254 $customers = $customers->where( [ 'wordpress_user_id' => [ 'OR' => [ 0, 'IS NULL' ] ] ] )->get_results_as_models();
255 if ( $customers ) {
256 foreach ( $customers as $customer ) {
257 $wp_user_id = OsCustomerHelper::create_wp_user_for_customer( $customer );
258 if ( $wp_user_id ) {
259 //check if wp user already connected to another customer
260 $connected_customer = new OsCustomerModel();
261 $connected_customer = $connected_customer->where( [ 'wordpress_user_id' => $wp_user_id ] )->set_limit( 1 )->get_results_as_models();
262 if ( ! $connected_customer ) {
263 $customer->update_attributes( [ 'wordpress_user_id' => $wp_user_id ] );
264 }
265 }
266 }
267 }
268
269 if ( $this->get_return_format() == 'json' ) {
270 $this->send_json( array( 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => __( 'Customers Connected', 'latepoint' ) ) );
271 }
272 }
273
274 public function disconnect_from_wp_user() {
275 // CSRF protection
276 $this->check_nonce( 'disconnect_customer_from_wp_user_' . $this->params['customer_id'] );
277
278 $customer_id = $this->params['customer_id'];
279 $customer = new OsCustomerModel();
280 $customer = $customer->where( [ 'id' => $customer_id ] )->set_limit( 1 )->get_results_as_models();
281 if ( $customer ) {
282 $customer->update_attributes( [ 'wordpress_user_id' => null ] );
283 }
284 if ( $this->get_return_format() == 'json' ) {
285 $this->send_json( array( 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => __( 'Customer Disconnected', 'latepoint' ) ) );
286 }
287 }
288
289 public function connect_to_wp_user() {
290 // CSRF protection
291 $this->check_nonce( 'connect_customer_to_wp_user_' . $this->params['customer_id'] );
292
293 $customer_id = $this->params['customer_id'];
294 $customer = new OsCustomerModel();
295 $customer = $customer->where( [ 'id' => $customer_id ] )->set_limit( 1 )->get_results_as_models();
296 if ( $customer && ! $customer->wordpress_user_id ) {
297 $wp_user = OsCustomerHelper::create_wp_user_for_customer( $customer );
298 }
299 if ( $this->get_return_format() == 'json' ) {
300 $this->send_json( array( 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => __( 'Customer Connected', 'latepoint' ) ) );
301 }
302 }
303
304
305 public function index() {
306 $this->vars['page_header'] = false;
307 $page_number = isset( $this->params['page_number'] ) ? $this->params['page_number'] : 1;
308 $per_page = OsSettingsHelper::get_number_of_records_per_page();
309 $offset = ( $page_number > 1 ) ? ( ( $page_number - 1 ) * $per_page ) : 0;
310
311
312 $customers = new OsCustomerModel();
313 $query_args = [];
314
315 $filter = isset( $this->params['filter'] ) ? $this->params['filter'] : false;
316
317 // TABLE SEARCH FILTERS
318 if ( $filter ) {
319 if ( $filter['id'] ) {
320 $query_args['id'] = $filter['id'];
321 }
322 if ( $filter['registration_date_from'] && $filter['registration_date_to'] ) {
323 $query_args[ LATEPOINT_TABLE_CUSTOMERS . '.created_at >=' ] = $filter['registration_date_from'];
324 $query_args[ LATEPOINT_TABLE_CUSTOMERS . '.created_at <=' ] = $filter['registration_date_to'];
325 }
326 if ( $filter['customer'] ) {
327 $query_args[ 'concat_ws(" ", ' . LATEPOINT_TABLE_CUSTOMERS . '.first_name,' . LATEPOINT_TABLE_CUSTOMERS . '.last_name) LIKE' ] = '%' . $filter['customer'] . '%';
328 $this->vars['customer_name_query'] = $filter['customer'];
329 }
330 if ( $filter['phone'] ) {
331 $query_args['phone LIKE'] = '%' . $filter['phone'] . '%';
332 $this->vars['phone_query'] = $filter['phone'];
333 }
334 if ( $filter['email'] ) {
335 $query_args['email LIKE'] = '%' . $filter['email'] . '%';
336 $this->vars['email_query'] = $filter['email'];
337 }
338 }
339
340
341 // OUTPUT CSV IF REQUESTED
342 if ( isset( $this->params['download'] ) && $this->params['download'] == 'csv' ) {
343 // CSRF protection
344 $this->check_nonce( 'customers_csv_export' );
345
346 $csv_filename = 'customers_' . OsUtilHelper::random_text();
347
348 header( "Content-Type: text/csv" );
349 header( "Content-Disposition: attachment; filename={$csv_filename}.csv" );
350
351 $labels_row = [
352 __( 'ID', 'latepoint' ),
353 __( 'Name', 'latepoint' ),
354 __( 'Phone', 'latepoint' ),
355 __( 'Email', 'latepoint' ),
356 __( 'Total Appointments', 'latepoint' ),
357 __( 'Next Appointment', 'latepoint' ),
358 __( 'Registered On', 'latepoint' )
359 ];
360
361
362 $customers_data = [];
363 $customers_data[] = $labels_row;
364
365
366 $customers_arr = $customers->where( $query_args )->filter_allowed_records()->order_by( 'id desc' )->get_results_as_models();
367 if ( $customers_arr ) {
368 foreach ( $customers_arr as $customer ) {
369 $next_booking = $customer->get_future_bookings( 1, true );
370 $values_row = [
371 $customer->id,
372 $customer->full_name,
373 $customer->phone,
374 $customer->email,
375 $customer->total_bookings_count,
376 $next_booking ? $next_booking->nice_start_datetime : 'n/a',
377 $customer->formatted_created_date()
378 ];
379 $values_row = apply_filters( 'latepoint_customer_row_for_csv_export', $values_row, $customer, $this->params );
380 $customers_data[] = $values_row;
381 }
382 }
383 $customers_data = apply_filters( 'latepoint_customers_data_for_csv_export', $customers_data, $this->params );
384 OsCSVHelper::array_to_csv( $customers_data );
385
386 return;
387 }
388
389 $customers->where( $query_args )->filter_allowed_records();
390 $count_total_customers = clone $customers;
391
392 $total_customers = $count_total_customers->count();
393 $total_pages = ceil( $total_customers / $per_page );
394
395
396 $this->vars['customers_violating_auth_rules'] = OsAuthHelper::count_total_customers_violating_auth_rules();
397
398 $this->vars['customers'] = $customers->set_limit( $per_page )->set_offset( $offset )->order_by( 'id desc' )->get_results_as_models();
399 $this->vars['total_customers'] = $total_customers;
400
401 $this->vars['total_pages'] = ceil( $total_customers / $per_page );
402 $this->vars['per_page'] = $per_page;
403 $this->vars['current_page_number'] = $page_number;
404
405 $this->vars['showing_from'] = ( ( $page_number - 1 ) * $per_page ) ? ( ( $page_number - 1 ) * $per_page ) : 1;
406 $this->vars['showing_to'] = min( $page_number * $per_page, $this->vars['total_customers'] );
407
408 $this->format_render( [ 'json_view_name' => '_table_body', 'html_view_name' => __FUNCTION__ ], [], [
409 'total_pages' => $total_pages,
410 'showing_from' => $this->vars['showing_from'],
411 'showing_to' => $this->vars['showing_to'],
412 'total_records' => $total_customers
413 ] );
414 }
415
416
417 public function import_csv_modal() {
418 $current_step = $this->params['step'] ?? 'upload_csv';
419 $steps = [
420 'upload_csv' => [ 'next_step' => 'mapping' ],
421 'mapping' => [ 'next_step' => 'importing' ],
422 ];
423 $this->vars['current_step'] = $current_step;
424 $this->vars['next_step'] = array_key_exists( $current_step, $steps ) ? $steps[ $current_step ]['next_step'] : 'upload_csv';
425 $this->format_render( __FUNCTION__ );
426 }
427
428 public function import_load_step() {
429 $this->check_nonce( 'import_customers_csv' );
430 $current_step = $this->params['step'] ?? 'upload_csv';
431 $status = LATEPOINT_STATUS_SUCCESS;
432
433 try {
434 switch ( $current_step ) {
435 case 'upload_csv':
436 $response_html = $this->_handle_upload_step();
437 break;
438
439 case 'mapping':
440 $response_html = $this->_handle_mapping_step();
441 break;
442
443 case 'confirmation':
444 $response_html = $this->_handleConfirmationStep();
445 break;
446
447 default:
448 throw new Exception( 'Invalid step provided' );
449 }
450 } catch ( Exception $e ) {
451 $response_html = $e->getMessage();
452 $status = LATEPOINT_STATUS_ERROR;
453 }
454
455 $this->send_json( array( 'status' => $status, 'message' => $response_html ) );
456 }
457
458 private function _handle_upload_step(): string {
459 $file_path = OsCSVHelper::upload_csv_file( $this->files, 'latepoint_customers_csv' );
460 $csv_data = OsCSVHelper::get_csv_data( $file_path, 1 );
461
462 return $this->render( $this->views_folder . 'import_steps/step_mapping.php', 'none', [
463 'csv_data' => $csv_data,
464 ] );
465 }
466
467 private function _handle_mapping_step(): string {
468 $columnMapping = $this->params['latepoint_column_mapping'] ?? [];
469
470 if ( ! OsCustomerImportHelper::validate_import_mapping( $columnMapping ) ) {
471 throw new Exception( esc_html__( 'Email field is required', 'latepoint' ) );
472 }
473
474 $csv_data = OsCSVHelper::get_csv_data( OsCustomerImportHelper::get_import_tmp_filepath() );
475 $validation_result = OsCustomerImportHelper::validate_csv_data( $csv_data, $columnMapping );
476
477 return $this->render( $this->views_folder . 'import_steps/step_confirmation.php', 'none', [
478 'conflicts' => $validation_result['conflicts'],
479 'can_be_imported' => $validation_result['importable_count'],
480 'latepoint_column_mapping' => $columnMapping
481 ] );
482 }
483
484
485 private function _handleConfirmationStep(): string {
486 $update_existing_customers = ! empty( $this->params['latepoint_update_customers_acknowledgement'] ) && OsUtilHelper::is_on( $this->params['latepoint_update_customers_acknowledgement'] );
487 $column_mapping = ! empty( $this->params['latepoint_column_mapping'] ) ? json_decode( $this->params['latepoint_column_mapping'], true ) : [];
488
489 $file_path = OsCustomerImportHelper::get_import_tmp_filepath();
490 $csv_data = OsCSVHelper::get_csv_data( $file_path );
491
492 $importResult = OsCustomerImportHelper::import_customers( $csv_data, $column_mapping, $update_existing_customers );
493
494 // Cleanup after successful import
495 OsCustomerImportHelper::cleanup_stored_file();
496
497 return $this->render( $this->views_folder . 'import_steps/step_done.php', 'none', [
498 'skipped_records' => $importResult['skipped_count'],
499 'updated_records' => $importResult['updated_count'],
500 ] );
501 }
502
503
504 }
505
506
507 endif;