From 3e80e1baa07472147094ddf0d6baa9b5a7d23af6 Mon Sep 17 00:00:00 2001
From: Ruben Ramirez
Date: Thu, 3 Apr 2025 19:55:33 -0500
Subject: [PATCH] feat: Add SMTP mailer configuration and integration\n\n- Adds
PHPMailer dependency via Composer.\n- Adds SMTP settings section (Host, Port,
Encryption, Auth, Credentials, From Address/Name, Enable toggle) to Settings
> Quiztech page.\n- Implements render callbacks and sanitization for new
settings.\n- Adds 'Send Test Email' button and AJAX handler for
verification.\n- Hooks into 'phpmailer_init' to configure PHPMailer based on
saved settings.\n- Ensures Invitations class uses wp_mail().
---
composer.json | 3 +-
composer.lock | 86 ++++++++-
quiztech-assessment-platform.php | 68 +++++++
src/Admin/SettingsPage.php | 319 ++++++++++++++++++++++++++++++-
src/Includes/Invitations.php | 6 +-
5 files changed, 474 insertions(+), 8 deletions(-)
diff --git a/composer.json b/composer.json
index 9712a78..3968eaa 100644
--- a/composer.json
+++ b/composer.json
@@ -10,7 +10,8 @@
}
],
"require": {
- "php": ">=7.4"
+ "php": ">=7.4",
+ "phpmailer/phpmailer": "^6.8"
},
"autoload": {
"psr-4": {
diff --git a/composer.lock b/composer.lock
index 07a77ca..3e655f3 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,90 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "ae3db58fab4d027fbd3814761fec769f",
- "packages": [],
+ "content-hash": "aa1767c4b73d4c64f1f4b5531b361ba5",
+ "packages": [
+ {
+ "name": "phpmailer/phpmailer",
+ "version": "v6.9.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPMailer/PHPMailer.git",
+ "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e",
+ "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-filter": "*",
+ "ext-hash": "*",
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "doctrine/annotations": "^1.2.6 || ^1.13.3",
+ "php-parallel-lint/php-console-highlighter": "^1.0.0",
+ "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "phpcompatibility/php-compatibility": "^9.3.5",
+ "roave/security-advisories": "dev-latest",
+ "squizlabs/php_codesniffer": "^3.7.2",
+ "yoast/phpunit-polyfills": "^1.0.4"
+ },
+ "suggest": {
+ "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
+ "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
+ "ext-openssl": "Needed for secure SMTP sending and DKIM signing",
+ "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
+ "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
+ "league/oauth2-google": "Needed for Google XOAUTH2 authentication",
+ "psr/log": "For optional PSR-3 debug logging",
+ "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
+ "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PHPMailer\\PHPMailer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-2.1-only"
+ ],
+ "authors": [
+ {
+ "name": "Marcus Bointon",
+ "email": "phpmailer@synchromedia.co.uk"
+ },
+ {
+ "name": "Jim Jagielski",
+ "email": "jimjag@gmail.com"
+ },
+ {
+ "name": "Andy Prevost",
+ "email": "codeworxtech@users.sourceforge.net"
+ },
+ {
+ "name": "Brent R. Matzelle"
+ }
+ ],
+ "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+ "support": {
+ "issues": "https://github.com/PHPMailer/PHPMailer/issues",
+ "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Synchro",
+ "type": "github"
+ }
+ ],
+ "time": "2024-11-24T18:04:13+00:00"
+ }
+ ],
"packages-dev": [
{
"name": "php-stubs/wordpress-stubs",
diff --git a/quiztech-assessment-platform.php b/quiztech-assessment-platform.php
index e083a69..11d0a70 100644
--- a/quiztech-assessment-platform.php
+++ b/quiztech-assessment-platform.php
@@ -139,3 +139,71 @@ function quiztech_init() {
// Future core classes (e.g., Shortcode handlers) should be instantiated here.
}
add_action( 'plugins_loaded', 'quiztech_init' );
+
+
+/**
+ * Configure PHPMailer to use SMTP settings if enabled.
+ *
+ * @param PHPMailer\PHPMailer\PHPMailer $phpmailer The PHPMailer instance.
+ */
+function quiztech_configure_smtp( $phpmailer ) {
+ $options = get_option( 'quiztech_settings', [] );
+
+ // Check if SMTP is enabled in settings
+ if ( empty( $options['smtp_enabled'] ) || ! $options['smtp_enabled'] ) {
+ return; // SMTP not enabled, do nothing
+ }
+
+ // Basic validation
+ if ( empty( $options['smtp_host'] ) || empty( $options['smtp_port'] ) ) {
+ // Optionally log an error or add an admin notice here
+ error_log('Quiztech SMTP Error: Host or Port not configured.');
+ return; // Cannot configure SMTP without host and port
+ }
+
+ // Set mailer to use SMTP
+ $phpmailer->isSMTP();
+
+ // Set SMTP server details
+ $phpmailer->Host = $options['smtp_host'];
+ $phpmailer->Port = (int) $options['smtp_port'];
+
+ // Set encryption type (tls, ssl, or none)
+ $encryption = isset( $options['smtp_encryption'] ) ? strtolower( $options['smtp_encryption'] ) : '';
+ if ( in_array( $encryption, [ 'ssl', 'tls' ] ) ) {
+ $phpmailer->SMTPSecure = $encryption;
+ } else {
+ $phpmailer->SMTPSecure = ''; // Explicitly set to none if not ssl/tls
+ }
+
+ // Set authentication details if enabled
+ $phpmailer->SMTPAuth = ! empty( $options['smtp_auth'] ) && $options['smtp_auth'];
+ if ( $phpmailer->SMTPAuth ) {
+ if ( empty( $options['smtp_username'] ) || empty( $options['smtp_password'] ) ) {
+ error_log('Quiztech SMTP Error: Auth enabled but Username or Password missing.');
+ // Decide if we should prevent sending or let PHPMailer handle the error
+ // For now, we proceed and let PHPMailer fail if credentials are bad
+ }
+ $phpmailer->Username = isset( $options['smtp_username'] ) ? $options['smtp_username'] : '';
+ $phpmailer->Password = isset( $options['smtp_password'] ) ? $options['smtp_password'] : '';
+ }
+
+ // Set From address (optional, but recommended)
+ $from_address = isset( $options['smtp_from_address'] ) ? $options['smtp_from_address'] : '';
+ $from_name = isset( $options['smtp_from_name'] ) ? $options['smtp_from_name'] : get_bloginfo( 'name' );
+
+ if ( ! empty( $from_address ) && is_email( $from_address ) ) {
+ try {
+ $phpmailer->setFrom( $from_address, $from_name );
+ } catch ( \PHPMailer\PHPMailer\Exception $e ) {
+ error_log( 'Quiztech SMTP Error setting From address: ' . $e->getMessage() );
+ // Continue without setting From if it fails
+ }
+ }
+
+ // Optional: Add debugging
+ // $phpmailer->SMTPDebug = 2; // Enable verbose debug output (use 0 in production)
+ // $phpmailer->Debugoutput = 'error_log'; // Log debug output to PHP error log
+}
+add_action( 'phpmailer_init', 'quiztech_configure_smtp' );
+
diff --git a/src/Admin/SettingsPage.php b/src/Admin/SettingsPage.php
index dee13d7..84fa30e 100644
--- a/src/Admin/SettingsPage.php
+++ b/src/Admin/SettingsPage.php
@@ -31,6 +31,7 @@ class SettingsPage {
public function register_hooks() {
add_action( 'admin_menu', [ $this, 'add_admin_page' ] );
add_action( 'admin_init', [ $this, 'register_settings' ] );
+ add_action( 'wp_ajax_quiztech_send_test_email', [ $this, 'handle_send_test_email_ajax' ] );
}
/**
@@ -95,8 +96,160 @@ class SettingsPage {
]
);
+
+ // --- SMTP Section ---
+ add_settings_section(
+ 'quiztech_smtp_section', // Section ID
+ __( 'SMTP Settings', 'quiztech' ), // Section Title
+ [ $this, 'render_smtp_section_description' ], // Section callback for description
+ $this->page_slug // Page slug where section appears
+ );
+
+ // SMTP Enabled Field (Checkbox)
+ add_settings_field(
+ 'quiztech_smtp_enabled',
+ __( 'Enable SMTP', 'quiztech' ),
+ [ $this, 'render_checkbox_field' ], // New callback needed
+ $this->page_slug,
+ 'quiztech_smtp_section',
+ [
+ 'id' => 'quiztech_smtp_enabled',
+ 'option_name' => $this->option_name,
+ 'key' => 'smtp_enabled',
+ 'description' => __( 'Enable sending emails via a custom SMTP server instead of the default WordPress mail function.', 'quiztech' )
+ ]
+ );
+
+ // SMTP Host Field
+ add_settings_field(
+ 'quiztech_smtp_host',
+ __( 'SMTP Host', 'quiztech' ),
+ [ $this, 'render_text_field' ],
+ $this->page_slug,
+ 'quiztech_smtp_section',
+ [
+ 'id' => 'quiztech_smtp_host',
+ 'option_name' => $this->option_name,
+ 'key' => 'smtp_host',
+ 'description' => __( 'e.g., smtp.example.com', 'quiztech' )
+ ]
+ );
+
+ // SMTP Port Field
+ add_settings_field(
+ 'quiztech_smtp_port',
+ __( 'SMTP Port', 'quiztech' ),
+ [ $this, 'render_text_field' ], // Use text field, sanitize as number later
+ $this->page_slug,
+ 'quiztech_smtp_section',
+ [
+ 'id' => 'quiztech_smtp_port',
+ 'option_name' => $this->option_name,
+ 'key' => 'smtp_port',
+ 'type' => 'number',
+ 'description' => __( 'e.g., 587 (TLS) or 465 (SSL)', 'quiztech' )
+ ]
+ );
+
+ // SMTP Encryption Field (Select)
+ add_settings_field(
+ 'quiztech_smtp_encryption',
+ __( 'Encryption', 'quiztech' ),
+ [ $this, 'render_select_field' ], // New callback needed
+ $this->page_slug,
+ 'quiztech_smtp_section',
+ [
+ 'id' => 'quiztech_smtp_encryption',
+ 'option_name' => $this->option_name,
+ 'key' => 'smtp_encryption',
+ 'options' => [
+ '' => __( 'None', 'quiztech' ),
+ 'ssl' => __( 'SSL', 'quiztech' ),
+ 'tls' => __( 'TLS', 'quiztech' ),
+ ],
+ 'description' => __( 'Select the encryption method.', 'quiztech' )
+ ]
+ );
+
+ // SMTP Auth Field (Checkbox)
+ add_settings_field(
+ 'quiztech_smtp_auth',
+ __( 'Use Authentication', 'quiztech' ),
+ [ $this, 'render_checkbox_field' ], // Reuse callback
+ $this->page_slug,
+ 'quiztech_smtp_section',
+ [
+ 'id' => 'quiztech_smtp_auth',
+ 'option_name' => $this->option_name,
+ 'key' => 'smtp_auth',
+ 'description' => __( 'Enable if your SMTP server requires a username and password.', 'quiztech' )
+ ]
+ );
+
+ // SMTP Username Field
+ add_settings_field(
+ 'quiztech_smtp_username',
+ __( 'SMTP Username', 'quiztech' ),
+ [ $this, 'render_text_field' ],
+ $this->page_slug,
+ 'quiztech_smtp_section',
+ [
+ 'id' => 'quiztech_smtp_username',
+ 'option_name' => $this->option_name,
+ 'key' => 'smtp_username',
+ 'description' => __( 'The username for SMTP authentication.', 'quiztech' )
+ ]
+ );
+
+ // SMTP Password Field
+ add_settings_field(
+ 'quiztech_smtp_password',
+ __( 'SMTP Password', 'quiztech' ),
+ [ $this, 'render_text_field' ],
+ $this->page_slug,
+ 'quiztech_smtp_section',
+ [
+ 'id' => 'quiztech_smtp_password',
+ 'option_name' => $this->option_name,
+ 'key' => 'smtp_password',
+ 'type' => 'password',
+ 'description' => __( 'The password for SMTP authentication. Stored as plain text in the database.', 'quiztech' )
+ ]
+ );
+
+ // SMTP From Address Field
+ add_settings_field(
+ 'quiztech_smtp_from_address',
+ __( 'From Email Address', 'quiztech' ),
+ [ $this, 'render_text_field' ],
+ $this->page_slug,
+ 'quiztech_smtp_section',
+ [
+ 'id' => 'quiztech_smtp_from_address',
+ 'option_name' => $this->option_name,
+ 'key' => 'smtp_from_address',
+ 'type' => 'email',
+ 'description' => __( 'The email address emails will be sent from.', 'quiztech' )
+ ]
+ );
+
+ // SMTP From Name Field
+ add_settings_field(
+ 'quiztech_smtp_from_name',
+ __( 'From Name', 'quiztech' ),
+ [ $this, 'render_text_field' ],
+ $this->page_slug,
+ 'quiztech_smtp_section',
+ [
+ 'id' => 'quiztech_smtp_from_name',
+ 'option_name' => $this->option_name,
+ 'key' => 'smtp_from_name',
+ 'description' => __( 'The name emails will be sent from.', 'quiztech' )
+ ]
+ );
+
// Future settings sections/fields (e.g., email, defaults) should be registered here.
- }
+ } // End register_settings()
/**
* Renders the main settings page container and form.
@@ -116,6 +269,42 @@ class SettingsPage {
// Output save settings button
submit_button( __( 'Save Settings', 'quiztech' ) );
?>
+
+
+
+
()
+
+
+
+
' . esc_html__( 'Configure your SMTP server details for reliable email sending. If disabled, WordPress will use the default PHP mail function.', 'quiztech' ) . '
';
+ }
+
+ /**
+ * Renders a checkbox input field for a setting.
+ * Expects args: 'id', 'option_name', 'key', 'description' (optional)
+ *
+ * @param array $args Arguments passed from add_settings_field.
+ */
+ public function render_checkbox_field( $args ) {
+ $options = get_option( $args['option_name'], [] );
+ $checked = isset( $options[ $args['key'] ] ) && $options[ $args['key'] ] ? 'checked' : '';
+ ?>
+
+
+ />
+
+
+
+ label), 'description' (optional)
+ *
+ * @param array $args Arguments passed from add_settings_field.
+ */
+ public function render_select_field( $args ) {
+ $options = get_option( $args['option_name'], [] );
+ $value = isset( $options[ $args['key'] ] ) ? $options[ $args['key'] ] : '';
+ ?>
+
+
+
+
+ 'sanitize_text_field',
+ 'smtp_port' => 'absint', // Sanitize as integer
+ 'smtp_encryption' => 'sanitize_key', // 'ssl', 'tls', or ''
+ 'smtp_username' => 'sanitize_text_field',
+ 'smtp_password' => 'sanitize_text_field', // Keep as text, WP handles display
+ 'smtp_from_address' => 'sanitize_email',
+ 'smtp_from_name' => 'sanitize_text_field',
+ ];
+
+ foreach ( $smtp_fields as $key => $sanitize_callback ) {
+ if ( isset( $input[ $key ] ) ) {
+ $sanitized_input[ $key ] = call_user_func( $sanitize_callback, $input[ $key ] );
+ }
+ }
+
+ // Checkboxes (only save if present and value is '1')
+ $checkbox_fields = [ 'smtp_enabled', 'smtp_auth' ];
+ foreach ( $checkbox_fields as $key ) {
+ $sanitized_input[ $key ] = ( isset( $input[ $key ] ) && $input[ $key ] === '1' ) ? 1 : 0;
+ }
+
+ // Validate encryption value
+ if ( ! in_array( $sanitized_input['smtp_encryption'], [ '', 'ssl', 'tls' ], true ) ) {
+ $sanitized_input['smtp_encryption'] = ''; // Default to none if invalid
}
// Future settings added should have their sanitization logic implemented here.
return $sanitized_input;
}
+
+
+ /**
+ * Handles the AJAX request to send a test email.
+ */
+ public function handle_send_test_email_ajax() {
+ check_ajax_referer( 'quiztech_test_email_nonce', '_ajax_nonce' );
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error( [ 'message' => __( 'Permission denied.', 'quiztech' ) ], 403 );
+ }
+
+ $admin_email = get_option( 'admin_email' );
+ if ( ! is_email( $admin_email ) ) {
+ wp_send_json_error( [ 'message' => __( 'Invalid site administrator email address.', 'quiztech' ) ] );
+ }
+
+ $subject = __( 'Quiztech SMTP Test Email', 'quiztech' );
+ $message = __( 'This is a test email sent from the Quiztech plugin settings page to verify your SMTP configuration.', 'quiztech' );
+ $headers = []; // Can add headers if needed, e.g., Content-Type
+
+ // Temporarily hook into phpmailer to capture errors
+ $error_message = '';
+ add_action( 'wp_mail_failed', function ( $wp_error ) use ( &$error_message ) {
+ if ( is_wp_error( $wp_error ) ) {
+ $error_message = $wp_error->get_error_message();
+ }
+ }, 10, 1 );
+
+ $sent = wp_mail( $admin_email, $subject, $message, $headers );
+
+ // Remove the temporary hook
+ // Note: Removing anonymous functions requires more complex handling (e.g., storing the closure in a property).
+ // For simplicity in this test function, we'll leave it, but be aware it persists for the request.
+ // A better approach for production code might involve a dedicated class or named function.
+ // remove_action( 'wp_mail_failed', $this->capture_wp_mail_error_closure ); // Example if stored
+
+ if ( $sent ) {
+ wp_send_json_success();
+ } else {
+ $error_to_send = ! empty( $error_message ) ? $error_message : __( 'The email could not be sent. Check your SMTP settings and server logs.', 'quiztech' );
+ wp_send_json_error( [ 'message' => $error_to_send ] );
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Includes/Invitations.php b/src/Includes/Invitations.php
index 8891f36..f833506 100644
--- a/src/Includes/Invitations.php
+++ b/src/Includes/Invitations.php
@@ -83,10 +83,8 @@ class Invitations {
$message = "Please click the following link to take your assessment:\n\n" . $invite_url;
// $headers = ['Content-Type: text/html; charset=UTF-8']; // If sending HTML email
- // $sent = \wp_mail($applicant_email, $subject, $message); // Corrected line 76
- // return $sent;
-
- return true; // Placeholder
+ $sent = \wp_mail($applicant_email, $subject, $message); // Corrected line 76
+ return $sent;
}
/**