<?php
/**
 * @version $Id: bfFormExtractor.php 144 2010-01-10 20:34:42Z  $
 * @package Blue Flame Framework (bfFramework)
 * @copyright Copyright (C) 2003,2004,2005,2006,2007,2008,2009 Blue Flame IT Ltd. All rights reserved.
 * @license GNU General Public License
 * @link http://www.blueflameit.ltd.uk
 * @author Phil Taylor / Blue Flame IT Ltd.
 *
 * bfFramework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * bfFramework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this package.  If not, see http://www.gnu.org/licenses/
 */
defined ( '_JEXEC' ) or die ( 'Restricted access' );

/**
 * Class that takes an HTML string and rips out the form fields returning them as properties of the object
 * @author Phil Taylor
 */
final class bfFormExtractor {

	public $name = '';
	public $id = '';
	public $method = 'GET';
	public $url = 'UNKNOWN';
	public $enctype = "application/x-www-form-urlencoded";
	public $class = '';
	public $autocomplete = '';
	public $formOpenTagOriginalHTML = '';
	public $formCloseTagOriginalHTML = '</form>';

	public $elements = array ();
	private $original_form_html = '';
	private $_attributes = array ('name', 'value', 'type', 'title', 'autocomplete', 'class', 'maxlength', 'tabindex', 'size', 'id', 'onblur', 'onclick' );

	// decode HTML <form> into browser object
	public function __construct($HTML_form) {
		$this->original_form_html = $HTML_form;
		$uu = '';
		$mine = array ();

		$form = $this->_parseFormHeader ();

		/* Locate all fields */
		//
		preg_match_all ( "/(<input[^>]+>)|<select[^>]+>(.+?)<\/select|<textarea[^>]+>([^<]+)|<textarea[^>]+>/xims", $form, $this->rawHTMLElements );

		$this->rawHTMLElements = $this->rawHTMLElements [0];

		foreach ( $this->rawHTMLElements as $i => $_element_html ) {
			$this->_parseElement ( $i, $_element_html );
		}
	}

	private function _parseFormHeader() {
		$form = false;

		if (preg_match ( "/<form.+?<\\/form[^>]*>/ims", $this->original_form_html, $uu )) {
			$form = $uu [0];
		} else if (preg_match ( '/{\\$FORM_OPEN_TAG}.+?FORM_CLOSE_TAG}/ims', $this->original_form_html, $uu )) {
			$form = $uu [0];
		}

		/* no form tag found */
		if ($form === false) {
			$form = '<form>' . $this->original_form_html . '</form>';
		}

		if (preg_match ( "/<form.+?[^>]*>/ims", $form, $uu )) {
			$this->formOpenTagOriginalHTML = $uu [0];
		}

		if (preg_match ( "/<form[^>]+?id=[\"']?([^\"'>\s]+)/ims", $form, $uu )) {
			$this->id = $uu [1];
		}

		if (preg_match ( "/<form[^>]+?name=[\"']?([^\"'>\s]+)/ims", $form, $uu )) {
			$this->name = $uu [1];
		}

		// fetch METHOD=
		if (preg_match ( "/<form[^>]+method=[\"']?(\w+)/ims", $form, $uu )) {
			$this->method = strtoupper ( $uu [1] );
		}

		// and URL=
		if (preg_match ( "/<form[^>]+?action=[\"']?([^\"'>\s]+)/ims", $form, $uu )) {
			$this->url = $uu [1];
		}

		// and class
		if (preg_match ( "/<form[^>]+?class=[\"']?([^\"'>\s]+)/ims", $form, $uu )) {
			$this->class = $uu [1];
		}

		// and enctype= if any
		if (preg_match ( "/<form[^>]+enctype=[\"']?([^\"'>\s]+)/ims", $form, $uu )) {
			$this->enctype = $uu [1];
		}

		if (preg_match ( "/<form[^>]+autocomplete=[\"']?([^\"'>\s]+)/ims", $form, $uu )) {
			$this->autocomplete = $uu [1];
		}

		/* return the first form in the HTML */
		return $form;

	}

