<?php

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package fusionLib
 * @copyright Copyright (c) 2012 - 2016 fusionLib. All rights reserved.
 * @link http://fusionlib.com
 */

/**
 * flFormBuilder Class
 *
 * Class to build a form from an XML definition.
 *
 * @package fusionLib
 */
class flFormBuilder {

	/**
	 * The fields.
	 *
	 * @var array
	 */
	protected $fields = array();

	/**
	 * Build a form from an XML definition.
	 *
	 * @param flForm $form The form to build onto.
	 * @param string $definition The XML string defining the form elements.
	 * @param array $defaults Optional default values for elements.
	 * @param flSessionData $verification Session data to store human verification information in.
	 * @param array $options Array of additional options.
	 * @return bool true if the form was built; else false.
	 */
	function build($form, $definition, $defaults = array(), $verification = null, $options = array()) {
		// Parse the XML
		$definition = @simplexml_load_string($definition);
		if(!$definition || !isset($definition->field))
			return false;

		foreach($definition->field as $field) {
			$attrs = $field->attributes();

			// Create the element
			$elem = null;
			if(isset($attrs['id'])) {
				$elemName = (string)$attrs['id'];
				switch($attrs['type']) {
					case 'text': $elem = new flFormElementText($form, $elemName, isset($defaults[$elemName]) ? $defaults[$elemName] : null); break;
					case 'textarea': $elem = new flFormElementTextArea($form, $elemName, isset($defaults[$elemName]) ? $defaults[$elemName] : null); break;
					case 'email': $elem = new flFormElementEmail($form, $elemName, isset($defaults[$elemName]) ? $defaults[$elemName] : null); break;
					case 'uri': $elem = new flFormElementUri($form, $elemName, isset($defaults[$elemName]) ? $defaults[$elemName] : null); break;
					case 'number': $elem = new flFormElementNumber($form, $elemName, isset($attrs['min']) ? $attrs['min'] : null, isset($attrs['max']) ? $attrs['max'] : null, isset($defaults[$elemName]) ? $defaults[$elemName] : null); break;
					case 'int': $elem = new flFormElementInteger($form, $elemName, isset($attrs['min']) ? $attrs['min'] : null, isset($attrs['max']) ? $attrs['max'] : null, isset($defaults[$elemName]) ? $defaults[$elemName] : null); break;
					case 'checkbox': $elem = new flFormElementCheckbox($form, $elemName, isset($defaults[$elemName]) ? $defaults[$elemName] : null); break;
					case 'file': $elem = new flFormElementFile($form, (string)$attrs['id']); break;
					case 'select':
						$data = array();
						$default = null;
						if(isset($field->option)) {
							foreach($field->option as $o) {
								$data[] = (string)$o;
								if(isset($attrs['default']) && $attrs['default'] == (string)$o) {
									end($data);
									$default = key($data);
								}
							}
						}
						$elem = new flFormElementSelect($form, $elemName, isset($defaults[$elemName]) ? $defaults[$elemName] : $default, $data);
						break;
					case 'radio':
						$data = array();
						if(isset($field->option)) {
							foreach($field->option as $o)
								$data[] = (string)$o;
						}
						$elem = new flFormElementRadio($form, $elemName, isset($defaults[$elemName]) ? $defaults[$elemName] : null, $data);
						break;
					case 'checklist':
						$data = array();
						if(isset($field->option)) {
							foreach($field->option as $o)
								$data[] = (string)$o;
						}
						$elem = new flFormElementChecklist($form, $elemName, array(), $data);
						break;
					case 'pane':
						$elem = new flFormElementPane($form, $elemName, (string)$field);
						break;
					case 'verification':
						if($verification !== null) {
							$v1 = $verification->get('v1', intval(rand(1, 5)));
							$v2 = $verification->get('v2', intval(rand(1, 5)));
							$verification->v1 = $v1;
							$verification->v2 = $v2;

							$elem = new flFormElementInteger($form, $elemName);
							$elem->setLabel(sprintf(__('What is %d + %d?', 'fusionLib'), $v1, $v2))
								->setRequired()
								->addValidator(new flFormValidatorRequired(__('Please add the two numbers and enter the answer.', 'fusionLib')))
								->addValidator(new flFormValidatorEquals($v1 + $v2, __('Please add the two numbers and enter the answer.', 'fusionLib')));
							$field = trim((string)$field);
							if(!empty($field))
								$elem->setDescription($field);
						}
						break;
					case 'recaptcha':
						$siteKey = isset($attrs['site_key']) ? (string)$attrs['site_key'] : (isset($options['recaptcha_sitekey']) ? $options['recaptcha_sitekey'] : null);
						$secretKey = isset($attrs['secret_key']) ? (string)$attrs['secret_key'] : (isset($options['recaptcha_secretkey']) ? $options['recaptcha_secretkey'] : null);

						if($siteKey && $secretKey) {
							$elem = new flFormElementRecaptcha($form, $elemName, $siteKey, $secretKey);
						}
						else {
							$elem = null;
						}
						break;
				}
			}

			// If element created fill out labels etc
			if($elem !== null && $attrs['type'] != 'verification') {
				if(isset($attrs['label']))
					$elem->setLabel((string)$attrs['label']);
				if(isset($attrs['placeholder']))
					$elem->setPlaceholder((string)$attrs['placeholder']);
				if(isset($attrs['readonly']))
					$elem->setReadonly();
				if(isset($field->description))
					$elem->setDescription($field->description);

				// If has validation
				if(isset($field->validate)) {
					foreach($field->validate->children() as $v => $d) {
						$msg = (string)$d;
						if(empty($msg))
							$msg = null;

						switch($v) {
							case 'required': $elem->setRequired(); $elem->addValidator(new flFormValidatorRequired($msg)); break;
							case 'min':
								$a = $d->attributes();
								if(isset($a['length']))
									$elem->addValidator(new flFormValidatorMinLength($a['length'], $msg));
								else if(isset($a['value']))
									$elem->addValidator($attrs['type'] == 'checklist' ? new flFormValidatorMinChecklist($a['value'], $msg) : new flFormValidatorMin($a['value'], $msg));
								break;
							case 'max':
								$a = $d->attributes();
								if(isset($a['length']))
									$elem->addValidator(new flFormValidatorMaxLength($a['length'], $msg));
								else if(isset($a['value']))
									$elem->addValidator($attrs['type'] == 'checklist' ? new flFormValidatorMaxChecklist($a['value'], $msg) : new flFormValidatorMax($a['value'], $msg));
								break;
							case 'pattern':
								$a = $d->attributes();
								if(isset($a['value']))
									$elem->addValidator(new flFormValidatorRegex($a['value'], $msg));
								break;
							case 'email': $elem->addValidator(new flFormValidatorEmail($msg)); break;
							case 'uri': $elem->addValidator(new flFormValidatorUri($msg)); break;
							case 'not':
								$a = $d->attributes();
								if(isset($a['value']))
									$elem->addValidator(new flFormValidatorNotEquals($a['value'], $msg));
								break;
							case 'equal':
								$a = $d->attributes();
								if(isset($a['value']))
									$elem->addValidator(new flFormValidatorEquals($a['value'], $msg));
								break;
						}
					}
				}
			}

			// Remember the field
			$this->fields[(string)$attrs['id']] = $elem;
		}

		$submit = new flFormElementSubmit($form, $form->getName() . '_submit');
		if(isset($definition->submit)) {
			$a = $definition->submit->attributes();
			if(isset($a['caption']))
				$submit->setCaption($a['caption'])->setCSS('primary');
		}

		return true;
	}

