TL;DR
This month I wanted to improve my WordPress knowledge and create a simple form that wasn't bloated with features but was secure. I wanted to make sure this plugin improved my knowledge of:
- reCaptcha
- Akismet
- AJAX
- RESTful form submission
After spending the beginning part of the month figuring out WP_REST_Request
I was able to create a form plugin. The plugin works and emails who the author of the page is but I was wondering if I could make any improvements to my PHP and if any of my code has a vulnerability:
The PHP:
if (!defined('ABSPATH')) die('-1'); function contact_js() { if (is_page_template('page-contact.php')) { wp_enqueue_script( 'contact_js', site_url() . '/js/contact.js', array('jquery'), '1.0.0', true); // URL STRING $localized = array( 'url_string' => site_url(), ); wp_localize_script('contact_js', 'url_object', $localized); } } add_action('wp_enqueue_scripts', 'contact_js'); function reCaptcha_in_head() { if (is_page_template('page-contact.php')) { ?><script src='https://www.google.com/recaptcha/api.js'></script><?php } } add_action('wp_head', 'reCaptcha_in_head'); add_action('rest_api_init', function() { register_rest_route('darthvader/v2', '/contact/', array( 'methods' => 'POST', 'callback' => 'darth_contact_form', )); }); function darth_contact_form(\WP_REST_Request $request) { $pre_checks = true; if (check_ip_address() == "undefined") : $pre_checks = false; else : $form_ip_address = check_ip_address(); endif; if (check_referrer() == "undefined") : $pre_checks = false; else : $form_referrer = check_referrer(); endif; if (check_user_agent() == "undefined") : $pre_checks = false; else : $form_agent = check_user_agent(); endif; if (akismet_verify_key(akismet_get_key()) != 'valid') : $pre_checks = false; endif; if ($pre_checks !== true ) { return new WP_Error('error', 'prechecks failed', array('status' => 400)); } $form_email = clean_string($request['form_email']); $form_name = clean_string($request['form_name']); $form_message = clean_string($request['form_message']); $recaptcha = reCaptcha_check($form_ip_address); if ($recaptcha !== true) { // return('Recaptcha shows this to be: ' . $recaptcha); return new WP_Error('error', 'reCaptcha says spam', array('status' => 400)); } $akismet_outcome = akismet_spam_check($form_ip_address, $form_agent, $form_referrer, $form_email, $form_name, $form_message); // return('Is it spam? Akismet says: ' . $akismet_outcome); if ($akismet_outcome !== "false") { return new WP_Error('error', 'reCaptcha says spam', array('status' => 400)); } $form_success = email_the_message($form_ip_address, $form_agent, $form_referrer, $form_email, $form_name, $form_message); if (false === $form_success) { return new WP_Error('error', 'email failed to send', array('status' => 400)); } return; } // CHECK AKISMET IF IT'S SPAM function akismet_spam_check($form_ip_address, $form_agent, $form_referrer, $form_email, $form_name, $form_message) { if (function_exists('akismet_http_post')) : global $akismet_api_host, $akismet_api_port; // CALL TO COMMENT CHECK $contact_data = array( 'blog' => site_url(), 'user_ip' => $form_ip_address, 'user_agent' => $form_agent, 'referrer' => $form_referrer, 'comment_type' => 'contact-form', 'comment_author' => $form_name, 'comment_author_email' => $form_email, 'comment_content' => $form_message, 'permalink' => the_permalink(), 'is_test' => TRUE, // for testing purposes ); // CONSTRUCT THE QUERY STRING $query_string = http_build_query($contact_data); // POST IT TO AKISMET $response = akismet_http_post($query_string, $akismet_api_host, '/1.1/comment-check', $akismet_api_port); // CHECK THE RESULTS $result = (is_array($response) && isset($response[1])) ? $response[1] : 'false'; return $result; else : return new WP_Error('error', 'Akismet HTTP not found', array('status' => 404)); endif; } // CHECK WITH RECAPTCHA function reCaptcha_check($form_ip_address) { try { $url = 'https://www.google.com/recaptcha/api/siteverify'; $data = [ 'secret' => CAPTACHA_SECRET_KEY, // declared constant in wp-config 'response' => $_POST['g-recaptcha-response'], 'remoteip' => $form_ip_address, ]; $options = [ 'http' => [ 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 'method' => 'POST', 'content' => http_build_query($data) ] ]; $context = stream_context_create($options); $result = file_get_contents($url, false, $context); return json_decode($result)->success; } catch (Exception $e) { return null; } } // TIME function form_time() { $time = clean_string(date("F jS Y, h:ia", time())); return $time; } // REFERRER function check_referrer() { if (isset($_SERVER['HTTP_REFERER'])) : $referer = clean_string($_SERVER['HTTP_REFERER']); else : $referer = "undefined"; endif; return $referer; } // USER AGENT function check_user_agent() { if (isset($_SERVER['HTTP_USER_AGENT'])) { $agent = clean_string($_SERVER['HTTP_USER_AGENT']); } else { $agent = "undefined"; } return $agent; } // IP ADDRESS function check_ip_address() { if (isset($_SERVER['REMOTE_ADDR'])) : $ip_address = clean_string($_SERVER['REMOTE_ADDR']); else : $ip_address = "undefined"; endif; return $ip_address; } // SANITIZE function clean_string($string) { $string = rtrim($string); $string = ltrim($string); $string = htmlentities($string, ENT_QUOTES); $string = str_replace("n", "<br>", $string); if (get_magic_quotes_gpc()) { $string = stripslashes($string); } return $string; } function email_the_message($form_ip_address, $form_agent, $form_referrer, $form_email, $form_name, $form_message) { global $post; $to = get_the_author_meta('user_email'); $subject = the_title(); $body = "TIME: " . form_time() . "\n" . "IP ADDRESS: " . $form_ip_address . "\n" . "USER AGENT: " . $form_agent . "\n" . "EMAIL: " . $form_email . "\n" . "NAME: " . $form_name . "\n" . "MESSAGE: " . $form_message . "\n"; // send the awesomeness wp_mail($to, $subject, $body); }
After any improvements are suggested I will break up the PHP into a plugin and utilize WP Settings and Options API.
I did have help with this when I hit roadblocks. If you'd like to see where my faults lied here is a resource list of the Q&As I made to get me to this point: