PluginProbe ʕ •ᴥ•ʔ
LatePoint – Calendar Booking Plugin for Appointments and Events / 5.2.9
LatePoint – Calendar Booking Plugin for Appointments and Events v5.2.9
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 5 months ago auth_controller.php 9 months ago booking_form_settings_controller.php 4 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 9 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 1 year ago manage_booking_by_key_controller.php 1 year 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 9 months ago processes_controller.php 1 year ago search_controller.php 1 year ago services_controller.php 9 months ago settings_controller.php 5 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 1 year ago wizard_controller.php 5 months ago
customers_controller.php
494 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 if ( filter_var( $this->params['id'], FILTER_VALIDATE_INT ) ) {
89 $customer = new OsCustomerModel( $this->params['id'] );
90 if ( $customer->update_attributes( [ 'is_guest' => true ] ) ) {
91 $status = LATEPOINT_STATUS_SUCCESS;
92 $response_html = __( 'Customer is now allowed to book without password', 'latepoint' );
93 } else {
94 $status = LATEPOINT_STATUS_ERROR;
95 $response_html = $customer->get_error_messages();
96 }
97 } else {
98 $status = LATEPOINT_STATUS_ERROR;
99 $response_html = __( 'Error setting customer as guest', 'latepoint' );
100 }
101
102 if ( $this->get_return_format() == 'json' ) {
103 $this->send_json( array( 'status' => $status, 'message' => $response_html ) );
104 }
105 }
106
107 /*
108 Edit customer
109 */
110
111 public function edit_form() {
112 $this->vars['page_header'] = __( 'Edit Customer', 'latepoint' );
113 $this->vars['breadcrumbs'][] = array( 'label' => __( 'Edit Customer', 'latepoint' ), 'link' => false );
114
115 if ( filter_var( $this->params['id'], FILTER_VALIDATE_INT ) ) {
116 // check if allowed to access
117 $customer = new OsCustomerModel();
118 $customer = $customer->where( [ LATEPOINT_TABLE_CUSTOMERS . '.id' => absint( $this->params['id'] ) ] )->filter_allowed_records()->set_limit( 1 )->get_results_as_models();
119 $this->vars['customer'] = $customer;
120 $this->vars['wp_users_for_select'] = OsWpUserHelper::get_wp_users_for_select();
121 }
122
123 $this->format_render( __FUNCTION__ );
124 }
125
126
127 public function query_for_booking_form() {
128 $query = trim( $this->params['query'] );
129 $sql_query = '%' . $query . '%';
130 $query = $this->params['query'];
131 $customers = new OsCustomerModel();
132 $this->vars['query'] = $query;
133 $this->vars['customers'] = $customers->where( array(
134 'OR' => array(
135 'CONCAT (first_name, " ", last_name) LIKE ' => $sql_query,
136 'email LIKE' => $sql_query,
137 'phone LIKE' => $sql_query
138 )
139 ) )->set_limit( 20 )->order_by( 'first_name asc, last_name asc' )->get_results_as_models();
140
141 $this->format_render( __FUNCTION__ );
142 }
143
144
145 /*
146 Create customer
147 */
148
149 public function create() {
150 $this->check_nonce( 'new_customer' );
151 $customer = new OsCustomerModel();
152 // Security fix: Prevent mass assignment of wordpress_user_id by non-admin users.
153 // Use admin scope if user is authenticated as admin, otherwise restrict to public fields.
154 $customer->set_data( $this->params['customer'], OsAuthHelper::is_admin_logged_in() ? LATEPOINT_PARAMS_SCOPE_ADMIN : LATEPOINT_PARAMS_SCOPE_PUBLIC );
155 if ( $customer->save() ) {
156 // translators: %s is the html of a customer edit link
157 $response_html = sprintf( __( 'Customer Created ID: %s', 'latepoint' ), '<span class="os-notification-link" ' . OsCustomerHelper::quick_customer_btn_html( $customer->id ) . '>' . $customer->id . '</span>' );
158 $status = LATEPOINT_STATUS_SUCCESS;
159 do_action( 'latepoint_customer_created', $customer );
160 } else {
161 $response_html = $customer->get_error_messages();
162 $status = LATEPOINT_STATUS_ERROR;
163 }
164 if ( $this->get_return_format() == 'json' ) {
165 $this->send_json( array( 'status' => $status, 'message' => $response_html ) );
166 }
167 }
168
169
170 /*
171 Update customer
172 */
173
174 public function update() {
175 if ( isset( $this->params['customer']['id'] ) && filter_var( $this->params['customer']['id'], FILTER_VALIDATE_INT ) ) {
176 $this->check_nonce( 'edit_customer_' . $this->params['customer']['id'] );
177 $customer = new OsCustomerModel( $this->params['customer']['id'] );
178 if ( ! $customer || ! OsRolesHelper::can_user_make_action_on_model_record( $customer, 'edit' ) ) {
179 $response_html = __( 'Access Restricted', 'latepoint' );
180 $status = LATEPOINT_STATUS_ERROR;
181 } else {
182 $old_customer_data = $customer->get_data_vars();
183 // Security fix: Prevent mass assignment of wordpress_user_id by non-admin users.
184 // Use admin scope if user is authenticated as admin, otherwise restrict to public fields.
185 $customer->set_data( $this->params['customer'], OsAuthHelper::is_admin_logged_in() ? LATEPOINT_PARAMS_SCOPE_ADMIN : LATEPOINT_PARAMS_SCOPE_PUBLIC );
186 if ( $customer->save() ) {
187 // translators: %s is the html of a customer edit link
188 $response_html = sprintf( __( 'Customer Updated ID: %s', 'latepoint' ), '<span class="os-notification-link" ' . OsCustomerHelper::quick_customer_btn_html( $customer->id ) . '>' . $customer->id . '</span>' );
189 $status = LATEPOINT_STATUS_SUCCESS;
190 do_action( 'latepoint_customer_updated', $customer, $old_customer_data );
191 } else {
192 $response_html = $customer->get_error_messages();
193 $status = LATEPOINT_STATUS_ERROR;
194 }
195 }
196 } else {
197 $response_html = __( 'Invalid customer ID', 'latepoint' );
198 $status = LATEPOINT_STATUS_ERROR;
199 }
200 if ( $this->get_return_format() == 'json' ) {
201 $this->send_json( array( 'status' => $status, 'message' => $response_html ) );
202 }
203 }
204
205 public function mini_profile() {
206 if ( filter_var( $this->params['customer_id'], FILTER_VALIDATE_INT ) ) {
207 $customer = new OsCustomerModel( $this->params['customer_id'] );
208 $this->vars['upcoming_booking'] = $customer->get_future_bookings( 1, true );
209 $this->vars['customer'] = $customer;
210
211
212 $pie_labels = [];
213 $pie_colors = [];
214 $pie_values = [];
215 $pie_chart_data = OsBookingHelper::get_stat( 'bookings', [ 'group_by' => 'status', 'customer_id' => $customer->id ] );
216 $colors = [ '#2752E4', '#C066F1', '#26B7DD', '#E8C634', '#19CED6', '#2FEAA3', '#252a3e', '#8d87a5', '#b9b784' ];
217 $status_colors = [
218 LATEPOINT_BOOKING_STATUS_APPROVED => '#35d893',
219 LATEPOINT_BOOKING_STATUS_PENDING => '#e6b935',
220 LATEPOINT_BOOKING_STATUS_PAYMENT_PENDING => '#4ca4ef',
221 LATEPOINT_BOOKING_STATUS_CANCELLED => '#f1585d'
222 ];
223 $i = 0;
224 foreach ( $pie_chart_data as $pie_data ) {
225 $pie_labels[] = $pie_data['status'];
226 $pie_colors[] = isset( $status_colors[ $pie_data['status'] ] ) ? $status_colors[ $pie_data['status'] ] : $colors[ $i ];
227 $pie_values[] = $pie_data['stat'];
228 $i ++;
229 }
230
231 $this->vars['pie_chart_data'] = [ 'labels' => $pie_labels, 'colors' => $pie_colors, 'values' => $pie_values ];
232
233
234 $this->set_layout( 'none' );
235 $response_html = $this->format_render_return( __FUNCTION__ );
236 } else {
237 $status = LATEPOINT_STATUS_ERROR;
238 $response_html = __( 'Error Accessing Customer', 'latepoint' );
239 }
240
241 if ( $this->get_return_format() == 'json' ) {
242 $this->send_json( array( 'status' => $status, 'message' => $response_html ) );
243 }
244 }
245
246 public function connect_all_to_wp_users() {
247 $customers = new OsCustomerModel();
248 $customers = $customers->where( [ 'wordpress_user_id' => [ 'OR' => [ 0, 'IS NULL' ] ] ] )->get_results_as_models();
249 if ( $customers ) {
250 foreach ( $customers as $customer ) {
251 $wp_user_id = OsCustomerHelper::create_wp_user_for_customer( $customer );
252 if ( $wp_user_id ) {
253 //check if wp user already connected to another customer
254 $connected_customer = new OsCustomerModel();
255 $connected_customer = $connected_customer->where( [ 'wordpress_user_id' => $wp_user_id ] )->set_limit( 1 )->get_results_as_models();
256 if ( ! $connected_customer ) {
257 $customer->update_attributes( [ 'wordpress_user_id' => $wp_user_id ] );
258 }
259 }
260 }
261 }
262
263 if ( $this->get_return_format() == 'json' ) {
264 $this->send_json( array( 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => __( 'Customers Connected', 'latepoint' ) ) );
265 }
266 }
267
268 public function disconnect_from_wp_user() {
269 $customer_id = $this->params['customer_id'];
270 $customer = new OsCustomerModel();
271 $customer = $customer->where( [ 'id' => $customer_id ] )->set_limit( 1 )->get_results_as_models();
272 if ( $customer ) {
273 $customer->update_attributes( [ 'wordpress_user_id' => null ] );
274 }
275 if ( $this->get_return_format() == 'json' ) {
276 $this->send_json( array( 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => __( 'Customer Disconnected', 'latepoint' ) ) );
277 }
278 }
279
280 public function connect_to_wp_user() {
281 $customer_id = $this->params['customer_id'];
282 $customer = new OsCustomerModel();
283 $customer = $customer->where( [ 'id' => $customer_id ] )->set_limit( 1 )->get_results_as_models();
284 if ( $customer && ! $customer->wordpress_user_id ) {
285 $wp_user = OsCustomerHelper::create_wp_user_for_customer( $customer );
286 }
287 if ( $this->get_return_format() == 'json' ) {
288 $this->send_json( array( 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => __( 'Customer Connected', 'latepoint' ) ) );
289 }
290 }
291
292
293 public function index() {
294
295
296 $this->vars['page_header'] = false;
297 $page_number = isset( $this->params['page_number'] ) ? $this->params['page_number'] : 1;
298 $per_page = OsSettingsHelper::get_number_of_records_per_page();
299 $offset = ( $page_number > 1 ) ? ( ( $page_number - 1 ) * $per_page ) : 0;
300
301
302 $customers = new OsCustomerModel();
303 $query_args = [];
304
305 $filter = isset( $this->params['filter'] ) ? $this->params['filter'] : false;
306
307 // TABLE SEARCH FILTERS
308 if ( $filter ) {
309 if ( $filter['id'] ) {
310 $query_args['id'] = $filter['id'];
311 }
312 if ( $filter['registration_date_from'] && $filter['registration_date_to'] ) {
313 $query_args[ LATEPOINT_TABLE_CUSTOMERS . '.created_at >=' ] = $filter['registration_date_from'];
314 $query_args[ LATEPOINT_TABLE_CUSTOMERS . '.created_at <=' ] = $filter['registration_date_to'];
315 }
316 if ( $filter['customer'] ) {
317 $query_args[ 'concat_ws(" ", ' . LATEPOINT_TABLE_CUSTOMERS . '.first_name,' . LATEPOINT_TABLE_CUSTOMERS . '.last_name) LIKE' ] = '%' . $filter['customer'] . '%';
318 $this->vars['customer_name_query'] = $filter['customer'];
319 }
320 if ( $filter['phone'] ) {
321 $query_args['phone LIKE'] = '%' . $filter['phone'] . '%';
322 $this->vars['phone_query'] = $filter['phone'];
323 }
324 if ( $filter['email'] ) {
325 $query_args['email LIKE'] = '%' . $filter['email'] . '%';
326 $this->vars['email_query'] = $filter['email'];
327 }
328 }
329
330
331 // OUTPUT CSV IF REQUESTED
332 if ( isset( $this->params['download'] ) && $this->params['download'] == 'csv' ) {
333 $csv_filename = 'customers_' . OsUtilHelper::random_text();
334
335 header( "Content-Type: text/csv" );
336 header( "Content-Disposition: attachment; filename={$csv_filename}.csv" );
337
338 $labels_row = [
339 __( 'ID', 'latepoint' ),
340 __( 'Name', 'latepoint' ),
341 __( 'Phone', 'latepoint' ),
342 __( 'Email', 'latepoint' ),
343 __( 'Total Appointments', 'latepoint' ),
344 __( 'Next Appointment', 'latepoint' ),
345 __( 'Registered On', 'latepoint' )
346 ];
347
348
349 $customers_data = [];
350 $customers_data[] = $labels_row;
351
352
353 $customers_arr = $customers->where( $query_args )->filter_allowed_records()->order_by( 'id desc' )->get_results_as_models();
354 if ( $customers_arr ) {
355 foreach ( $customers_arr as $customer ) {
356 $next_booking = $customer->get_future_bookings( 1, true );
357 $values_row = [
358 $customer->id,
359 $customer->full_name,
360 $customer->phone,
361 $customer->email,
362 $customer->total_bookings_count,
363 $next_booking ? $next_booking->nice_start_datetime : 'n/a',
364 $customer->formatted_created_date()
365 ];
366 $values_row = apply_filters( 'latepoint_customer_row_for_csv_export', $values_row, $customer, $this->params );
367 $customers_data[] = $values_row;
368 }
369 }
370 $customers_data = apply_filters( 'latepoint_customers_data_for_csv_export', $customers_data, $this->params );
371 OsCSVHelper::array_to_csv( $customers_data );
372
373 return;
374 }
375
376 $customers->where( $query_args )->filter_allowed_records();
377 $count_total_customers = clone $customers;
378
379 $total_customers = $count_total_customers->count();
380 $total_pages = ceil( $total_customers / $per_page );
381
382
383 $this->vars['customers_violating_auth_rules'] = OsAuthHelper::count_total_customers_violating_auth_rules();
384
385 $this->vars['customers'] = $customers->set_limit( $per_page )->set_offset( $offset )->order_by( 'id desc' )->get_results_as_models();
386 $this->vars['total_customers'] = $total_customers;
387
388 $this->vars['total_pages'] = ceil( $total_customers / $per_page );
389 $this->vars['per_page'] = $per_page;
390 $this->vars['current_page_number'] = $page_number;
391
392 $this->vars['showing_from'] = ( ( $page_number - 1 ) * $per_page ) ? ( ( $page_number - 1 ) * $per_page ) : 1;
393 $this->vars['showing_to'] = min( $page_number * $per_page, $this->vars['total_customers'] );
394
395 $this->format_render( [ 'json_view_name' => '_table_body', 'html_view_name' => __FUNCTION__ ], [], [
396 'total_pages' => $total_pages,
397 'showing_from' => $this->vars['showing_from'],
398 'showing_to' => $this->vars['showing_to'],
399 'total_records' => $total_customers
400 ] );
401 }
402
403
404 public function import_csv_modal() {
405 $current_step = $this->params['step'] ?? 'upload_csv';
406 $steps = [
407 'upload_csv' => [ 'next_step' => 'mapping' ],
408 'mapping' => [ 'next_step' => 'importing' ],
409 ];
410 $this->vars['current_step'] = $current_step;
411 $this->vars['next_step'] = array_key_exists( $current_step, $steps ) ? $steps[ $current_step ]['next_step'] : 'upload_csv';
412 $this->format_render( __FUNCTION__ );
413 }
414
415 public function import_load_step() {
416 $this->check_nonce( 'import_customers_csv' );
417 $current_step = $this->params['step'] ?? 'upload_csv';
418 $status = LATEPOINT_STATUS_SUCCESS;
419
420 try {
421 switch ( $current_step ) {
422 case 'upload_csv':
423 $response_html = $this->_handle_upload_step();
424 break;
425
426 case 'mapping':
427 $response_html = $this->_handle_mapping_step();
428 break;
429
430 case 'confirmation':
431 $response_html = $this->_handleConfirmationStep();
432 break;
433
434 default:
435 throw new Exception( 'Invalid step provided' );
436 }
437 } catch ( Exception $e ) {
438 $response_html = $e->getMessage();
439 $status = LATEPOINT_STATUS_ERROR;
440 }
441
442 $this->send_json( array( 'status' => $status, 'message' => $response_html ) );
443 }
444
445 private function _handle_upload_step(): string {
446 $file_path = OsCSVHelper::upload_csv_file( $this->files, 'latepoint_customers_csv' );
447 $csv_data = OsCSVHelper::get_csv_data( $file_path, 1 );
448
449 return $this->render( $this->views_folder . 'import_steps/step_mapping.php', 'none', [
450 'csv_data' => $csv_data,
451 ] );
452 }
453
454 private function _handle_mapping_step(): string {
455 $columnMapping = $this->params['latepoint_column_mapping'] ?? [];
456
457 if ( ! OsCustomerImportHelper::validate_import_mapping( $columnMapping ) ) {
458 throw new Exception( esc_html__( 'Email field is required', 'latepoint' ) );
459 }
460
461 $csv_data = OsCSVHelper::get_csv_data( OsCustomerImportHelper::get_import_tmp_filepath() );
462 $validation_result = OsCustomerImportHelper::validate_csv_data( $csv_data, $columnMapping );
463
464 return $this->render( $this->views_folder . 'import_steps/step_confirmation.php', 'none', [
465 'conflicts' => $validation_result['conflicts'],
466 'can_be_imported' => $validation_result['importable_count'],
467 'latepoint_column_mapping' => $columnMapping
468 ] );
469 }
470
471
472 private function _handleConfirmationStep(): string {
473 $update_existing_customers = ! empty( $this->params['latepoint_update_customers_acknowledgement'] ) && OsUtilHelper::is_on( $this->params['latepoint_update_customers_acknowledgement'] );
474 $column_mapping = ! empty( $this->params['latepoint_column_mapping'] ) ? json_decode( $this->params['latepoint_column_mapping'], true ) : [];
475
476 $file_path = OsCustomerImportHelper::get_import_tmp_filepath();
477 $csv_data = OsCSVHelper::get_csv_data( $file_path );
478
479 $importResult = OsCustomerImportHelper::import_customers( $csv_data, $column_mapping, $update_existing_customers );
480
481 // Cleanup after successful import
482 OsCustomerImportHelper::cleanup_stored_file();
483
484 return $this->render( $this->views_folder . 'import_steps/step_done.php', 'none', [
485 'skipped_records' => $importResult['skipped_count'],
486 'updated_records' => $importResult['updated_count'],
487 ] );
488 }
489
490
491 }
492
493
494 endif;