I have a simple website contact form created back in 2017.
The form was developed using PHP, PHPMailer, jQuery, HTML and CSS.
I would like to make sure the code is up to modern standards and secure.
I would also like to switch from PHP to AJAX for success and error messaging (in order to avoid a page refresh). But that matter is probably out of scope here so I'll leave it out of the question.
Here's a detailed breakdown:
(1) The form sends messages from site users to site managers.
(2) It uses HTML for structure and CSS for presentation.
(3) User input is sent via SMTP using PHPMailer.
(4) A textarea box, with a character counter, is provided for users to submit their message.
(5) If the character count is exceeded, a jQuery script prevents the form from being submitted.
(6) Google reCAPTCHA is installed to reduce spam.
(7) HTML and standard browser functions are used for user input validation on the client-side.
(8) PHP functions are used for user input validation and sanitization on the server-side.
(9) PHP handles success and error messaging to the user.
The form works fine, as far as I can tell.
I'm looking for some guidance here:
The SMTP/PHPMailer set-up requires a username and password embedded in the code. Is there a more secure way?
Any suggestions for improving the overall quality and efficiency of this code?
Thank you.
HTML, CSS & jQuery
// text area character counter
// displays total characters allowed
// displays warning at defined count (currently 150)
// disables submit button when < 0
// max characters that can be input set by maxlength attribute in HTML
(function($) {
$.fn.charCount = function(submit, options){
this.submit = submit;
// default configuration properties
var defaults = {
allowed: 1250,
warning: 150,
css: 'counter',
counterElement: 'span',
cssWarning: 'warning',
cssExceeded: 'exceeded',
counterText: ''
};
var options = $.extend(defaults, options);
function calculate(obj,submit){
submit.attr("disabled", "disabled");
var count = $(obj).val().length;
var available = options.allowed - count;
if(available <= options.warning && available >= 0){
$(obj).next().addClass(options.cssWarning);
} else {
$(obj).next().removeClass(options.cssWarning);
}
if(available < 0){
$(obj).next().addClass(options.cssExceeded);
} else {
$(obj).next().removeClass(options.cssExceeded);
submit.removeAttr("disabled");
}
$(obj).next().html(options.counterText + available);
};
this.each(function() {
$(this).after('<'+ options.counterElement +' class="' + options.css + '">'+ options.counterText +'</'+ options.counterElement +'>');
calculate(this, submit);
$(this).keyup(function(){calculate(this,submit)});
$(this).change(function(){calculate(this,submit)});
});
};
})(jQuery);
$(document).ready(function(){
$("#comments").charCount($("#submit"));
});
#contact-form {
display: flex;
flex-direction: column;
width: 50%;
font: 1rem/1.5 arial, helvetica, sans-serif;
margin: 0 auto 1em !important;
}
#contact-form > div {
display: flex;
flex-direction: column;
margin-bottom: 10px;
}
#contact-form > div:not(.send-fail-notice):not(#counter-container) {
width: 70%;
}
#contact-form > .ad2 { align-self: flex-start; }
/* label formatting */
#contact-form > div > label {
margin-bottom: 2px;
}
/* asterisk formatting */
#contact-form > div > label > span {
color: #f00;
font-size: 1.5em;
line-height: 1;
}
/* input field formatting */
#contact-form input,
#contact-form textarea {
border: 1px solid #ccc;
background: #fcfcfc;
padding: 2px 5px;
resize: none;
font-family: arial, helvetica, sans-serif;
}
#contact-form input:focus,
#contact-form textarea:focus {
border: 1px solid #777;
}
/* textarea and character counter */
#contact-form > #counter-container { }
#contact-form > #counter-container > .counter {
font-size: 1.5em;
color: #ccc;
}
#contact-form > #counter-container .warning {
color: orange;
}
#contact-form > #counter-container .warning::after {
content: " approaching limit";
font-size: 1em;
}
#contact-form > #counter-container .exceeded {
color: red;
}
#contact-form > #counter-container .exceeded::after {
content: " form won't submit";
font-size: 1em;
}
/* submit button formatting */
#contact-form > button {
align-self: flex-start;
padding: 5px 15px;
cursor: pointer;
border: 1px solid #ccc;
background-color: #f1f1f1;
background-image: linear-gradient(#f1f1f1, #fafafa);
}
#contact-form > button:hover {
background-image: linear-gradient(#e1e1e1, #eaeaea);
}
/* form errors */
.send-fail-notice {
flex-direction: row !important;
align-items: center;
padding: 15px;
background-color: #ffc;
border: 2px solid red;
}
.send-fail-notice > span {
color: #e13a3e;
font-weight: bold;
margin-left: 10px;
}
<form method="post" id="contact-form">
<?php echo $send_fail_one ?>
<?php echo $send_fail_two ?>
<div>
<label for="name">Name <span>*</span></label>
<input type="text" name="name" id="name" maxlength="75" value="<?php echo $name ?>" required>
</div>
<div>
<label for="email">E-mail <span>*</span></label>
<input type="email" name="email" id="email" maxlength="75" value="<?php echo $email ?>" required>
</div>
<div id="subject-line">
<label for="subject">Subject</label>
<input type="text" name="subject" id="subject" maxlength="75" value="<?php echo $subject ?>">
</div>
<div id="counter-container">
<label for="comments">Message <span>*</span></label>
<textarea name="comments" id="comments" maxlength="1500" cols="25" rows="5" required><?php echo $comments ?></textarea>
</div>
<div class="g-recaptcha" data-sitekey="6LdruToUAAAAAO4EZua5CV6_jREeCpv8knqklYa9" style="width: auto;"></div>
<button type="submit" name="submit" id="submit">Send Message</button>
</form>
PHP
// CONTACT FORM PROCESSING SCRIPT
// set default values (prevents undefined variable error)
$send_fail_one = null;
$send_fail_two = null;
$name = null;
$email = null;
$subject = null;
$comments = null;
// Load PHPMailer (v 6.5.3 02/02/2022)
// Import PHPMailer classes into the global namespace
// These must be at the top of your script, not inside a function
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// Load PHPMailer
require 'PHPMailer/src/Exception.php';
require 'PHPMailer/src/PHPMailer.php';
require 'PHPMailer/src/SMTP.php';
// Create new PHPMailer instance; passing `true` enables exceptions
$mail = new PHPMailer(true);
$mail->CharSet = "UTF-8";
// SMTP Debugging
//SMTP::DEBUG_OFF = off (for production use)
//SMTP::DEBUG_CLIENT = client messages
//SMTP::DEBUG_SERVER = client and server messages
$mail->SMTPDebug = SMTP::DEBUG_SERVER;
$mail->Debugoutput = 'html';
// SMTP settings
$mail->isSMTP();
$mail->Host = 'smtp.gmail.com';
$mail->SMTPAuth = true;
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
$mail->Port = 465;
$mail->Username = '[email protected]';
$mail->Password = 'FakePasswordForDemoPurposes';
$mail->setFrom('[email protected]');
$mail->addAddress('[email protected]');
$mail->isHTML(true);
// Sanitize & Validate Input
// Trim all $_POST values
/* Using FILTER_SANITIZE_SPECIAL_CHARS as opposed to FILTER_SANITIZE_STRING because:
* (1) FILTER_SANITIZE_SPECIAL_CHARS will disarm code but still display it.
* (2) FILTER_SANITIZE_STRING removes all code, leaving no trace, and all code input is lost (e.g., anything with brackets is lost).
* Hence, with (1) we can identify users trying to submit code.
* Exception: textarea field ('comments') uses FILTER_SANITIZE_STRING because otherwise nl2br (keep line breaks) doesn't work. */
$name = trim(filter_input(INPUT_POST, 'name', FILTER_SANITIZE_SPECIAL_CHARS));
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$subject = trim(filter_input(INPUT_POST, 'subject', FILTER_SANITIZE_SPECIAL_CHARS));
$comments = nl2br(filter_input(INPUT_POST, 'comments', FILTER_SANITIZE_STRING));
// Email Body
$message = <<<HTML
<br>
<table style="border: 1px solid #e1e1e1; background-color: #fafafa; margin: 0 auto; width: 75%; padding: 10px 20px;">
<tr><td style="padding: 10px 0;"><b>NAME</b></td></tr>
<tr><td style="border-bottom: 1px solid #e1e1e1; padding-bottom: 15px;">$name</td></tr>
<tr><td style="padding: 10px 0;"><b>E-MAIL</b></td></tr>
<tr><td style="border-bottom: 1px solid #e1e1e1; padding-bottom: 15px;">$email</td></tr>
<tr><td style="padding: 10px 0;"><b>SUBJECT</b></td></tr>
<tr><td style="border-bottom: 1px solid #e1e1e1; padding-bottom: 15px;">$subject</td></tr>
<tr><td style="padding: 10px 0;"><b>MESSAGE</b></td></tr>
<tr><td style="padding-bottom: 15px;">$comments<td></tr>
</table>
HTML;
$mail->Subject = 'Message Received =?utf-8?B?4oCT?= Website Contact Form';
$mail->Body = $message;
// verify recaptcha response
$url = "https://www.google.com/recaptcha/api/siteverify";
$privatekey = "xxx";
$response = file_get_contents($url."?secret=".$privatekey."&response=".$_POST['g-recaptcha-response']."&remoteip=".$_SERVER['REMOTE_ADDR']);
$data = json_decode($response);
// if recaptcha verification is a success...
if(isset($data->success) AND $data->success==true) {
// ... and if phpmailer does not send (for whatever reason, such as a wrong SMTP password)...
if (!$mail->send()) {
// then show this error message:
$send_fail_one = <<<ERROR
<div class="send-fail-notice">
<img src="images/warning-sign.gif" height="44" width="50" alt="error sign">
<span>ERROR. Message not sent.<br>Please try again or contact us at <i><u>bireg</u></i> <i><u>at</u></i> <i><u>outlook</u></i> <i><u>dot</u></i> <i><u>com</u></i>.</span>
</div>
ERROR;
} else {
// ...otherwise delivery is successful and page re-directs to thank you / confirmation page
header('Location: https://www.yahoo.com');
}
} else {
// if re-captcha verification fails, show this error message:
$send_fail_two = <<<ERROR
<div class="send-fail-notice">
<img src="images/warning-sign.gif" height="44" width="50" alt="error sign">
<span>ERROR. Message not sent.<br>Please check the anti-spam box.</span>
</div>
ERROR;
}
}