'Form submitted from website: %s', 'submitted_by' => 'Visitor IP address: %s', 'too_many_submissions' => 'Too many recent submissions from this IP', 'failed_to_send_email' => 'Failed to send email', 'invalid_field_type' => 'Unknown field type \'%s\'.', 'invalid_form_config' => 'Field \'%s\' has an invalid configuration.', 'unknown_method' => 'Unknown server request method' ); public function __construct() {} public function process($form) { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { die($this->_getErrorResponse($this->_messages['unknown_method'])); } if ($this->checkTooManySubmissions($_SERVER['REMOTE_ADDR'])) { die($this->_getErrorResponse($this->_messages['too_many_submissions'])); } // will die() if there are any errors $this->_checkRequiredFields($form); // will die() if there is a send email problem $this->_formSubmission($form); } public function checkTooManySubmissions($ip) { $tooManySubmissions = false; try { if (in_array("sqlite", PDO::getAvailableDrivers(), TRUE)) { $db = new PDO('sqlite:muse-throttle-db.sqlite3'); } else if (function_exists("sqlite_open")) { $db = new PDO('sqlite2:muse-throttle-db'); } else { return false; } } catch(PDOException $Exception) { return $tooManySubmissions; } if ($db) { $res = $db->query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='Submission_History';"); if (!$res or $res->fetchColumn() == 0) { $db->exec("CREATE TABLE Submission_History (IP VARCHAR(39), Submission_Date TIMESTAMP)"); } $db->exec("DELETE FROM Submission_History WHERE Submission_Date < DATETIME('now','-2 hours')"); $stmt = $db->prepare("INSERT INTO Submission_History (IP,Submission_Date) VALUES (:ip, DATETIME('now'))"); $stmt->bindParam(':ip', $ip); $stmt->execute(); $stmt->closeCursor(); $stmt = $db->prepare("SELECT COUNT(1) FROM Submission_History WHERE IP = :ip;"); $stmt->bindParam(':ip', $ip); $stmt->execute(); if ($stmt->fetchColumn() > 25) { $tooManySubmissions = true; } // Close file db connection $db = null; } return $tooManySubmissions; } private function _checkRequiredFields($form) { $errors = array(); foreach ($form['fields'] as $field => $properties) { if (!$properties['required']) { continue; } if (strpos($field, ' ')) { $field = str_replace(' ', '_', $field); } if (!array_key_exists($field, $_REQUEST) || ($_REQUEST[$field] !== "0" && empty($_REQUEST[$field]))) { array_push($errors, array('field' => $field, 'message' => $properties['errors']['required'])); } else if (!$this->_checkFieldValueFormat($field, $properties)) { array_push($errors, array('field' => $field, 'message' => $properties['errors']['format'])); } } if (!empty($errors)) { die($this->_getErrorResponse(array('fields' => $errors))); } } private function _checkFieldValueFormat($field, $properties) { $value = $this->_getFormFieldValue($field, $properties); switch($properties['type']) { case 'checkbox': case 'tel': case 'string': // no format to validate for those fields return true; case 'email': return 1 == preg_match('/^[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i', $value); default: die($this->_getErrorResponse(sprintf($this->_messages['invalid_field_type'], $properties['type']))); } } private function _getFormFieldValue($field, $properties) { $value = isset($_REQUEST[$field]) ? $_REQUEST[$field] : ''; switch($properties['type']) { case 'checkbox': return $value ? $value : ''; case 'string': case 'tel': case 'email': $returnValues = ''; if (is_array($value)) { foreach ($value as $key => $val) { $returnValues .= $this->_encodeValue($val); if ($key !== count($value) - 1) { $returnValues .= ', '; } } } else { $returnValues = $this->_encodeValue($value); } return $returnValues; default: die($this->_getErrorResponse(sprintf($this->_messages['invalid_field_type'], $properties['type']))); } } private function _formSubmission($form) { $emailFrom = $form['email']['from']; $formEmail = $emailFrom ? $emailFrom : ((array_key_exists('email', $_REQUEST) && !empty($_REQUEST['email'])) ? $this->_cleanupEmail($_REQUEST['email']) : ''); $to = $form['email']['to']; $subject = $form['subject']; $message = $this->_getEmailBody($subject, $form['email_message'], $form['fields']); $headers = $this->_getEmailHeaders($to, $formEmail); $sent = @mail($to, $subject, $message, $headers); if(!$sent) { die($this->_getErrorResponse($this->_messages['failed_to_send_email'])); } $success_data = array( 'redirect' => $form['success_redirect'] ); echo $this->_getFormResponse(true, $success_data); } private function _getEmailHeaders($toEmail, $formEmail) { $headers = 'From: ' . $toEmail . PHP_EOL; $headers .= 'Reply-To: ' . $formEmail . PHP_EOL; $headers .= 'X-Mailer: PHP/' . phpversion() . PHP_EOL; $headers .= 'Content-type: text/html; charset=utf-8' . PHP_EOL; return $headers; } private function _getEmailBody($subject, $emailMsg, $fields) { $message = ''; $message .= '' . $this->_encodeValue($subject) . ''; $styles = << th, td, caption { font-weight: 400; vertical-align: top; text-align: left; } STYLES; $message .= $styles; $message .= ''; $message .= ''; $message .= '
'; $message .= ''; $message .= '
'; $message .= ''; $message .= '
'; $message .= ''; $message .= '
'; $message .= $emailMsg; $message .= '
'; $message .= ''; $message .= '
'; $message .= ''; $message .= ''; $message .= ''; $message .= ''; $message .= '
'; $message .= ''; $sortedFields = array(); foreach ($fields as $field => $properties) { array_push($sortedFields, array('field' => $field, 'properties' => $properties)); } // sort fields usort($sortedFields, array(&$this, '_fieldComparer')); foreach ($sortedFields as $fieldWrapper) { $message .= ''; } $message .= '
'; $message .= '' . $this->_encodeValue($fieldWrapper['properties']['label']) . ':'; $message .= '
' . $this->_getFormFieldValue($fieldWrapper['field'], $fieldWrapper['properties']) . '
'; $message .= '
'; $message .= '
'; $message .= '
' . sprintf($this->_messages['submitted_from'], $this->_encodeValue($_SERVER['SERVER_NAME'])) . '
'; $message .= '
' . sprintf($this->_messages['submitted_by'], $this->_encodeValue($_SERVER['REMOTE_ADDR'])) . '
'; $message .= '
'; $message .= '
'; $message .= '
'; $message .= '
'; $message .= '
'; $message .= ''; return $this->_cleanupMessage($message); } private function _fieldComparer($field1, $field2) { if ($field1['properties']['order'] == $field2['properties']['order']) return 0; return (($field1['properties']['order'] < $field2['properties']['order']) ? -1 : 1); } private function _getErrorResponse($error) { if (is_array($error)) { $errorMsg = ''; foreach ($error as $err) { if(isset($err['message'])) { $errorMsg .= $err['message'] . "\n"; } } $error = $errorMsg; } return $this->_getFormResponse(false, array('error' => $error)); } private function _getFormResponse($success, $data) { header('Content-Type: application/json'); $status = array_merge(array('success' => $success), $data); return json_encode($status); } private function _encodeValue($text) { return htmlentities(stripslashes($text), ENT_QUOTES, 'UTF-8'); } private function _cleanupMessage($message) { $message = wordwrap($message, 70, "\r\n"); return $message; } private function _cleanupEmail($email) { $email = $this->_encodeValue($email); $email = preg_replace('=((||0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', null, $email); return $email; } public static function startDiagnostics() { if ($_SERVER['REQUEST_METHOD'] !== 'GET') { exit; } $supportResponse = FormProcessor::checkSupport(); if (!empty($_GET['mode']) and $_GET['mode'] == 'verify') { exit($supportResponse); } $message = 'Diagnostics'; $message .= '

Diagnostics

    '; if (strrpos($supportResponse,'PHP:0;') === false) { $message .= '
  • PHP: version too low'; } else { $message .= '
  • PHP: OK'; } if (strrpos($supportResponse,'MAIL:0;') === false) { $message .= '
  • Mail configuration: PHP mail() configured incorrectly on server. Form will not be able to send email.'; } else { $message .= '
  • Mail configuration: OK'; } if (strrpos($supportResponse,'SQL:1;') !== false) { $message .= '
  • Spam control: SQLite not found. Form may send email successfully, but limiting spam submissions by IP address will not work.'; } else if (strrpos($supportResponse,'SQL:8;') !== false) { $message .= '
  • Spam control: Cannot write to scripts directory. Form may send email successfully, but limiting spam submissions by IP address will not work.'; } else if (strrpos($supportResponse,'SQL:0;') === false) { $message .= '
  • Spam control: SQL configuration problem. Form may send email successfully, but limiting spam submissions by IP address will not work.'; } else { $message .= '
  • Spam control: OK

    Emails will be limited to 25 in 2 hours from the same IP address'; } $message .= '


'; $message .= ''; exit($message); } public static function checkSupport() { $throttleSupport = FormProcessor::checkDb(); $response ='SQL:' . $throttleSupport . ';'; $version = explode('.', PHP_VERSION); if ($version[0] < 4 || ($version[0] == 4 && $version[1] < 1)) { $response .='PHP:1;'; return $response; } else { $response .='PHP:0;'; } if (strncasecmp(php_uname('s'), 'win', 3) == 0) { $mailserver = ini_get('SMTP'); } else { $mailserver = ini_get('sendmail_path'); } if (strlen($mailserver) == 0) { $response .='MAIL:1;'; } else { if (!function_exists("mail")) { $response .='MAIL:2;'; } else { $sent = mail("recipient@example.com", "Hi", "test message", "From: sender@example.com"); if($sent) { $response .='MAIL:0;'; } else { $response .='MAIL:3;'; } } } return $response; } public static function checkDb() { if (!is_writable('.')) { return '8'; } try { if (in_array("sqlite", PDO::getAvailableDrivers(), TRUE)) { $db = new PDO('sqlite:muse-throttle-db.sqlite3'); if (file_exists('muse-throttle-db')) { unlink('muse-throttle-db'); } } else if (function_exists("sqlite_open")) { $db = new PDO('sqlite2:muse-throttle-db'); if (file_exists('muse-throttle-db.sqlite3')) { unlink('muse-throttle-db.sqlite3'); } } else { return '4'; } } catch (PDOException $Exception) { return '9'; } $retCode ='5'; if ($db) { $res = $db->query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='Submission_History';"); if (!$res or $res->fetchColumn() == 0) { $created = $db->exec("CREATE TABLE Submission_History (IP VARCHAR(39), Submission_Date TIMESTAMP)"); if($created == 0) { $created = $db->exec("INSERT INTO Submission_History (IP,Submission_Date) VALUES ('256.256.256.256', DATETIME('now'))"); } if ($created != 1) { $retCode = '2'; } } if($retCode == '5') { $res = $db->query("SELECT COUNT(1) FROM Submission_History;"); if ($res && $res->fetchColumn() > 0) { $retCode = '0'; } else { $retCode = '3'; } } // Close file db connection $db = null; } else { $retCode = '4'; } return $retCode; } }