I realize this is probably bad practice but... I've used a simple php script I wrote with the help of a tutorial book I read years ago. I've adapted it as much as I'm able for use with multiple sites but it's largely the same across sites. I have tried and tried to eliminate spam type messages, but alas I cannot figure out what else I can do/can be done. I'm sure someone will mention that e.g. Javascript would be better but I don't have the time or drive to learn it at this point, so please stick to the PHP. The specific code follows below, suggestions will be greatly appreciated as to how to future proof this for spam elimination.
The contact page:
<?php
session_start();
$_SESSION['form_time'] = time();
define ('SITE_KEY', '...');
define ('SECRET_KEY', '...');
if (array_key_exists('send', $_POST)) {
function getCaptcha($SecretKey) {
$Response = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=".SECRET_KEY."&response={$SecretKey}");
$Return = json_decode($Response);
return $Return;
}
$Return = getCaptcha($_POST['g-recaptcha-response']);
//var_dump($Return);
// mail processing script
$to = 'email1';
$me = 'email2';
$subject = 'Feedback From Website';
// list expected fields
$expected = array('name', 'email', 'question');
// set required fields
$required = array('name', 'email', 'question');
// set additional headers
$headers = 'From: Megan Roth<feedback@meganroth.com>';
// set the include
$process = 'includes/process.inc.php';
if (file_exists($process) && is_readable($process)) {
include($process);
}
else {
$mailSent = false;
mail($me, 'Server Problem', "$process cannot be read", $headers);
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta property="og:title" content="Megan Roth, Mezzo-Soprano - Contact">
<meta property="og:type" content="website">
<meta property="og:image" content="http://www.meganroth.com/EditedImages/index.jpg">
<meta property="og:url" content="http://www.meganroth.com/contact.php">
<meta property="og:description" content="Mezzo-soprano Megan Roth enjoys a career as a soloist in opera and oratorio as well as with prestigious chamber ensembles around the country.">
<title>Megan Roth, Mezzo-Soprano - Contact</title>
<meta name="description" content="Mezzo-soprano Megan Roth enjoys a career as a soloist in opera and oratorio as well as with prestigious chamber ensembles around the country.">
<meta name="author" content="Nathan Roth" >
<link href="css/w3_parallax_template.css" type="text/css" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Crimson+Pro">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=EB+Garamond">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Domine">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link href="css/parallax12.css" type="text/css" rel="stylesheet">
<style>
/************* ABOVE THIS LINE GLOBAL ***************/
form { width: 100%; }
form p { margin: 0px 0px 25px 20px; }
textarea {
width: 380px;
height: 150px;
}
@media screen and (max-width: 400px) { textarea {
width: 240px;
height: 120px;
} }
.textInput { width: 300px; }
@media screen and (max-width: 400px) { .textInput { width: 125px; } }
.sendButton { border:1px solid #000000!important; color: #000; background-color: #FAC41E; }
.sendButton:hover { border:1px; color:#000000; background-color: #08748F; }
form .website{ display:none; } /* hide because is spam protection */
.conStudio {
font-family: "EB Garamond", Times, "Times New Roman", serif;
font-size: 1.4em;
line-height: 1.68em;
color: #FAC41E;
font-weight: bold;
margin: 0px 0px 0px 0px;
}
</style>
<script>
<!--
function MM_validateForm() { //v4.0
if (document.getElementById){
var i,p,q,nm,test,num,min,max,errors='',args=MM_validateForm.arguments;
for (i=0; i<(args.length-2); i+=3) { test=args[i+2]; val=document.getElementById(args[i]);
if (val) { nm=val.name; if ((val=val.value)!="") {
if (test.indexOf('isEmail')!=-1) { p=val.indexOf('@');
if (p<1 || p==(val.length-1)) errors+='- '+nm+' must contain an e-mail address.\n';
} else if (test!='R') { num = parseFloat(val);
if (isNaN(val)) errors+='- '+nm+' must contain a number.\n';
if (test.indexOf('inRange') != -1) { p=test.indexOf(':');
min=test.substring(8,p); max=test.substring(p+1);
if (num<min || max<num) errors+='- '+nm+' must contain a number between '+min+' and '+max+'.\n';
} } } else if (test.charAt(0) == 'R') errors += '- '+nm+' is required.\n'; }
} if (errors) alert('The following error(s) occurred:\n'+errors);
document.MM_returnValue = (errors == '');
} }
//-->
</script>
<script src="https://www.google.com/recaptcha/api.js?render=6LcEl-8UAAAAAMlzOfIDXmnooj34lkDNfKDTxN2m"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-34193066-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-34193066-1');
</script>
</head>
<body>
<?php include("includes/new_navigation12.inc.php"); ?>
<!-- Container -->
<div class="w3-content w3-container w3-padding-64">
<div class="w3-row">
<div class="hdquote">
"…(her) soaring mezzo-soprano is clean and clear and her vocal glissandos precise and near perfect."<br>
- <em>Asheville Citizen-Times</em><br>
<strong>The Barber of Seville</strong>, Brevard Music Center.
<div class="decLine"></div>
</div>
</div>
<div class="w3-row">
<div class="w3-col m9 w3-padding-large">
<?php
if ($_POST && isset($missing) && !empty($missing)) {
?>
<p class="warning">Please complete the missing item(s) indicated.</p>
<?php
}
elseif ($_POST && $linkOne) {
?>
<p class="warning">Sorry, Messages that contain inappropriate data will not be sent.</p>
<?php
}
elseif ($_POST && $linkTwo) {
?>
<p class="warning">Sorry, Messages that contain inappropriate data will not be sent.</p>
<?php
}
elseif ($_POST && $linkThree) {
?>
<p class="warning">Sorry, Messages that contain inappropriate data will not be sent.</p>
<?php
}
elseif ($_POST && !$mailSent) {
?>
<p class="warning">Sorry, there was a problem sending your message. Please try again later.</p>
<?php
}
elseif ($_POST && $Return->success == true && $Return->score > 0.5 && $mailSent) {
?>
<p class="success">Your message has been sent. Thank you for your comments/questions!</p>
<?php } ?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" name="contact" id="contact" class="w3-container w3-card-4" onSubmit="MM_validateForm('name','','R','email','','RisEmail','comments','','R');return document.MM_returnValue">
<p><input name="website" type="text" class="website"></p>
<p>
<label for="name">Name: <?php
if (isset($missing) && in_array('name', $missing)) { ?>
<span class="warning">Please enter your name</span><?php } ?>
</label>
<input name="name" type="text" class="textInput" id="name"
<?php if (isset($missing)) {
echo 'value="'.htmlentities($_POST['name'], ENT_QUOTES).'"';
} ?>
>
</p>
<p>
<label for="email">Email: <?php
if (isset($missing) && in_array('email', $missing)) { ?>
<span class="warning">Please enter your email address</span><?php } ?>
</label>
<input name="email" type="text" class="textInput" id="email"
<?php if (isset($missing)) {
echo 'value="'.htmlentities($_POST['email'], ENT_QUOTES).'"';
} ?>
>
</p>
<p>
<label for="question">Comments:<?php
if (isset($missing) && in_array('question', $missing)) { ?>
<span class="warning">Please enter your comments</span><?php } ?>
</label>
<textarea name="question" id="question" cols="25" rows="5"><?php
if (isset($missing)) {
echo htmlentities($_POST['question'], ENT_QUOTES);
} ?></textarea>
</p>
<p>
<input type="hidden" id="g-recaptcha-response" name="g-recaptcha-response">
</p>
<p>
<input class="sendButton" type="submit" name="send" id="send" value="Click to Submit Comments">
</p>
</form>
<script>
grecaptcha.ready(function() {
grecaptcha.execute('<?php echo SITE_KEY; ?>', {action: 'homepage'}).then(function(token) {
//console.log(token);
document.getElementById('g-recaptcha-response').value=token;
});
});
</script>
<p class="welcome">Please take this time to send comments and your email address so we can stay in touch with you!</p><br><br>
</div>
<div class="w3-col m3 w3-padding-large">
<img class="border" src="EditedImages/Contact.jpg" alt="Headshot for Megan Roth's Contact Webpage">
</div>
</div>
<div class="w3-row w3-center">
<span class="conStudio">Interested in private lessons? Please visit my <a href="http://studio.meganroth.com/" onclick="window.open(this.href, '_blank');return false;">studio site!</a></span>
</div>
</div>
<!-- Footer -->
<?php include("includes/new_footer12.inc.php"); ?>
<script>
// Change style of navbar on scroll
window.onscroll = function() {myFunction()};
function myFunction() {
var navbar = document.getElementById("myNavbar");
if (document.body.scrollTop > 100 || document.documentElement.scrollTop > 100) {
navbar.className = "w3-bar" + " w3-card" + " w3-animate-top" + " w3-white";
} else {
navbar.className = navbar.className.replace(" w3-card w3-animate-top w3-white", "");
}
}
// Used to toggle the menu on small screens when clicking on the menu button
function toggleFunction() {
var x = document.getElementById("navDemo");
if (x.className.indexOf("w3-show") == -1) {
x.className += " w3-show";
} else {
x.className = x.className.replace(" w3-show", "");
}
}
// Toggle between showing and hiding the sidebar, and add overlay effect
function w3_open() {
if (mySidebar.style.display === 'block') {
mySidebar.style.display = 'none';
overlayBg.style.display = "none";
} else {
mySidebar.style.display = 'block';
overlayBg.style.display = "block";
}
}
// Close the sidebar with the close button
function w3_close() {
mySidebar.style.display = "none";
overlayBg.style.display = "none";
}
/* When the user clicks on the button,
toggle between hiding and showing the dropdown content */
function myFunction() {
document.getElementById("myDropdown").classList.toggle("w3-show");
}
// Close the dropdown if the user clicks outside of it
window.onclick = function(e) {
if (!e.target.matches('.dropbtn')) {
var myDropdown = document.getElementById("myDropdown");
if (myDropdown.classList.contains('w3-show')) {
myDropdown.classList.remove('w3-show');
}
}
}
</script>
</body>
</html>
And now for the actual mail processing script:
<?php
// 30 second minimum
session_start();
$time_limit = 30;
$suspect = false;
if (isset($_SESSION['form_time']) && is_numeric($_SESSION['form_time'])) {
$seconds_passed = time() - $_SESSION['form_time'];
if ($seconds_passed < $time_limit) {
$suspect = true;
}
} else {
$suspect = true;
}
# spam protection
if (isset($_POST["website"]) && $_POST["website"] == "") {
if (isset($_SERVER['SCRIPT_NAME']) && strpos($_SERVER['SCRIPT_NAME'], 'inc.php')) exit;
// remove escape characters from POST array
if (get_magic_quotes_gpc()) {
function stripslashes_deep($value) {
$value = is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value);
return $value;
}
$_POST = array_map('stripslashes_deep', $_POST);
}
// create empty array for any missing fields
$missing = array();
// assume that there is nothing suspect
$suspect = false;
// create a pattern to locate suspect phrases
$pattern = '/Content-Type:|Bcc:|CC:/i';
// function to check for suspect phrases
function isSuspect($val, $pattern, &$suspect) {
// if the variable is an array, loop through each element
// and pass it recursively back to the same function
if (is_array($val)) {
foreach ($val as $item) {
isSuspect($item, $pattern, $suspect);
if ($suspect)
break;
}
}
else {
// if one of the suspect phrases is found, set Boolean to true
if (preg_match($pattern, $val)) {
$suspect = true;
}
}
}
// check the $_POST array and any subarrays for suspect content
isSuspect($_POST, $pattern, $suspect);
if ($suspect ) {
$mailSent = false;
unset($missing);
}
else {
// process the $_POST variables
foreach ($_POST as $key => $value) {
// assign to temporary variable and strip whitespace if not an array
$temp = is_array($value) ? $value : trim($value);
// if empty and required, add to $missing array
if (empty($temp) && in_array($key, $required)) {
array_push($missing, $key);
}
// otherwise, assign to a variable of the same name as $key
elseif (in_array($key, $expected)) {
${$key} = $temp;
}
}
}
// validate the email address
if (!empty($email)) {
// regex to identify illegal characters in email address
$checkEmail = '/^[^@]+@[^\s\r\n\'";,@%]+$/';
// reject the email address if it doesn't match
if (!preg_match($checkEmail, $email)) {
$suspect = true;
$mailSent = false;
unset($missing);
}
}
// validate the comments
// regex to identify html links
$linkOne = false;
$checkCommentsLinks = '/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i'; // '/(http:\/\/|www)/';
if(preg_match($checkCommentsLinks, stripcslashes($question))){
$linkOne = true;
$suspect = true;
$mailSent = false;
unset($missing);
}
//validate comments against email addresses
$linkTwo = false;
$checkCommentsEmail = '/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/';
if(preg_match($checkCommentsEmail, stripcslashes($question))){
$linkTwo = true;
$suspect = true;
$mailSent = false;
unset($missing);
}
//look for links in comments
$linkThree = false;
if(preg_match('/http|www/i',$question)) {
$linkThree = true;
$suspect = true;
$mailSent = false;
unset($missing);
}
// go ahead only if not suspect and all required fields OK
if (!$suspect && empty($missing)) {
// initialize the $message variable
$message = '';
// loop through the $expected array
foreach($expected as $item) {
// assign the value of the current item to $val
if (isset(${$item})) {
$val = ${$item};
}
// if it has no value, assign 'Not Selected'
else {
$val = 'Not selected';
}
// if an array, expand as comma-separated string
if (is_array($val)) {
$val = implode(', ', $val);
}
// add label and value to the message body
$message .= ucfirst($item).": $val\n\n";
}
// limit line length
$message = wordwrap($message, 70);
// create Reply-To header
if (!empty($email)) {
$headers .= "\r\nReply-To: $email";
}
// send it
$mailSent = mail($to, $subject, $message, $headers);
if ($mailSent) {
// $missing is no longer needed if the email is sent, so unset it
unset($missing);
}
}
}
else {
http_response_code(400);
exit;
}
?>
Something I'm really banging my head against lately is the latest spam messages actually are sending an email with a subject line (don't know where a subject comes into this) that starts "SPAM". Any help will be more than appreciated!! Thanks for reading!
There is no future proof to prevent spam, you already use three ways to prevent it
You can add
In your isSuspect
function you should break when $suspect
is true, so you don't have to check all others values
//...
foreach ($val as $item) {
isSuspect($item, $pattern, $suspect);
if ($suspect)
break;
}
//...
When generating the form save the time in a session
:
session_start();
$_SESSION['form_time'] = time();
When submitting the form check for the time:
session_start();
$time_limit = 30;
$suspect = false;
if (isset($_SESSION['form_time']) && is_numeric($_SESSION['form_time'])) {
$seconds_passed = time() - $_SESSION['form_time'];
if ($seconds_passed < $time_limit) {
$suspect = true;
}
} else {
$suspect = true;
}
NOTE: if the user opens multiple forms the values will be overwritten, the last value will be used and the previous oppened forms will be invalidated
This is just basic way, there are many ways to implement this, the 30 seconds is just an arbitrary value that I chose