Current File : /home/jeconsul/public_html/wp-content/plugins/suremails/inc/api/save-test-connection.php |
<?php
/**
* SaveTestConnection class
*
* Handles the REST API endpoint for testing and saving email connection settings.
*
* @package SureMails\Inc\API
*/
namespace SureMails\Inc\API;
use SureMails\Inc\Emails\Handler\ConnectionHandlerFactory;
use SureMails\Inc\Providers;
use SureMails\Inc\Settings;
use SureMails\Inc\Traits\Instance;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Class SaveTestConnection
*
* Handles the `/test-and-save-email-connection` REST API endpoint.
*/
class SaveTestConnection extends Api_Base {
use Instance;
/**
* Indicates whether the current flow is for saving a connection.
* When simulation mode is enabled, this flag ensures that the Connection Handler Factory returns the appropriate handler for performing the save operation.
*
* @since 1.5.0
* @var bool True if saving a connection, false otherwise.
*/
public $saving_connection = false;
/**
* Route base.
*
* @var string
*/
protected $rest_base = '/test-and-save-email-connection';
/**
* Register API routes.
*
* @since 0.0.1
* @return void
*/
public function register_routes() {
register_rest_route(
$this->get_api_namespace(),
$this->rest_base,
[
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'handle_test_save_email_connection' ],
'permission_callback' => [ $this, 'validate_permission' ],
],
]
);
}
/**
* Handle testing and saving the email connection settings.
*
* This method will:
* - Retrieve the provider-specific fields via Providers::get_provider_options().
* - Validate that all required fields are present.
* - Prepare the connection data.
* - Check for priority uniqueness.
* - Authenticate the connection using the appropriate handler.
* - Save the connection settings.
*
* @param WP_REST_Request<array<string, mixed>> $request The REST request object.
* @return WP_REST_Response The REST API response.
*/
public function handle_test_save_email_connection( $request ) {
try {
$params = $request->get_json_params();
$settings = $params['settings'] ?? [];
$provider = strtoupper( sanitize_text_field( $params['provider'] ?? '' ) );
$this->saving_connection = true;
$options = Providers::instance()->get_provider_options( $provider );
if ( null === $options ) {
return new WP_REST_Response(
[
'success' => false,
'message' => __( 'Unsupported connection type.', 'suremails' ),
],
400
);
}
$fields = $options['fields'] ?? [];
$validation = $this->validate_schema_fields( $fields, $settings );
if ( ! $validation['success'] ) {
return new WP_REST_Response(
[
'success' => false,
'message' => $validation['message'],
],
400
);
}
// Prepare connection data (only the fields defined in the providier options).
$connection_data = $this->prepare_connection_data( $fields, $settings );
$connection_data['type'] = strtoupper( $provider );
// Check for priority uniqueness.
if ( isset( $connection_data['priority'] ) && ! $this->is_priority_unique( intval( $connection_data['priority'] ), $connection_data['id'] ?? '' ) ) {
return new WP_REST_Response(
[
'success' => false,
'message' => sprintf(
/* translators: %s: Connection priority */
__( 'Connection Sequence %1$s is already assigned to another connection. Please choose a different sequence.', 'suremails' ),
$connection_data['priority']
),
],
400
);
}
// Authenticate the connection before testing or saving it.
$auth_result = $this->authenticate_connection( $connection_data );
if ( $auth_result['success'] !== true ) {
$status_code = $auth_result['error_code'] ?? 401;
return new WP_REST_Response(
[
'success' => false,
'message' => $auth_result['message'] ?? __( 'Failed to authenticate.', 'suremails' ),
],
$status_code
);
}
// If the connection is authenticated, store the connection data.
$add_extra_fields = $this->add_extra_fields( $connection_data, $auth_result );
$new_connection_data = $this->store_connection( $add_extra_fields );
return new WP_REST_Response(
[
'success' => true,
'message' => __( 'Connection authenticated and settings saved.', 'suremails' ),
'connection' => $new_connection_data,
],
200
);
} catch ( \Exception $e ) {
return new WP_REST_Response(
[
'success' => false,
'message' => __( 'An error occurred: ', 'suremails' ) . $e->getMessage(),
],
500
);
} finally {
// Reset the saving_connection flag.
$this->saving_connection = false;
}
}
/**
* Authenticate the email connection to ensure it is valid.
*
* @param array $connection_data The connection data.
* @return array The result of the authentication process.
*/
public function authenticate_connection( array $connection_data ) {
$handler = ConnectionHandlerFactory::create( $connection_data );
if ( empty( $handler ) ) {
return [
'success' => false,
'message' => __( 'Invalid connection type.', 'suremails' ),
'error_code' => 400,
];
}
$auth_result = $handler->authenticate();
// Include an error code based on the specific error, if provided.
if ( isset( $auth_result['success'] ) && ! $auth_result['success'] ) {
$auth_result['error_code'] = $auth_result['error_code'] ?? 401;
}
return $auth_result;
}
/**
* Get all connection details.
*
* @return array The array of all connections.
*/
public function get_all_connections() {
// Fetch the connections from the WordPress options.
$connections = Settings::instance()->get_settings();
// Ensure the returned value is an array, even if the option does not exist.
return is_array( $connections['connections'] ) ? $connections['connections'] : [];
}
/**
* Store the email connection data in the database.
*
* @param array $connection_data The connection data.
* @return array The stored connection data.
*/
public function store_connection( array $connection_data ) {
$options = Settings::instance()->get_settings();
// Check if it's a new connection or an update.
if ( isset( $connection_data['id'] ) && ! empty( $connection_data['id'] ) ) {
// If it's an update, keep the existing `created_at` timestamp if it exists.
if ( ! empty( $options['connections'][ $connection_data['id'] ]['created_at'] ) ) {
$connection_data['created_at'] = $options['connections'][ $connection_data['id'] ]['created_at'];
}
if ( $options['default_connection']['id'] === $connection_data['id'] ) {
$options['default_connection'] = [
'type' => $connection_data['type'],
'email' => $connection_data['from_email'],
'id' => $connection_data['id'],
'connection_title' => $connection_data['connection_title'],
];
}
$options['connections'][ $connection_data['id'] ] = $connection_data;
} else {
// For new connections, generate a unique ID and add a `created_at` timestamp.
$connection_data['id'] = $this->generate_unique_id( $options['connections'] );
// Store the timestamp in MySQL datetime format.
$connection_data['created_at'] = current_time( 'mysql' );
$options['connections'][ $connection_data['id'] ] = $connection_data;
}
// Set default connection if necessary.
if ( count( $options['connections'] ) === 1 ) {
$options['default_connection'] = [
'type' => $connection_data['type'],
'email' => $connection_data['from_email'] ?? '',
'id' => $connection_data['id'],
'connection_title' => $connection_data['connection_title'] ?? '',
];
}
// Update the options in the WordPress database.
update_option( SUREMAILS_CONNECTIONS, Settings::instance()->encrypt_all( $options ) );
return $connection_data;
}
/**
* Add extra fields to the connection data.
*
* @param array $connection_data The connection data.
* @param array $new_fields The new fields to add.
* @return array The updated connection data.
*/
protected function add_extra_fields( $connection_data, $new_fields ) {
$providers = [
'GMAIL',
];
if ( ! in_array( $connection_data['type'], $providers ) ) {
return $connection_data;
}
if ( isset( $new_fields['refresh_token'] ) ) {
$connection_data['refresh_token'] = $new_fields['refresh_token'];
}
if ( isset( $new_fields['access_token'] ) ) {
$connection_data['access_token'] = $new_fields['access_token'];
}
if ( isset( $new_fields['expires_in'] ) ) {
$connection_data['expires_in'] = $new_fields['expires_in'];
}
if ( isset( $new_fields['expire_stamp'] ) ) {
$connection_data['expire_stamp'] = $new_fields['expire_stamp'];
}
unset( $connection_data['auth_code'] );
return $connection_data;
}
/**
* Validate the provided settings against the provider's field schema.
*
* @param array $schema The field definitions from the provider's options.
* @param array $settings The submitted settings.
* @return array ['success' => bool, 'message' => string]
*/
private function validate_schema_fields( array $schema, array $settings ) {
foreach ( $schema as $field => $rules ) {
if ( ! empty( $rules['required'] ) ) {
$value = trim( $settings[ $field ] ?? '' );
if ( '' === $value ) {
return [
'success' => false,
'message' => __( 'Missing required field.', 'suremails' ),
];
}
}
}
return [ 'success' => true ];
}
/**
* Prepare the connection data array based on the provider schema and submitted settings.
*
* Only fields defined in the schema are stored.
*
* @param array $schema The provider's field definitions.
* @param array $settings The settings array from the request.
* @return array The prepared connection data.
*/
private function prepare_connection_data( array $schema, array $settings ) {
$data = [];
foreach ( $schema as $field => $rules ) {
if ( isset( $settings[ $field ] ) ) {
$value = $settings[ $field ];
switch ( $rules['datatype'] ) {
case 'email':
$data[ $field ] = sanitize_email( $value );
break;
case 'int':
$data[ $field ] = intval( $value );
break;
case 'boolean':
$data[ $field ] = filter_var( $value, FILTER_VALIDATE_BOOLEAN );
break;
case 'string':
default:
$data[ $field ] = sanitize_text_field( $value );
break;
}
} else {
$data[ $field ] = '';
}
}
if ( isset( $settings['force_from_name'] ) && $settings['force_from_name'] ) {
$data['force_from_name'] = ! empty( $data['from_name'] );
}
if ( isset( $settings['force_from_email'] ) && $settings['force_from_email'] ) {
$data['force_from_email'] = ! empty( $data['from_email'] );
}
// ID is set for updatesss.
$data['id'] = $settings['id'] ?? '';
return $data;
}
/**
* Check if the desired priority is unique among existing connections.
*
* @param int $desired_priority The desired priority.
* @param string $current_id The current connection ID (if updating).
* @return bool True if unique, false otherwise.
*/
private function is_priority_unique( int $desired_priority, string $current_id ) {
$all_connections = $this->get_all_connections();
foreach ( $all_connections as $existing_connection ) {
if ( ! empty( $existing_connection['id'] ) && $existing_connection['id'] === $current_id ) {
// Skip the current connection if updating.
continue;
}
if ( intval( $existing_connection['priority'] ) === $desired_priority ) {
return false;
}
}
return true;
}
/**
* Generates a unique ID for a connection.
*
* @param array $existing_connections The list of existing connections.
* @return string The generated unique ID.
*/
private function generate_unique_id( array $existing_connections ) {
do {
$id = bin2hex( random_bytes( 16 ) ); // Generate a 32-character unique ID.
} while ( $this->id_exists( $id, $existing_connections ) );
return $id;
}
/**
* Checks if a connection ID already exists.
*
* @param string $id The ID to check.
* @param array $existing_connections The existing connections.
* @return bool True if exists, false otherwise.
*/
private function id_exists( string $id, array $existing_connections ) {
foreach ( $existing_connections as $connection ) {
if ( isset( $connection['id'] ) && $connection['id'] === $id ) {
return true;
}
}
return false;
}
/**
* Encrypt sensitive data.
*
* @param string $data The data to encrypt.
* @return string The encrypted data.
*/
private function encrypt( string $data ) {
// Implement your encryption logic here.
return $data;
}
}
// Initialize the SaveTestConnection singleton.
SaveTestConnection::instance();