request_method = !empty($options['request_method']) ? $options['request_method'] : 'POST'; $this->request_url = !empty($options['request_url']) ? $options['request_url'] : BASEURL; $this->fields = !empty($options['fields']) ? $options['fields'] : []; $this->content_below = !empty($options['content_below']) ? $options['content_below'] : null; $this->content_above = !empty($options['content_above']) ? $options['content_above'] : null; $this->submit_caption = !empty($options['submit_caption']) ? $options['submit_caption'] : 'Save information'; $this->trim_inputs = !empty($options['trim_inputs']); } public function getFields() { return $this->fields; } public function getData() { return $this->data; } public function getSubmitButtonCaption() { return $this->submit_caption; } public function getMissing() { return $this->missing; } public function setData($data) { $this->verify($data, true); $this->missing = []; } public function setFieldAsMissing($field) { $this->missing[] = $field; } public function verify($post, $initalisation = false) { $this->data = []; $this->missing = []; foreach ($this->fields as $field_id => $field) { // Field disabled? if (!empty($field['disabled'])) { $this->data[$field_id] = ''; continue; } // No data present at all for this field? if ((!isset($post[$field_id]) || $post[$field_id] == '') && $field['type'] !== 'captcha') { if (empty($field['is_optional'])) $this->missing[] = $field_id; if ($field['type'] === 'select' && !empty($field['multiple'])) $this->data[$field_id] = []; else $this->data[$field_id] = ''; continue; } // Should we trim this? if ($this->trim_inputs && $field['type'] !== 'captcha' && empty($field['multiple'])) $post[$field_id] = trim($post[$field_id]); // Using a custom validation function? if (isset($field['validate']) && is_callable($field['validate'])) { // Validation functions can clean up the data if passed by reference $this->data[$field_id] = $post[$field_id]; // Evaluate validation functions as boolean to see if data is missing if (!$field['validate']($post[$field_id])) $this->missing[] = $field_id; continue; } // Verify data by field type switch ($field['type']) { case 'select': case 'radio': $this->validateSelect($field_id, $field, $post); break; case 'checkbox': // Just give us a 'boolean' int for this one $this->data[$field_id] = empty($post[$field_id]) ? 0 : 1; break; case 'color': $this->validateColor($field_id, $field, $post); break; case 'file': // Asset needs to be processed out of POST! This is just a filename. $this->data[$field_id] = isset($post[$field_id]) ? $post[$field_id] : ''; break; case 'numeric': $this->validateNumeric($field_id, $field, $post); break; case 'captcha': if (isset($_POST['g-recaptcha-response']) && !$initalisation) $this->validateCaptcha($field_id); elseif (!$initalisation) { $this->missing[] = $field_id; $this->data[$field_id] = 0; } break; case 'text': case 'textarea': default: $this->validateText($field_id, $field, $post); } } } private function validateCaptcha($field_id) { $postdata = http_build_query([ 'secret' => RECAPTCHA_API_SECRET, 'response' => $_POST['g-recaptcha-response'], ]); $opts = [ 'http' => [ 'method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => $postdata, ] ]; $context = stream_context_create($opts); $result = file_get_contents('https://www.google.com/recaptcha/api/siteverify', false, $context); $check = json_decode($result); if ($check->success) { $this->data[$field_id] = 1; } else { $this->data[$field_id] = 0; $this->missing[] = $field_id; } } private function validateColor($field_id, array $field, array $post) { // Colors are stored as a string of length 3 or 6 (hex) if (!isset($post[$field_id]) || (strlen($post[$field_id]) != 3 && strlen($post[$field_id]) != 6)) { $this->missing[] = $field_id; $this->data[$field_id] = ''; } else $this->data[$field_id] = $post[$field_id]; } private function validateNumeric($field_id, array $field, array $post) { $data = isset($post[$field_id]) ? $post[$field_id] : ''; // Sanity check: does this even look numeric? if (!is_numeric($data)) { $this->missing[] = $field_id; $this->data[$field_id] = 0; return; } // Do we need to a minimum bound? if (isset($field['min_value'])) { if (is_float($field['min_value']) && (float) $data < $field['min_value']) { $this->missing[] = $field_id; $this->data[$field_id] = 0.0; } elseif (is_int($field['min_value']) && (int) $data < $field['min_value']) { $this->missing[] = $field_id; $this->data[$field_id] = 0; } } // What about a maximum bound? if (isset($field['max_value'])) { if (is_float($field['max_value']) && (float) $data > $field['max_value']) { $this->missing[] = $field_id; $this->data[$field_id] = 0.0; } elseif (is_int($field['max_value']) && (int) $data > $field['max_value']) { $this->missing[] = $field_id; $this->data[$field_id] = 0; } } $this->data[$field_id] = $data; } private function validateSelect($field_id, array $field, array $post) { // Skip validation? Dangerous territory! if (isset($field['verify_options']) && $field['verify_options'] === false) { $this->data[$field_id] = $post[$field_id]; return; } // Check whether selected option is valid. if (($field['type'] !== 'select' || empty($field['multiple'])) && empty($field['has_groups'])) { if (isset($post[$field_id]) && !isset($field['options'][$post[$field_id]])) { $this->missing[] = $field_id; $this->data[$field_id] = ''; return; } else $this->data[$field_id] = $post[$field_id]; } // Multiple selections involve a bit more work. elseif (!empty($field['multiple']) && empty($field['has_groups'])) { $this->data[$field_id] = []; if (!is_array($post[$field_id])) { if (isset($field['options'][$post[$field_id]])) $this->data[$field_id][] = $post[$field_id]; else $this->missing[] = $field_id; return; } foreach ($post[$field_id] as $option) { if (isset($field['options'][$option])) $this->data[$field_id][] = $option; } if (empty($this->data[$field_id])) $this->missing[] = $field_id; } // Any optgroups involved? elseif (!empty($field['has_groups'])) { if (!isset($post[$field_id])) { $this->missing[] = $field_id; $this->data[$field_id] = ''; return; } // Expensive: iterate over all groups until the value selected has been found. foreach ($field['options'] as $label => $options) { if (is_array($options)) { // Consider each of the options as a valid a value. foreach ($options as $value => $label) { if ($post[$field_id] === $value) { $this->data[$field_id] = $options; return; } } } else { // This is an ungrouped value in disguise! Treat it as such. if ($post[$field_id] === $options) { $this->data[$field_id] = $options; return; } else continue; } } // If we've reached this point, we'll consider the data invalid. $this->missing[] = $field_id; $this->data[$field_id] = ''; } else { throw new UnexpectedValueException('Unexpected field configuration in validateSelect!'); } } private function validateText($field_id, array $field, array $post) { $this->data[$field_id] = isset($post[$field_id]) ? $post[$field_id] : ''; // Trim leading and trailing whitespace? if (!empty($field['trim'])) $this->data[$field_id] = trim($this->data[$field_id]); // Is there a length limit to enforce? if (isset($field['maxlength']) && strlen($post[$field_id]) > $field['maxlength']) { $post[$field_id] = substr($post[$field_id], 0, $field['maxlength']); } } }