	/**
	 * Get the data for all form elements.
	 *
	 * @param bool $asHTML true if newlines should be replaced with br tags and output should be HTML.
	 * @return array The element name and it's value.
	 */
	function getData($asHTML = false) {
		$r = array();
		foreach($this->fields as $n => $e) {
			if($e instanceof flFormElementSelect || $e instanceof flFormElementRadio) {
				$o = $e->getOptions();
				$r[$n] = isset($o[$e->getValue()]) ? $o[$e->getValue()] : '';
			}
			else if($e instanceof flFormElementChecklist) {
				$sel = array();
				$o = $e->getOptions();
				foreach($e->getValue() as $v) {
					if(isset($o[$v]))
						$sel[] = $asHTML ? '<li>' . $o[$v] . '</li>' : $o[$v];
				}
				if(count($sel) && $asHTML)
					$r[$n] = '<ul>' . join("\n", $sel) . '</ul>';
				else
					$r[$n] = join("\n", $sel);
			}
			else if(!$e instanceof flFormElementFile)
				$r[$n] = $asHTML ? str_replace("\n", "<br />\n", $e->getValue()) : $e->getValue();
		}
		return $r;
	}

	/**
	 * Get the data for all form file elements.
	 *
	 * @return array The element name and it's value.
	 */
	function getFileData() {
		$r = array();
		foreach($this->fields as $n => $e) {
			if(($e instanceof flFormElementFile) && $e->fileUploaded())
				$r[$n] = $e;
		}
		return $r;
	}
}
