[Back] <?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;