2026-03-27 11:54:51 +00:00

608 lines
18 KiB
PHP

<?php
/**
* Simply Schedule Appointments Support Status Api.
*
* @since 2.1.6
* @package Simply_Schedule_Appointments
*/
/**
* Simply Schedule Appointments Support Status Api.
*
* @since 2.1.6
*/
class SSA_Support_Status_Api extends WP_REST_Controller {
/**
* Parent plugin class
*
* @var class
* @since 1.0.0
*/
protected $plugin = null;
/**
* Constructor
*
* @since 1.0.0
* @param object $plugin Main plugin object.
* @return void
*/
public function __construct( $plugin ) {
$this->plugin = $plugin;
$this->hooks();
}
/**
* Initiate our hooks
*
* @since 1.0.0
* @return void
*/
public function hooks() {
$this->register_routes();
}
/**
* Register the routes for the objects of the controller.
*/
public function register_routes() {
$version = '1';
$namespace = 'ssa/v' . $version;
$base = 'support_status';
register_rest_route( $namespace, '/' . $base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
),
),
) );
register_rest_route( $namespace, '/' . 'support_ticket', array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_support_ticket' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
),
),
) );
register_rest_route( $namespace, '/' . 'support_debug/wp', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_wp_debug_log_content' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
),
),
) );
register_rest_route( $namespace, '/' . 'support_debug/wp/delete', array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'empty_wp_debug_log_content' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
),
),
) );
register_rest_route( $namespace, '/' . 'support_debug/ssa', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_ssa_debug_log_content' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
),
),
) );
register_rest_route( $namespace, '/' . 'support_debug/ssa/delete', array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'empty_ssa_debug_log_content' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
),
),
) );
register_rest_route( $namespace, '/' . 'support_debug/logs', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_debug_log_urls' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
),
),
) );
register_rest_route( $namespace, '/' . 'support/export', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_export_code' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
),
),
) );
register_rest_route( $namespace, '/' . 'support/import', array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'import_data_api' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
),
),
) );
register_rest_route(
$namespace,
'/fetch-guides',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_guides' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
)
);
register_rest_route( $namespace, '/user/check', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'check_user_login_status' ),
'permission_callback' => '__return_true',
) );
}
public function check_user_login_status( $request ) {
$data = array(
'is_user_logged_in' => is_user_logged_in(),
);
$response = array(
'response_code' => 200,
'error' => '',
'data' => $data
);
return new WP_REST_Response( $response, 200 );
}
public function create_support_ticket( $request ) {
// queue the minimal ticket sending here
$params = $request->get_params();
$debug_logs_hash = SSA_Debug::get_site_unique_hash_for_debug_logs();
ssa_schedule_single_action( time() + 30, 'ssa/support/send_minimal_support_ticket', array( 'params' => [...$params, 'export_code' => '{}', 'debug_logs_hash' => $debug_logs_hash] ) );
if ( ! empty( $params['include_active_plugins'] ) ) {
$params['active_plugins'] = array();
$active_plugins = get_option( 'active_plugins' );
sort( $active_plugins );
foreach ($active_plugins as $active_plugin) {
if ( strpos( $active_plugin, '/' ) ) {
$active_plugin = substr( $active_plugin, 0, strpos( $active_plugin, '/' ) );
}
$params['active_plugins'][] = $active_plugin;
}
unset( $params['include_active_plugins'] );
}
if ( ! empty( $params['include_settings'] ) ) {
$params['site_hash_for_debug_logs'] = $debug_logs_hash;
unset( $params['include_settings'] );
}
$result = self::ssa_send_support_ticket( $params, $debug_logs_hash );
if ( !is_wp_error( $result ) ) {
// remove the queued ticket from the queue
ssa_unschedule_all_actions( 'ssa/support/send_minimal_support_ticket' );
}
return $result;
}
public static function ssa_send_support_ticket($params = array(), $debug_logs_hash = '') {
$response = wp_remote_post( 'https://api.simplyscheduleappointments.com/support_ticket/', array(
'sslverify' => false,
'headers' => array(
'content-type' => 'application/json',
),
'body' => json_encode( $params ),
) );
$response_code = wp_remote_retrieve_response_code($response);
if( $response_code > 299 || $response_code < 200 ) {
ssa_debug_log( "Failed to submit support ticket - invalid response code - response: " .print_r ( $response, true), 100 ); //phpcs:ignore
return new WP_Error( 'failed_submission', __( 'Your support ticket failed to be sent, please send details to support@ssaplugin.com', 'simply-schedule-appointments' ), $debug_logs_hash );
}
$response = wp_remote_retrieve_body( $response );
if ( empty( $response ) ) {
ssa_debug_log( "Failed to submit support ticket - response empty - response: " .print_r ( $response, true), 100 ); //phpcs:ignore
return new WP_Error( 'empty_response', __( 'No response', 'simply-schedule-appointments' ), $debug_logs_hash );
}
$response = json_decode( $response, true );
if ( ! is_array( $response ) ) {
$response = json_decode( $response, true );
}
if ($response['status'] != 'success' ) {
ssa_debug_log( "Failed to submit support ticket - status != success - response: " .print_r ( $response, true), 100 ); //phpcs:ignore
return new WP_Error( 'failed_submission', __( 'Your support ticket failed to be sent, please send details to support@ssaplugin.com', 'simply-schedule-appointments' ), $debug_logs_hash );
}
return $response;
}
public function get_items_permissions_check( $request ) {
return current_user_can( 'ssa_manage_site_settings' );
}
public function get_items( $request ) {
$params = $request->get_params();
return array(
'response_code' => 200,
'error' => '',
'data' => array(
'site_status' => $this->plugin->support_status->get_site_status(),
),
);
}
/**
* Gets the default debug.log contents.
*
* @param WP_REST_Request $request
* @return WP_REST_Response
*/
public function get_wp_debug_log_content( WP_REST_Request $request ) {
$developer_settings = $this->plugin->developer_settings->get();
if( $developer_settings && isset( $developer_settings['debug_mode'] ) && $developer_settings['debug_mode'] ) {
$path = ini_get('error_log');
// return $path;
if ( file_exists( $path ) && is_writeable( $path ) ) {
$content = file_get_contents( $path );
return new WP_REST_Response( $content, 200 );
}
}
return new WP_REST_Response( "", 200 );
}
/**
* Deletes the default debug.log file.
*
* @param WP_REST_Request $request
* @return WP_REST_Response
*/
public function empty_wp_debug_log_content( WP_REST_Request $request ) {
$path = ini_get('error_log');
if ( file_exists( $path ) && is_writeable( $path ) ) {
unlink( $path );
return new WP_REST_Response( __( 'Debug Log file successfully cleared.' ), 200 );
} else {
return new WP_REST_Response( __( 'Debug Log file not found.' ), 200 );
}
}
/**
* Gets the ssa_debug.log contents.
*
* @param WP_REST_Request $request
* @return WP_REST_Response
*/
public function get_ssa_debug_log_content( WP_REST_Request $request ) {
$developer_settings = $this->plugin->developer_settings->get();
if( $developer_settings && isset( $developer_settings['ssa_debug_mode'] ) && $developer_settings['ssa_debug_mode'] ) {
$path = $this->plugin->support_status->get_log_file_path( 'debug' );
if ( file_exists( $path ) && is_readable( $path ) ) {
$content = file_get_contents( $path );
return new WP_REST_Response( $content, 200 );
}
}
return new WP_REST_Response( "", 200 );
}
/**
* Deletes the ssa_debug.log file.
*
* @param WP_REST_Request $request
* @return void
*/
public function empty_ssa_debug_log_content( WP_REST_Request $request ) {
$path = $this->plugin->support_status->get_log_file_path( 'debug' );
if ( file_exists( $path ) && is_writeable( $path ) ) {
unlink( $path );
return new WP_REST_Response( __( 'Debug Log file successfully cleared.' ), 200 );
} else {
return new WP_REST_Response( __( 'Debug Log file not found or could not be removed.' ), 200 );
}
}
/**
* Returns the urls for all debug log files.
*
* @return WP_REST_Response
*/
public function get_debug_log_urls() {
$logs = array(
'wp' => null,
'ssa' => null,
);
$path = ini_get('error_log');
if ( file_exists( $path ) && is_readable( $path ) ) {
$logs['wp'] = str_replace(
wp_normalize_path( untrailingslashit( ABSPATH ) ),
site_url(),
wp_normalize_path( $path )
);
}
$ssa_path = $this->plugin->support_status->get_log_file_path( 'debug' );
if ( file_exists( $ssa_path ) && is_readable( $ssa_path ) ) {
$logs['ssa'] = str_replace(
wp_normalize_path( untrailingslashit( ABSPATH ) ),
site_url(),
wp_normalize_path( $ssa_path )
);
}
return new WP_REST_Response( $logs, 200 );
}
/**
* Pulls plugin settings, Appointment Types and Appointments and returns a JSON payload to be imported into another SSA plugin.
*
* @param WP_REST_Request $request the Request payload.
* @return WP_REST_Response
*/
public function get_export_code( WP_REST_Request $request ) {
$params = $request->get_params();
$payload = array();
if ( isset( $params['settings'] ) && 'true' === $params['settings'] ) {
$payload['settings'] = $this->plugin->settings->get();
foreach ( $payload['settings']['notifications']['notifications'] as &$notification ) {
$subject = empty( $notification['subject'] ) ? null : $notification['subject'];
$notification['subject'] = wp_strip_all_tags( $subject );
$notification['message'] = str_ireplace(
array(
'&quot;',
),
array(
'"',
),
$notification['message']
);
// TODO if this happens again: use html entities functions instead.
}
}
if ( isset( $params['appointment_types'] ) && 'true' === $params['appointment_types'] ) {
$payload['resource_groups'] = $this->plugin->resource_group_model->query(
array(
'number' => -1,
'order' => 'ASC',
)
);
$payload['resources'] = $this->plugin->resource_model->query(
array(
'number' => -1,
'order' => 'ASC',
)
);
$payload['resource_group_resources'] = $this->plugin->resource_group_resource_model->query(
array(
'number' => -1,
'order' => 'ASC',
)
);
$payload['staff'] = $this->plugin->staff_model->query(
array(
'number' => -1,
'order' => 'ASC',
)
);
$payload['appointment_types'] = $this->plugin->appointment_type_model->query(
array(
'order' => 'ASC', // necessary for keeping integrity with the order of rows inserted on the database.
'number' => -1,
)
);
$payload['staff_appointment_types'] = $this->plugin->staff_appointment_type_model->query(
array(
'number' => -1,
'order' => 'ASC',
)
);
$payload['resource_group_appointment_types'] = $this->plugin->resource_group_appointment_type_model->query(
array(
'number' => -1,
'order' => 'ASC',
)
);
$payload['appointment_type_labels'] = $this->plugin->appointment_type_label_model->query(
array(
'number' => -1,
'order' => 'ASC',
)
);
}
if ( isset( $params['appointments'] ) && 'true' === $params['appointments'] ) {
$appointments = $this->plugin->appointment_model->query(
array(
'order' => 'ASC', // necessary for keeping integrity with the order of rows inserted on the database.
'number' => isset( $params['appointments_limit'] ) ? (int) $params['appointments_limit'] : -1,
'start_date_min' => isset( $params['future_appointments_only'] ) && 'true' === $params['future_appointments_only'] ? gmdate( 'Y-m-d H:i:s' ) : null,
)
);
if ( ! empty( $params['anonymize_customer_information'] ) && 'true' === $params['anonymize_customer_information'] ) {
foreach ( $appointments as &$appointment ) {
foreach ( $appointment['customer_information'] as $key => &$value ) {
switch ( $key ) {
case 'Phone':
$value = '123-456-7890';
break;
case 'Email':
$value = substr( sha1( $value ), 0, 10 ) . '@mailinator.com';
break;
default:
if ( is_array( $value ) ) {
$value = json_encode( $value );
}
$value = substr( sha1( $value ), 0, 10 );
break;
}
}
}
}
$payload['appointments'] = $appointments;
// import meta data as well.
$payload['appointment_meta'] = $this->plugin->appointment_meta_model->query(
array(
'order' => 'ASC', // necessary for keeping integrity with the order of rows inserted on the database.
'number' => -1,
)
);
$payload['staff_appointments'] = $this->plugin->staff_appointment_model->query(
array(
'number' => -1,
'order' => 'ASC',
)
);
$payload['resource_appointments'] = $this->plugin->resource_appointment_model->query(
array(
'number' => -1,
'order' => 'ASC',
)
);
} elseif ( isset( $params['appointment_types'] ) && 'true' === $params['appointment_types'] ) {
$payload['appointments'] = array();
$payload['appointment_meta'] = array();
$payload['staff_appointments'] = array();
}
// Backup export code. Conditional to avoid replacing a proper backup when generating export code to send to support.
if ( ! isset( $params['backup'] ) || 'false' !== $params['backup'] ) {
$this->plugin->support_status->save_export_backup( $payload );
}
return new WP_REST_Response( $payload, 200 );
}
/**
* Receives a JSON formatted string via POST on our REST API endpoint, and runs the import process.
*
* @param WP_REST_Request $request the request object.
* @return WP_REST_Response
*/
public function import_data_api( WP_REST_Request $request ) {
$json = $request->get_param( 'content' );
// verify if JSON data is valid.
$decoded = json_decode( $json, true );
if ( ! is_object( $decoded ) && ! is_array( $decoded ) ) {
return new WP_REST_Response( __( 'Invalid data format.'), 500 );
}
if ( json_last_error() !== JSON_ERROR_NONE ) {
return new WP_REST_Response( __( 'Invalid data format.'), 500 );
}
$import = $this->plugin->support_status->import_data( $decoded );
// if any error happens while trying to import appointment type data, return.
if ( is_wp_error( $import ) ) {
return new WP_REST_Response( $import->get_error_messages(), 500 );
}
// everything was successfully imported.
return new WP_REST_Response( __( 'Data successfully imported!' ), 200 );
}
/**
* Checks transients to see if a request to ssa.com/guides is cached. If not, calls the API and caches the response.
*
* @since 5.4.0
*
* @param WP_REST_Request $request the request object.
* @return WP_REST_Response
*/
public function get_guides( WP_REST_Request $request ) {
$params = $request->get_params();
// $build a string to use as a transient key.
$transient_key = array();
foreach ( $params as $key => $value ) {
$transient_key[] .= $key . ':' . $value;
}
$transient_key = implode( '|', $transient_key );
$transient_name = 'ssa_guides_' . $transient_key;
$cached_response = get_transient( $transient_name );
if ( false === $cached_response ) {
$response = wp_safe_remote_get(
'https://simplyscheduleappointments.com/wp-json/ssa/v1/guides',
array(
'body' => $params,
)
);
if ( is_wp_error( $response ) ) {
return new WP_REST_Response( $response->get_error_messages(), 500 );
}
// check if the response is valid.
if ( strpos( $response['body'], 'rest_forbidden' ) !== false ) {
return new WP_REST_Response( __( 'Invalid data format.' ), 500 );
}
$cached_response = json_decode( $response['body'], true );
set_transient( $transient_name, $cached_response, WEEK_IN_SECONDS );
}
return new WP_REST_Response( $cached_response, 200 );
}
}