<?php
/**
* class PhpXforms
*
* @author Victor Bolshov <hide@address.com>
* @copyright Use it if you like it
*
*/
class PhpXforms {
/**
* @var string
*/
protected $_schema;
/**
* @var string
*/
protected $_xmlSource;
/**
* @var bool
*/
protected $_schemaAsSource;
/**
* @var callback
*/
protected $_dataValidator;
/**
* @var callback
*/
protected $_dataProcessor;
/**
* @var callback
*/
protected $_exceptionHandler;
/**
* @var callback
*/
protected $_onSuccessCallback;
/**
* User-submitted data, in the form of PHP-array
* @var array
*/
protected $_data;
/**
* Constructor
*
* @param string $schema XMLSchema (either path to file containing schema source or the source itself;
* in the latter case the second parameter must be set to TRUE)
*/
function __construct($schema, $asSource = false)
{
$this->_schemaAsSource = (bool) $asSource;
$this->_schema = (string) $schema;
if (! $this->_schemaAsSource && ! is_readable($this->_schema))
{
throw new PhpXformsException('Schema file ' . $this->_schema . ' is unreadable');
}
}
/**
* Set the source document to be validated
*
* @param string $source XML document
* @return void
*/
function setXmlSource($source)
{
$this->_xmlSource = $source;
}
/**
* Set a callback for additional data validation.
* Callback must accept array of data (possibly multidimensional) as parameter and
* throw an Exception in case of validation error.
*
* NOTE: primary validation (datatypes, min-max values) is actually supposed to be done with XmlSchema,
* this callback is for additional validation, such as check for unique field in the database.
*
* @param callback $callback
* @return void
* @throws PhpXformsException
*/
function setValidatorCallback($callback)
{
$this->_setCallback($this->_dataValidator, $callback);
}
/**
* @see setValidatorCallback
* This function is an alias to setValidatorCallback(array($object, $method))
*
* @param object $object
* @param string $method
* @return void
* @throws PhpXformsException
*/
function setValidator($object, $method)
{
$this->setValidatorCallback(array($object, $method));
}
/**
* Set a callback for data processing.
* Callback must accept array of data (possibly multidimensional) as parameter and
* throw an Exception in case of error.
*
* @param callback $callback
* @return void
* @throws PhpXformsException
*/
function setProcessorCallback($callback)
{
$this->_setCallback($this->_dataProcessor, $callback);
}
/**
* @see setProcessorCallback
* This function is an alias to setProcessorCallback(array($object, $method))
*
* @param object $object
* @param string $method
* @return void
* @throws PhpXformsException
*/
function setProcessor($object, $method)
{
$this->setProcessorCallback(array($object, $method));
}
/**
* Set a callback for exception handling.
* Callback must accept an Exception as parameter
*
* @param callback $callback
* @return void
* @throws PhpXformsException
*/
function setExceptionHandlerCallback($callback)
{
$this->_setCallback($this->_exceptionHandler, $callback);
}
/**
* @see setExceptionHandlerCallback
* This function is an alias to setExceptionHandlerCallback(array($object, $method))
*
* @param object $object
* @param string $method
* @return void
* @throws PhpXformsException
*/
function setExceptionHandler($object, $method)
{
$this->setExceptionHandlerCallback(array($object, $method));
}
/**
* Sets default exception handler.
* @see setExceptionHandler
* @see PhpXforms_ExceptionHandler
* @param object $object
* @param string $method
* @return void
* @throws PhpXformsException
*/
function setDefaultExceptionHandler($encoding = null)
{
require_once dirname(__FILE__) . '/' . __CLASS__ . '/ExceptionHandler.php';
$this->setExceptionHandler(
new PhpXforms_ExceptionHandler($encoding), 'xmlErrorList');
}
/**
* Set a callback for execute after successful data processing.
* Callback must accept array of data (possibly multidimensional) as parameter
*
* @param callback $callback
* @return void
* @throws PhpXformsException
*/
function setSuccessProcessorCallback($callback)
{
$this->_setCallback($this->_onSuccessCallback, $callback);
}
/**
* @see setSuccessProcessorCallback
* This function is an alias to setSuccessProcessorCallback(array($object, $method))
*
* @param object $object
* @param string $method
* @return void
* @throws PhpXformsException
*/
function setSuccessProcessor($object, $method)
{
$this->setSuccessProcessorCallback(array($object, $method));
}
/**
* Sets default processor for successful data-processing.
* @see setSuccessProcessor
* @see PhpXforms_SuccessProcessor
* @param object $object
* @param string $method
* @return void
* @throws PhpXformsException
*/
function setDefaultSuccessProcessor($encoding = null)
{
require_once dirname(__FILE__) . '/' . __CLASS__ . '/SuccessProcessor.php';
$this->setSuccessProcessor(
new PhpXforms_SuccessProcessor($encoding), 'emptyErrorList');
}
/**
* Perform validation and data-processing
*
* @throws Exception
*/
function run()
{
$this->_ensureXmlSource();
try {
// validation
$this->_data = $this->_validate();
// data processing
if (null !== $this->_dataProcessor)
{
call_user_func($this->_dataProcessor, $this->_data);
}
// success! handle it, too...
if (null !== $this->_onSuccessCallback)
{
call_user_func($this->_onSuccessCallback, $this->_data);
}
} catch (Exception $e) {
if (null === $this->_exceptionHandler)
{
throw $e;
}
else
{
call_user_func($this->_exceptionHandler, $e);
}
}
}
/**
* Export user-submitted data as an array
* @return array
*/
function export()
{
if (null === $this->_data)
{
throw new PhpXformsException('Data is empty! Consider using run() first.');
}
return (array) $this->_data;
}
/**
* Validates user-submitted XML data with XmlSchema and (optionally)
* with user-provided validator
*
* @return array Validated data
* @throws PhpXformsException
*/
protected function _validate()
{
$this->_schemaValidate();
$data = (array) simplexml_load_string($this->_xmlSource);
if (null !== $this->_dataValidator)
{
call_user_func($this->_dataValidator, $data);
}
return $data;
}
/**
* Validates user-submitted XML data with XmlSchema
*
* @throws PhpXformsException
* @access protected
*/
protected function _schemaValidate()
{
$doc = $this->_getSourceDomDocument();
ini_set('track_errors', 'On');
if ($this->_schemaAsSource)
{
$valid = @ $doc->schemaValidateSource($this->_schema);
}
else
{
$valid = @ $doc->schemaValidate($this->_schema);
}
if (! $valid)
{
throw new PhpXformsException('Schema validation error: ' . $php_errormsg);
}
}
/**
* @param mixed $ref
* @param callback $callback
* @access protected
*/
protected function _setCallback(& $ref, $callback)
{
if (! is_callable($callback))
{
throw new PhpXformsException('Invalid callback');
}
$ref = $callback;
}
/**
* Make sure we have what to validate/process.
* In case we do not have a XML document specified by user,
* $HTTP_RAW_POST_DATA is used
* @access protected
*/
protected function _ensureXmlSource()
{
if (null === $this->_xmlSource)
{
$this->_xmlSource = $this->_getRawData();
}
}
/**
* @throws PhpXformsException
* @access protected
*/
protected function _assertPostRequest()
{
if ($_SERVER['REQUEST_METHOD'] != 'POST')
{
throw new PhpXformsException('Illegal request method');
}
}
/**
* retrieve $HTTP_RAW_POST_DATA
* @throws PhpXformsException
* @access protected
*/
protected function _getRawData()
{
$this->_assertPostRequest();
if (empty($GLOBALS['HTTP_RAW_POST_DATA']))
{
throw new PhpXformsException('Couldn\'t find HTTP_RAW_POST_DATA');
}
return $GLOBALS['HTTP_RAW_POST_DATA'];
}
/**
* @return DomDocument
* @throws PhpXformsException
* @access protected
*/
protected function _getSourceDomDocument()
{
$ret = new DOMDocument();
if (! $ret->loadXml($this->_xmlSource))
{
throw new PhpXformsException('XML source doesn\'t seem to be a valid XML string');
}
return $ret;
}
}
/**
* Exceptions for PhpXforms
*/
class PhpXformsException extends Exception {
}