forked from Public/pics
		
	
		
			
				
	
	
		
			346 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			346 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/*****************************************************************************
 | 
						|
 * Form.php
 | 
						|
 * Contains key class Form.
 | 
						|
 *
 | 
						|
 * Kabuki CMS (C) 2013-2023, Aaron van Geffen
 | 
						|
 *****************************************************************************/
 | 
						|
 | 
						|
class Form
 | 
						|
{
 | 
						|
	public $request_method;
 | 
						|
	public $request_url;
 | 
						|
	public $content_above;
 | 
						|
	public $content_below;
 | 
						|
	private $fields = [];
 | 
						|
	private $data = [];
 | 
						|
	private $missing = [];
 | 
						|
	private $submit_caption;
 | 
						|
	private $trim_inputs;
 | 
						|
 | 
						|
	// NOTE: this class does not verify the completeness of form options.
 | 
						|
	public function __construct($options)
 | 
						|
	{
 | 
						|
		$this->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']);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |