<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } if ( ! class_exists( 'OsCustomersController' ) ) : class OsCustomersController extends OsController { function __construct(){ parent::__construct(); $this->views_folder = LATEPOINT_VIEWS_ABSPATH . 'customers/'; $this->vars['page_header'] = OsMenuHelper::get_menu_items_by_id('customers'); $this->vars['breadcrumbs'][] = array('label' => __('Customers', 'latepoint'), 'link' => OsRouterHelper::build_link(OsRouterHelper::build_route_name('customers', 'index') ) ); } public function destroy(){ if(filter_var($this->params['id'], FILTER_VALIDATE_INT)){ $this->check_nonce('destroy_customer_'.$this->params['id']); $customer = new OsCustomerModel($this->params['id']); if($customer->delete()){ $status = LATEPOINT_STATUS_SUCCESS; $response_html = __('Customer Removed', 'latepoint'); }else{ $status = LATEPOINT_STATUS_ERROR; $response_html = __('Error Removing Customer', 'latepoint'); } }else{ $status = LATEPOINT_STATUS_ERROR; $response_html = __('Error Removing Customer', 'latepoint'); } if($this->get_return_format() == 'json'){ $this->send_json(array('status' => $status, 'message' => $response_html)); } } public function view_customer_log(){ $activities = new OsActivityModel(); $activities = $activities->where(['customer_id' => absint($this->params['customer_id'])])->order_by('id desc')->get_results_as_models(); $customer = new OsCustomerModel($this->params['customer_id']); $this->vars['customer'] = $customer; $this->vars['activities'] = $activities; $this->format_render(__FUNCTION__); } public function quick_new(){ $customer = new OsCustomerModel(); $this->vars['customer'] = $customer; $this->format_render('quick_edit'); } public function quick_edit(){ if(!filter_var($this->params['customer_id'], FILTER_VALIDATE_INT)) $this->access_not_allowed(); $customer = new OsCustomerModel($this->params['customer_id']); $this->vars['customer'] = $customer; $this->format_render(__FUNCTION__); } public function inline_edit_form(){ $selected_customer = new OsCustomerModel(); if(isset($this->params['customer_id'])){ $selected_customer->load_by_id($this->params['customer_id']); } $this->vars['default_fields_for_customer'] = OsSettingsHelper::get_default_fields_for_customer(); $this->vars['selected_customer'] = $selected_customer; $this->format_render(__FUNCTION__); } public function set_as_guest(){ if(filter_var($this->params['id'], FILTER_VALIDATE_INT)){ $customer = new OsCustomerModel($this->params['id']); if($customer->update_attributes(['is_guest' => true])){ $status = LATEPOINT_STATUS_SUCCESS; $response_html = __('Customer is now allowed to book without password', 'latepoint'); }else{ $status = LATEPOINT_STATUS_ERROR; $response_html = $customer->get_error_messages(); } }else{ $status = LATEPOINT_STATUS_ERROR; $response_html = __('Error setting customer as guest', 'latepoint'); } if($this->get_return_format() == 'json'){ $this->send_json(array('status' => $status, 'message' => $response_html)); } } /* Edit customer */ public function edit_form(){ $this->vars['page_header'] = __('Edit Customer', 'latepoint'); $this->vars['breadcrumbs'][] = array('label' => __('Edit Customer', 'latepoint'), 'link' => false ); if(filter_var($this->params['id'], FILTER_VALIDATE_INT)){ // check if allowed to access $customer = new OsCustomerModel(); $customer = $customer->where([LATEPOINT_TABLE_CUSTOMERS.'.id' => absint($this->params['id'])])->filter_allowed_records()->set_limit(1)->get_results_as_models(); $this->vars['customer'] = $customer; $this->vars['wp_users_for_select'] = OsWpUserHelper::get_wp_users_for_select(); } $this->format_render(__FUNCTION__); } public function query_for_booking_form(){ $query = trim($this->params['query']); $sql_query = '%'.$query.'%'; $query = $this->params['query']; $customers = new OsCustomerModel(); $this->vars['query'] = $query; $this->vars['customers'] = $customers->where(array('OR' => array('CONCAT (first_name, " ", last_name) LIKE ' => $sql_query, 'email LIKE' => $sql_query, 'phone LIKE' => $sql_query)))->set_limit(20)->order_by('first_name asc, last_name asc')->get_results_as_models(); $this->format_render(__FUNCTION__); } /* Create customer */ public function create(){ $this->check_nonce('new_customer'); $customer = new OsCustomerModel(); $customer->set_data($this->params['customer']); if($customer->save()){ // translators: %s is the html of a customer edit link $response_html = sprintf(__('Customer Created ID: %s', 'latepoint'), '<span class="os-notification-link" '.OsCustomerHelper::quick_customer_btn_html($customer->id).'>'.$customer->id.'</span>'); $status = LATEPOINT_STATUS_SUCCESS; do_action('latepoint_customer_created', $customer); }else{ $response_html = $customer->get_error_messages(); $status = LATEPOINT_STATUS_ERROR; } if($this->get_return_format() == 'json'){ $this->send_json(array('status' => $status, 'message' => $response_html)); } } /* Update customer */ public function update(){ if(isset($this->params['customer']['id']) && filter_var($this->params['customer']['id'], FILTER_VALIDATE_INT)){ $this->check_nonce('edit_customer_'.$this->params['customer']['id']); $customer = new OsCustomerModel($this->params['customer']['id']); if(!$customer || !OsRolesHelper::can_user_make_action_on_model_record($customer, 'edit')){ $response_html = __('Access Restricted', 'latepoint'); $status = LATEPOINT_STATUS_ERROR; }else{ $old_customer_data = $customer->get_data_vars(); $customer->set_data($this->params['customer']); if($customer->save()){ // translators: %s is the html of a customer edit link $response_html = sprintf(__('Customer Updated ID: %s', 'latepoint'), '<span class="os-notification-link" '.OsCustomerHelper::quick_customer_btn_html($customer->id).'>'.$customer->id.'</span>'); $status = LATEPOINT_STATUS_SUCCESS; do_action('latepoint_customer_updated', $customer, $old_customer_data); }else{ $response_html = $customer->get_error_messages(); $status = LATEPOINT_STATUS_ERROR; } } }else{ $response_html = __('Invalid customer ID', 'latepoint'); $status = LATEPOINT_STATUS_ERROR; } if($this->get_return_format() == 'json'){ $this->send_json(array('status' => $status, 'message' => $response_html)); } } public function mini_profile(){ if(filter_var($this->params['customer_id'], FILTER_VALIDATE_INT)){ $customer = new OsCustomerModel($this->params['customer_id']); $this->vars['upcoming_booking'] = $customer->get_future_bookings(1, true); $this->vars['customer'] = $customer; $pie_labels = []; $pie_colors = []; $pie_values = []; $pie_chart_data = OsBookingHelper::get_stat('bookings', ['group_by' => 'status', 'customer_id' => $customer->id]); $colors = ['#2752E4', '#C066F1', '#26B7DD', '#E8C634', '#19CED6', '#2FEAA3', '#252a3e', '#8d87a5', '#b9b784']; $status_colors = [ LATEPOINT_BOOKING_STATUS_APPROVED => '#35d893', LATEPOINT_BOOKING_STATUS_PENDING => '#e6b935', LATEPOINT_BOOKING_STATUS_PAYMENT_PENDING => '#4ca4ef', LATEPOINT_BOOKING_STATUS_CANCELLED => '#f1585d' ]; $i = 0; foreach($pie_chart_data as $pie_data){ $pie_labels[] = $pie_data['status']; $pie_colors[] = isset($status_colors[$pie_data['status']]) ? $status_colors[$pie_data['status']] : $colors[$i]; $pie_values[] = $pie_data['stat']; $i++; } $this->vars['pie_chart_data'] = ['labels' => $pie_labels, 'colors' => $pie_colors, 'values' => $pie_values]; $this->set_layout('none'); $response_html = $this->format_render_return(__FUNCTION__); }else{ $status = LATEPOINT_STATUS_ERROR; $response_html = __('Error Accessing Customer', 'latepoint'); } if($this->get_return_format() == 'json'){ $this->send_json(array('status' => $status, 'message' => $response_html)); } } public function connect_all_to_wp_users(){ $customers = new OsCustomerModel(); $customers = $customers->where(['wordpress_user_id' => ['OR' => [0, 'IS NULL']]])->get_results_as_models(); if($customers){ foreach($customers as $customer){ $wp_user_id = OsCustomerHelper::create_wp_user_for_customer($customer); if($wp_user_id) { //check if wp user already connected to another customer $connected_customer = new OsCustomerModel(); $connected_customer = $connected_customer->where( [ 'wordpress_user_id' => $wp_user_id ] )->set_limit( 1 )->get_results_as_models(); if ( !$connected_customer ) { $customer->update_attributes( [ 'wordpress_user_id' => $wp_user_id ] ); } } } } if($this->get_return_format() == 'json'){ $this->send_json(array('status' => LATEPOINT_STATUS_SUCCESS, 'message' => __('Customers Connected', 'latepoint'))); } } public function disconnect_from_wp_user(){ $customer_id = $this->params['customer_id']; $customer = new OsCustomerModel(); $customer = $customer->where(['id' => $customer_id])->set_limit(1)->get_results_as_models(); if($customer){ $customer->update_attributes(['wordpress_user_id' => NULL]); } if($this->get_return_format() == 'json'){ $this->send_json(array('status' => LATEPOINT_STATUS_SUCCESS, 'message' => __('Customer Disconnected', 'latepoint'))); } } public function connect_to_wp_user(){ $customer_id = $this->params['customer_id']; $customer = new OsCustomerModel(); $customer = $customer->where(['id' => $customer_id])->set_limit(1)->get_results_as_models(); if($customer && !$customer->wordpress_user_id){ $wp_user = OsCustomerHelper::create_wp_user_for_customer($customer); } if($this->get_return_format() == 'json'){ $this->send_json(array('status' => LATEPOINT_STATUS_SUCCESS, 'message' => __('Customer Connected', 'latepoint'))); } } public function index(){ $this->vars['page_header'] = false; $page_number = isset($this->params['page_number']) ? $this->params['page_number'] : 1; $per_page = OsSettingsHelper::get_number_of_records_per_page(); $offset = ($page_number > 1) ? (($page_number - 1) * $per_page) : 0; $customers = new OsCustomerModel(); $query_args = []; $filter = isset($this->params['filter']) ? $this->params['filter'] : false; // TABLE SEARCH FILTERS if($filter){ if($filter['id']) $query_args['id'] = $filter['id']; if($filter['registration_date_from'] && $filter['registration_date_to']){ $query_args[LATEPOINT_TABLE_CUSTOMERS.'.created_at >='] = $filter['registration_date_from']; $query_args[LATEPOINT_TABLE_CUSTOMERS.'.created_at <='] = $filter['registration_date_to']; } if($filter['customer']){ $query_args['concat_ws(" ", '.LATEPOINT_TABLE_CUSTOMERS.'.first_name,'.LATEPOINT_TABLE_CUSTOMERS.'.last_name) LIKE'] = '%'.$filter['customer'].'%'; $this->vars['customer_name_query'] = $filter['customer']; } if($filter['phone']){ $query_args['phone LIKE'] = '%'.$filter['phone'].'%'; $this->vars['phone_query'] = $filter['phone']; } if($filter['email']){ $query_args['email LIKE'] = '%'.$filter['email'].'%'; $this->vars['email_query'] = $filter['email']; } } // OUTPUT CSV IF REQUESTED if(isset($this->params['download']) && $this->params['download'] == 'csv'){ $csv_filename = 'customers_'.OsUtilHelper::random_text(); header("Content-Type: text/csv"); header("Content-Disposition: attachment; filename={$csv_filename}.csv"); $labels_row = [ __('ID', 'latepoint'), __('Name', 'latepoint'), __('Phone', 'latepoint'), __('Email', 'latepoint'), __('Total Appointments', 'latepoint'), __('Next Appointment', 'latepoint'), __('Registered On', 'latepoint') ]; $customers_data = []; $customers_data[] = $labels_row; $customers_arr = $customers->where($query_args)->filter_allowed_records()->order_by('id desc')->get_results_as_models(); if($customers_arr){ foreach($customers_arr as $customer){ $next_booking = $customer->get_future_bookings(1, true); $values_row = [ $customer->id, $customer->full_name, $customer->phone, $customer->email, $customer->total_bookings_count, $next_booking ? $next_booking->nice_start_datetime : 'n/a', $customer->formatted_created_date()]; $values_row = apply_filters('latepoint_customer_row_for_csv_export', $values_row, $customer, $this->params); $customers_data[] = $values_row; } } $customers_data = apply_filters('latepoint_customers_data_for_csv_export', $customers_data, $this->params); OsCSVHelper::array_to_csv($customers_data); return; } $customers->where($query_args)->filter_allowed_records(); $count_total_customers = clone $customers; $total_customers = $count_total_customers->count(); $total_pages = ceil($total_customers / $per_page); $this->vars['customers'] = $customers->set_limit($per_page)->set_offset($offset)->order_by('id desc')->get_results_as_models(); $this->vars['total_customers'] = $total_customers; $this->vars['total_pages'] = ceil($total_customers / $per_page); $this->vars['per_page'] = $per_page; $this->vars['current_page_number'] = $page_number; $this->vars['showing_from'] = (($page_number - 1) * $per_page) ? (($page_number - 1) * $per_page) : 1; $this->vars['showing_to'] = min($page_number * $per_page, $this->vars['total_customers']); $this->format_render(['json_view_name' => '_table_body', 'html_view_name' => __FUNCTION__], [], ['total_pages' => $total_pages, 'showing_from' => $this->vars['showing_from'], 'showing_to' => $this->vars['showing_to'], 'total_records' => $total_customers]); } public function import_csv_modal( ) { $current_step = $this->params['step'] ?? 'upload_csv'; $steps = [ 'upload_csv' => ['next_step' => 'mapping'], 'mapping' => ['next_step' => 'importing'], ]; $this->vars['current_step'] = $current_step; $this->vars['next_step'] = array_key_exists($current_step, $steps) ? $steps[$current_step]['next_step'] : 'upload_csv'; $this->format_render( __FUNCTION__ ); } public function import_load_step() { $this->check_nonce( 'import_customers_csv' ); $current_step = $this->params['step'] ?? 'upload_csv'; $status = LATEPOINT_STATUS_SUCCESS; try { switch ($current_step) { case 'upload_csv': $response_html = $this->_handle_upload_step(); break; case 'mapping': $response_html = $this->_handle_mapping_step(); break; case 'confirmation': $response_html = $this->_handleConfirmationStep(); break; default: throw new Exception('Invalid step provided'); } } catch (Exception $e) { $response_html = $e->getMessage(); $status = LATEPOINT_STATUS_ERROR; } $this->send_json(array('status' => $status, 'message' => $response_html)); } private function _handle_upload_step(): string { $file_path = OsCSVHelper::upload_csv_file( $this->files, 'latepoint_customers_csv' ); $csv_data = OsCSVHelper::get_csv_data( $file_path, 1 ); return $this->render( $this->views_folder . 'import_steps/step_mapping.php', 'none', [ 'csv_data' => $csv_data, ] ); } private function _handle_mapping_step(): string { $columnMapping = $this->params['latepoint_column_mapping'] ?? []; if (!OsCustomerImportHelper::validate_import_mapping($columnMapping)) { throw new Exception(esc_html__('Email field is required', 'latepoint')); } $csv_data = OsCSVHelper::get_csv_data(OsCustomerImportHelper::get_import_tmp_filepath()); $validation_result = OsCustomerImportHelper::validate_csv_data($csv_data, $columnMapping); return $this->render($this->views_folder . 'import_steps/step_confirmation.php', 'none', [ 'conflicts' => $validation_result['conflicts'], 'can_be_imported' => $validation_result['importable_count'], 'latepoint_column_mapping' => $columnMapping ]); } private function _handleConfirmationStep(): string { $update_existing_customers = !empty($this->params['latepoint_update_customers_acknowledgement']) && OsUtilHelper::is_on($this->params['latepoint_update_customers_acknowledgement']); $column_mapping = !empty($this->params['latepoint_column_mapping']) ? json_decode($this->params['latepoint_column_mapping'], true) : []; $file_path = OsCustomerImportHelper::get_import_tmp_filepath(); $csv_data = OsCSVHelper::get_csv_data($file_path); $importResult = OsCustomerImportHelper::import_customers( $csv_data, $column_mapping, $update_existing_customers ); // Cleanup after successful import OsCustomerImportHelper::cleanup_stored_file(); return $this->render($this->views_folder . 'import_steps/step_done.php', 'none', [ 'skipped_records' => $importResult['skipped_count'], 'updated_records' => $importResult['updated_count'], ]); } } endif;