	private function _parseElement($i, $_full) {

		$newElement = new bfFormElement ( );

		foreach ( $this->_attributes as $attr ) {
			if (preg_match ( "/<[^>]+" . $attr . "=\"(.?|.+?)\"[^>]*>/ims", $_full, $uu )) {
				$newElement->$attr = trim ( $uu [1] );
				$newElement->originalHTML = $_full;

				/* if radio or checkbox */
				if (preg_match ( '/\[\]/', $newElement->name ))
					$newElement->name = str_replace ( '[]', '', $newElement->name );
				if (preg_match ( '/\[\]/', $newElement->id ))
					$newElement->id = str_replace ( '[]', '', $newElement->id );
			}
		}

		/* try to find a label for the description */
		if (preg_match ( "/<label[^>]+for=\"" . $newElement->name . "\"[^>]*>(.?|.+?)<\/label>/ims", $this->original_form_html, $uu )) {
			$newElement->desc = trim ( strip_tags ( $uu [1] ) );
		}

		/* check if a radio box or checkbox and one element already added */
		if (array_key_exists ( $newElement->name, $this->elements ) && ($newElement->type == 'radio' || $newElement->type == 'checkbox')) {

			/* add the original html to the already there element */
			$this->elements [$newElement->name]->originalHTML .= "\n" . $newElement->originalHTML;

			/* swap the element back to the already existing one */
			$newElement = $this->elements [$newElement->name];

			/* then tidy the already there element again */
			$newElement->tidy ();

		} else {

			$newElement->tidy ();
			$this->elements [$newElement->name] = $newElement;
		}

	}

	public function tidy() {
		unset ( $this->originalHTML );
		unset ( $this->_attributes );
		unset ( $this->rawHTMLElements );
		unset ( $this->original_form_html );
	}
}

final class bfFormElement {

	public $name = "";
	public $value = "";
	public $type = "";
	public $desc = "";
	public $id = "";
	public $class = "";
	public $autocomplete = "";
	public $maxlength = "";
	public $size = "";
	public $tabindex = "";
	public $title = "";
	public $originalHTML = '';
	public $onclick = '';
	public $onblur = '';
	public $multiple = '';

	public function __construct() {

	}

	public function tidy() {

		if (! $this->id && $this->name)
			$this->id = $this->name;
		if (! $this->name && $this->id)
			$this->name = $this->id;

		switch ($this->type) {

			case "radio" :
				$this->_isRadioBox ();
				break;

			case "checkbox" :
				$this->_isCheckbox ();
				break;

			case "text" :
			case "hidden" :
			case "file" :
				break;

			default :
				$this->_findType ();
				break;
		}

	}

	private function _findType() {
		$this->type = 'UNKNOWN';

		if (preg_match ( '/<select/', $this->originalHTML )) {
			$this->_isSelectbox ();
		}

		if (preg_match ( '/<textarea/', $this->originalHTML )) {
			$this->_isTextarea ();
		}

		if (preg_match ( '/<input/', $this->originalHTML )) {
			if (preg_match ( '/type="submit"/i', $this->originalHTML )) {
				$this->_isSubmitButton ();
			}
			if (preg_match ( '/type="password"/i', $this->originalHTML )) {
				$this->_isPassword ();
			}
		}
	}

	private function _isPassword() {
		$this->type = 'password';
	}
	private function _isRadioBox() {
		$this->options = '';

		preg_match_all ( "/<input(?: [^>]+value=[\"']?([^\"'>]*)[^>]* )?>([^<]*)/xims", $this->originalHTML, $selectOptions );
		$mine = array ();

		foreach ( $selectOptions [1] as $n => $value ) {

			// is this the selected one...
			if ($selected = preg_match ( "/<[^>]+\s(selected|checked)[=>\s]/ims", $selectOptions [0] [$n] )) {
				$this->value = $value;
			}

			// add possible values + desc
			$this->options .= $value . '|' . $value . "\n";

		}
	}

