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