	private function _isSubmitButton() {
		$this->type = 'submit';
		$this->onclick = "bf_form.formSubmit('{\$FORM_ID}');";
	}

	private function _isCheckBox() {
		$this->options = '';
		$this->value = '';

		preg_match_all ( "/<input(?: [^>]+value=[\"']?([^\"'>]*)[^>]* )?>([^<]*)/xims", $this->originalHTML, $selectOptions );
		$mine = array ();

		foreach ( $selectOptions [1] as $n => $value ) {

			// is this the selected one...
			if ($selected = preg_match ( "/<[^>]+\s(selected|checked)[=>\s]/ims", $selectOptions [0] [$n] )) {
				$this->multiple = $value;
			}

			// add possible values + desc
			$this->value .= $value . '|' . $value . "\n";

		}

		$this->value = bfString::trim ( $this->value );

	}

	private function _isSelectbox() {
		$this->type = 'select';

		preg_match_all ( "/<option(?: [^>]+value=[\"]?([^\">]*)[^>]* )?>([^<]*)/xims", $this->originalHTML, $selectOptions );

		$mine = array ();
		foreach ( $selectOptions [1] as $n => $value ) {

			// either from value= or plain text following opening <option> tag
			$desc = $selectOptions [2] [$n];
			if (! $value) {
				$value = $desc;
			}

			// is this the selected one...
			if ($selected = preg_match ( "/<[^>]+\s(selected|checked)[=>\s]/ims", $selectOptions [0] [$n] )) {
				$this->value = $value;
			}
			$unPretty = array ('/ô/', '/á/', '/é/', '/í/', '/ó/', '/ú/', '/Á/', '/É/', '/Í/', '/Ó/', '/Ú/', '/ñ/', '/Ñ/', '/ü/', '/Ü/',  '/í/', '/ñ/', '/\!/', '/\?/', '/:/', '/ä/', '/ö/', '/ü/', '/Ä/', '/Ö/', '/Ü/', '/ß/', '/\s?987987\s?/', '/\s?_\s?/', '/\s?\/\s?/', '/\s?\\\s?/', '/\s/', '/"/' );
			$pretty = array ('o','/a/', '/e/', '/i/', '/o/', '/u/', '/A/', '/E/', '/I/', '/O/', '/U/', '/n/', '/N/', '/u/', '/U/',  'i', 'n', '', '', '', 'ae', 'oe', 'ue', 'Ae', 'Oe', 'Ue', 'ss', ' ', ' ', ' ', ' ', ' ', '' );

			$value = preg_replace ( $unPretty, $pretty, $value );
			$desc = preg_replace ( $unPretty, $pretty, $desc );

			// add possible values + desc
			$this->options .= trim($value) . '|' . trim($desc) . "\n";

		}

		$this->options = bfstring::trim($this->options);

	}

	private function _isTextarea() {

		$this->type = 'textarea';
		if (! preg_match ( '~/>~', $this->originalHTML )) {
			$this->originalHTML = bfString::trim ( $this->originalHTML ) . '</textarea>';
		}
		if ($value = preg_match_all ( "/>(.*)(<| )/ims", $this->originalHTML, $m )) {
			if ($m [1])
				$this->value = $m [1] [0];
		}

		if (preg_match ( "/<textarea[^>]+rows=[\"']?(\w+)/ims", $this->originalHTML, $uu )) {
			$this->rows = ($uu [1]);
		}
		if (preg_match ( "/<textarea[^>]+cols=[\"']?(\w+)/ims", $this->originalHTML, $uu )) {
			$this->cols = ($uu [1]);
		}
	}
}