Location: PHPKode > scripts > flashPash > flashpash/flashPash.php
<?php

/*==-==-==-==-==-==-==-==-==-==-==-==-==-==-==+
 |            FlashPashServerClass            |
 +==-==-==-==-==-==-==-==-==-==-==-==-==-==-==+
 |              version 1.0 *RC1*             |
 |                                            |
 | last update on 2003-07-07 by si*sshnug.com |
 |                                            |
 |   Copyright (c) 2002-2003, Simon Mckenna   |
 |            All rights reserved             |
 +==-==-==-==-==-==-==-==-==-==-==-==-==-==-==+
 | source best viewed with tab space set to 2 |
 +==-==-==-==-==-==-==-==-==-==-==-==-==-==-==*/

error_reporting( E_ALL );

require_once 'adodb/adodb.inc.php';  /* ADOdb class for our database abstraction  */
require_once 'XML/Tree.php';         /* PEAR API used to build XML document tree  */

$ADODB_FETCH_MODE = ADODB_FETCH_NUM; /* return a numbered index, not named array  */

/* static properties uppercased & prefixed FP_ & word delimited by _ (underscore) */
define( 'FP_SOURCE_PHP',        0 ); /* flashPash object created for use in PHP   */
define( 'FP_SOURCE_XML',        1 ); /* flashPash is expecting XML via HTTP POST  */
define( 'FP_SOURCE_XML_SOCKET', 2 ); /* flashPash is expecting XML from a socket  */

define( 'FP_ERROR_REPORTING_DEFAULT', 0 );     /* default setting - Report error number and brief error text */
define( 'FP_ERROR_REPORTING_VERBOSE', 1 );     /* include all available information in errorText property    */
define( 'FP_ERROR_REPORTING_ONLY_NUMBER', 2 ); /* only return the error number, errorText property not set   */
define( 'FP_ERROR_REPORTING_NONE', 3 );        /* error and errorText properties are not set if error found  */

/* error types: server-side errors are 50-99, client-side errors are 1-49 */
define( 'FP_ERROR_NONE',               0 );
define( 'FP_ERROR_INITIALISING',       50 );
define( 'FP_ERROR_PARSING_XML',        51 );
define( 'FP_ERROR_DB_ACCESS',          52 );
define( 'FP_ERROR_META',               53 );
define( 'FP_ERROR_ROLE_FILE_MISSING',  60 );
define( 'FP_ERROR_ROLE_INVALID_XML',   61 );
define( 'FP_ERROR_ROLE_INVALID',       62 );
define( 'FP_ERROR_ROLE_DENIED_ALLOW',  67 );
define( 'FP_ERROR_ROLE_DENIED_DENY',   68 );
define( 'FP_ERROR_ROLE_DENIED',        69 );
define( 'FP_ERROR_UNKNOWN',            99 );

define( 'FP_LOG_HTML', 1 );  /* Used with debug property - log is html friendly file - logfile.html  */
define( 'FP_LOG_TEXT', 2 );  /* Used with debug property - log is a plain text file - logfile.txt    */

/* ADOdb standard field types supported by flashPash */
define( 'FP_ADODB_CHARACTER', 'C' ); // Character fields that should be shown in a <input type="text"> tag.
define( 'FP_ADODB_CLOB',      'X' ); // Clob (character large objects), or large text fields that should be shown in a <textarea>
define( 'FP_ADODB_DATE',      'D' ); // Date field
define( 'FP_ADODB_TIMESTAMP', 'T' ); // Timestamp field 
define( 'FP_ADODB_BOOLEAN',   'L' ); // Logical field (boolean or bit-field)
define( 'FP_ADODB_NUMERIC',   'N' ); // Numeric field. Includes decimal, numeric, floating point, and real.
define( 'FP_ADODB_INTEGER',   'I' ); // Integer field.
define( 'FP_ADODB_COUNTER',   'R' ); // Counter or Autoincrement field. Must be numeric.
define( 'FP_ADODB_BLOB',      'B' ); // Blob, or binary large objects.

/* additional static properties that get used in class */
define( 'FP_SQL_ALIAS',      '__FP__' ); /* used to create SQL alias for field. Prevents errors with same fieldname in different tables*/

/* these are the only valid values for FP_SQL_TYPE */
define( 'FP_SELECT',  'select' );
define( 'FP_INSERT',  'insert' );
define( 'FP_UPDATE',  'update' );
define( 'FP_DELETE',  'delete' );
define( 'FP_RESOLVE', 'resolve' );
define( 'FP_GENERIC', 'generic' );
define( 'FP_META',    'meta' ); 

/* properties of flashPash PHP and MX object AND attributes of the flashPash XML AND also used as keys defining the 2nd dimension of 'dp' array. */
define( 'FP_SQL_TYPE',         'SQLtype' );
define( 'FP_SQL_CONDITION',    'SQLcondition' );
define( 'FP_RECORD_LIMIT',     'recordLimit' );
define( 'FP_LIMIT_FROM',       'limitFrom' );
define( 'FP_ERROR',            'error' );
define( 'FP_ERROR_TEXT',       'errorText' );
define( 'FP_RECORD_COUNT',     'recordCount' );
define( 'FP_DATA_SOURCE',      'dataSource' );
define( 'FP_DATA_SOURCE_TYPE', 'dataSourceType' );
define( 'FP_SET_DATA_SOURCE',  'setDataSource' );
define( 'FP_CREATE_NODES',     'createNodes' );
define( 'FP_DISABLE_KEYS',     'disableKeys' );
define( 'FP_META_TYPE',        'metaType' );

/* static properties used to define the types of meta-data to be generated when SQLtype is META */
define( 'FP_META_TYPE_DATABASES', 'DB' );      /* list all available databases */
define( 'FP_META_TYPE_TABLES',    'TABLES' );  /* list all available tables in database */
define( 'FP_META_TYPE_COLUMNS',   'COLUMNS' ); /* list all columns (fields) in a single table*/
define( 'FP_META_TYPE_SQL',       'SQL' );     /* list detailed column (field) meta-data obtained SQL statement */

/* static properties only used in dp array at this stage */
define( 'FP_SQL_FIELDS', 'SQLfields' );
define( 'FP_OUTPUT',     'output' );

/* XML static properties, might have to change encoding for other languages? */
define( 'FP_VERSION',      '1.0' );
define( 'FP_CONTENT_TYPE', 'Content-Type: application/flashPash' );
define( 'FP_ENCODING',     'UTF-8' );

/* flashPash's own XML bits. */
define( 'FP_ROOT_NODE',    'flashPash' );
define( 'FP_DATA_PACKET',  'dataPacket' );
define( 'FP_ROW_NODE',     'row' );

/* static property for flashPash XML root element.  If this is set to true, then all data packets */
/* under flashPash root will be controlled by a transaction, ie. all get processed or all fail */
define( 'FP_WRAP_IN_TRANS',    'wrapInTrans' );

/* attributes of the flashPash field element */
define( 'FP_FIELD_NODE',    'field' );
define( 'FP_FIELD_NAME',    'name' );
define( 'FP_FIELD_TYPE',    'type' );
define( 'FP_FIELD_VALUE',   'value' );
define( 'FP_FIELD_PATH',    'path' );
define( 'FP_FIELD_KEY',     'key' );
define( 'FP_FIELD_OLDKEY',  'oldKey' );
define( 'FP_FIELD_JOIN',    'join' );
define( 'FP_FIELD_ALIAS',   'fieldAlias' );
define( 'FP_TABLE_ALIAS',   'tableAlias' );
define( 'FP_FIELD_COMPUTE', 'compute' );

/* static properties for flashPash.roles.xml, NB: FP_FIELD_NODE and FP_FIELD_NAME also used */
define( 'FP_ROLE_NOT_FOUND',    -1 );
define( 'FP_ROLE_NODE',     'role' );
define( 'FP_DATABASE_NODE', 'database' );
define( 'FP_TABLE_NODE',    'table' );

/* flashPash flash MX supported data source types */
define( 'FP_DATA_SOURCE_FIREFLY',   'firefly' );
define( 'FP_DATA_SOURCE_RECORDSET', 'recordset' );

/* join types supported by flashPash */
define( 'FP_JOIN_INNER',   'I' );
define( 'FP_JOIN_LEFT',    'L' );
define( 'FP_JOIN_RIGHT',   'R' );

class FlashPashServerClass
{
	/* public properties */
	var $logfile;
	var $source;

	var $debug;

	var $dbType;
	var $dbHost;
	var $dbName;
	var $dbUser;
	var $dbPass;

	var $dp;

	var $SQLtype;
	var $SQLcondition;
	var $SQLfields;
	var $SQLresult;

	var $recordCount;
	var $recordLimit;
	var $limitFrom;

	var $dataSource;
	var $dataSourceType;
	var $setDataSource;
	var $createNodes;
	var $disableKeys;
	var $metaType;

	var $error;
	var $errorText;

	var $errorReporting;
	
	var $checkRole;
	var $role;

	var $socketPort;

	/* private properties */
	var $_dpi;
	var $_qri;

	var $_fpLog;

	var $_SQL;
	var $_db;
	var $_result;

	var $_gotData;

	var $_wrapInTrans;
	
	/* constructor */
	function FlashPashServerClass( $source = NULL, $dbType = NULL, $dbHost = NULL, $dbName = NULL, $dbUser = NULL, $dbPass = NULL, $debug = false )
	{
		$this->source = $source;

		$this->dbType = $dbType;
		$this->dbHost = $dbHost;
		$this->dbName = $dbName;
		$this->dbUser = $dbUser;
		$this->dbPass = $dbPass;

		$this->debug = $debug;

		$this->checkRole = false;
		$this->role      = NULL;

		$this->errorReporting = FP_ERROR_REPORTING_DEFAULT;

		$this->logfile = 'log.flashPash';

		$this->SQLfields = array();
		$this->SQLresult = array();

		$this->dp   = array();
		$this->_dpi = 0;

		$this->_gotData = false;

		/*
		_wrapInTrans is set to false in constructor, it can't be (re)set in _initialse
		as it is set in getData, which can be called independantly of execute method
		*/
		$this->_wrapInTrans = false;
	}

	function doSelect( $SQLcond = NULL )
	{
		return $this->_doExecute( FP_SELECT, $SQLcond );
	}

	function doInsert( $SQLcond = NULL )
	{
		return $this->_doExecute( FP_INSERT, $SQLcond );
	}

	function doUpdate( $SQLcond = NULL )
	{
		return $this->_doExecute( FP_UPDATE, $SQLcond );
	}

	function doDelete( $SQLcond = NULL )
	{
		return $this->_doExecute( FP_DELETE, $SQLcond );
	}

	function doSQL( $SQLcond = NULL )
	{
		return $this->_doExecute( FP_GENERIC, $SQLcond );
	}

	function doMeta( $metaType, $SQLcond = NULL )
	{
		$oldMeta = $this->metaType;
		$this->metaType = $metaType;
		$result = $this->_doExecute( FP_META, $SQLcond );
		$this->metaType = $oldMeta;
		return ( $result );
	}

	function execute()
	{ 
		/* database check and connection only occur once in getData (limitation or feature? you be the judge :) */
		if ( $this->getData() )
		{
			/* start transaction control if requested */
			if (( $this->_wrapInTrans ) && ( method_exists( $this->_db, 'StartTrans' )))
			{
				$this->_db->StartTrans();
				$this->_debugLog( 'Started Transaction' );
			}

			/* work out how many data packets we have, if none then doSQL or doMeta was called from PHP so set to 1 */
			$dpCount = count( $this->dp );
			if ( $dpCount == 0 )
				$dpCount++;

			/* if ok then loop through each datapacket.  php source iterates once, but xml may iterate many times */
			if ( $this->error == FP_ERROR_NONE )
				for ( $this->_dpi = 0; $this->_dpi < $dpCount; $this->_dpi++ )
					if ( $this->_loadPropertiesFromDataPacket() )
						if ( $this->_checkProperties() )
							if ( $this->_checkRole() )
								if ( $this->_buildQuery() )
									if ( $this->_runQuery() )
										$this->_buildResult();

				/* complete transaction if requested */
				if (( $this->_wrapInTrans ) && ( method_exists( $this->_db, 'CompleteTrans' )))
				{
					$this->_db->CompleteTrans( true );
					$this->_debugLog( 'Completed Transaction' );
					$this->_wrapInTrans = false;
				}
			}

		$this->_finalise();

		return ( $this->error == FP_ERROR_NONE );
	}

	function dumpXML( $contentType = NULL )
	{
		$this->_dpi = 0;

		/* can't send headers more than once */
		if ( !headers_sent() )
		{
			if ( is_null( $contentType ))
				header( FP_CONTENT_TYPE );
			else
				header( $contentType );
		}

		$tree = new XML_Tree();
		$tree->version = FP_VERSION;
		$root =& $tree->addRoot( FP_ROOT_NODE );

		/* since we only store results in FP_OUTPUT, combining is easy :D */
		for ( $i = 0; $i < count( $this->dp ); $i++ )
			if ( isSet( $this->dp[ $i ][ FP_OUTPUT ] ))
				$root->addChild( NULL, $this->dp[ $i ][ FP_OUTPUT ] );

		echo $tree->get();
		
		if (( $this->debug !== false ) && @array_key_exists( FP_OUTPUT, $this->dp[ $this->_dpi ] ) && ( $this->source != FP_SOURCE_PHP ))
		{
			$temp = fopen( $this->logfile . '.xml', 'w' );
			@fwrite( $temp, $tree->get() );
			@fclose( $temp );
		}
	}

	function setField( $table, $field, $type = NULL, $useNode = false, $value = NULL )
	{
		$this->_setTableAndField( $table, $field );

		if ( !is_null( $type ))
		{
			switch ( $type )
			{
				case FP_ADODB_CHARACTER :
				case FP_ADODB_CLOB      :
				case FP_ADODB_DATE      :
				case FP_ADODB_TIMESTAMP :
				case FP_ADODB_BOOLEAN   :
				case FP_ADODB_NUMERIC   :
				case FP_ADODB_INTEGER   :
				case FP_ADODB_COUNTER   :
				case FP_ADODB_BLOB      :
					break; /* okay it matched */
				default :
					return false;
			}
			$this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_FIELD_TYPE ] = $type;
		}

		if ( $useNode )
			unset( $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_FIELD_PATH ] );
		else
			$this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_FIELD_PATH ] = "@";

		if ( !is_null( $value ))
			$this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_FIELD_VALUE ] = $value;
		else
			unset( $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_FIELD_VALUE ] );

		//$this->_debugLog( 'setField table: ' . $table . ' field: ' . $field . ' path:' . $useNode . ' type: ' . $type . ' value: ' . $value );

		return true;
	}

	function setKey( $table, $field, $key = true, $oldKeyValue = NULL )
	{
		$this->_setTableAndField( $table, $field );

		if (( $key == TRUE ) || ( strtolower( $key ) == 'true' ))
			$this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_FIELD_KEY ] = 'true';
		else
			unset( $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_FIELD_KEY ] );
		
		if ( !is_null( $oldKeyValue ))
			$this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_FIELD_OLDKEY ] = $oldKeyValue;
		else
			unset( $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_FIELD_OLDKEY ] );

		//$this->_debugLog( 'setKey table: ' . $table . ' field: ' . $field . ' key: ' . $key. ' oldKeyValue: ' . $oldKeyValue );

		return true;
	}

	function setAlias( $table, $field, $tableAlias = NULL, $fieldAlias = NULL, $compute = NULL )
	{
		if (( $field != $fieldAlias ) && ( $table != $tableAlias ))
			$this->_setTableAndField( $table, $field );

		if ( !is_null( $tableAlias ))
			$this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_TABLE_ALIAS ] = $tableAlias;
		elseif ( isset( $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_TABLE_ALIAS ] ))
			unset( $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_TABLE_ALIAS ] );

		if ( !is_null( $fieldAlias ))
		{
			/* make field alias array */
			$this->_setTableAndField( $table, $fieldAlias );

			/* copy field into field alias */
			$this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $fieldAlias ] = $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ];

			/* create a link in alias to field */
			$this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $fieldAlias ][ FP_FIELD_NAME ] = $field;

			/* set old field alias flag so we know not to use it */
			$this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_FIELD_ALIAS ] = true;

			/* remove field alias flag in the field alias */
			unset( $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $fieldAlias ][ FP_FIELD_ALIAS ] );
		}
		else
		{
			/* remove alias flag in field so we know we can use it again */
			unset( $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_FIELD_ALIAS ] );
		}

		if ( !is_null( $compute ))
		{
			/* can't have a calculation without an alias */ 
			if ( is_null( $fieldAlias ))
				return false;

			$this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $fieldAlias ][ FP_FIELD_COMPUTE ] = $compute;
		}

		//$this->_debugLog( 'setAlias table: ' . $table . ' field: ' . $field . ' tableAlias: ' . $tableAlias. ' fieldAlias: ' . $fieldAlias . ' compute: ' . $compute );

		return true;
	}

	function setJoin( $table, $field, $joinType = NULL, $joinTable = NULL, $joinField = NULL )
	{
		/*
		quick review of joins:
		- An INNER join is symmetrical, all matching pairs of rows (records) are returned.
		- An OUTER join is asymmetrical, depending on the join type, rows from either table can be included in resultset.
		- A LEFT (OUTER) join specifies that all rows from the left table not meeting the specified condition are included in the result set, and output columns from the right table are set to NULL in addition to all rows returned.
		- A RIGHT (OUTER) join specifies that all rows from the right table not meeting the specified condition are included in the result set, and output columns from the left table are set to NULL in addition to all rows returned.

		Therefore, it is possible to define the different flashPash joins as follows:
		FP_JOIN_INNER   == Return all rows in both tables that match only on the join field(s)
		FP_JOIN_LEFT    == Return all rows in the left table, but only rows in the right table that match. Unmatched rows on the right table are padded with NULL.
		FP_JOIN_RIGHT   == Return all rows in the right table, but only rows in the left table that match. Unmatched rows on the left table are padded with NULL.
		
		FULL OUTER and CROSS JOINS are not currently supported, if you need them, feel free to add code here, 
		in setJoin on client classes, in _getTablesForXXX() and _getFieldsForXXX(), or buy me a cartoon of Cooper's Pale Ale :)
		*/
		$this->_setTableAndField( $table, $field );

		/* remove join is no join type passed */
		if ( is_null( $joinType ))
		{
			unset( $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_FIELD_JOIN ] );
			return true;
		}
		elseif ( is_null( $joinTable ))
		{
			return false;
		}
		elseif ( is_null( $joinField ))
		{
			/* assume field names match between tables if joinField not passed. only do server-side, client-side increases XML size */
			$joinField = $field;
		}

		switch ( $joinType )
		{
			case FP_JOIN_INNER :
			case FP_JOIN_LEFT  :
			case FP_JOIN_RIGHT :
				break; /* okay it matched */
			default :
				return false;
		}
		$this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ][ FP_FIELD_JOIN ] = $joinType . ',' . $joinTable . ',' . $joinField;

		//$this->_debugLog( 'setJoin table: ' . $table . ' field: ' . $field . ' joinType: ' . $joinType. ' joinTable: ' . $joinTable . ' joinField: ' . $joinField );

		return true;
	}

	function deleteField( $table, $field = NULL )
	{
		/* ensure that table is in SQL fields array */
		if ( $this->findTable( $table ))
		{
			if ( is_null( $field ))
			{
				unset( $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ] );
				return true;
			}
			elseif ( $this->findField( $field, $table ))
			{
				unset( $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ] );
				return true;
			}
		}
		return false;
	}

	function findTable( $table )
	{
		return @array_key_exists( $table, $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ] );
	}

	function findField( $field, $table = NULL )
	{
		/* quit if field not defined */
		if ( !isSet( $field ) || ( !is_array( $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ] )))
			return false;

		if ( is_null( $table ))
		{
			foreach ( $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ] as $tableArray )
				if ( @array_key_exists( $field, $tableArray ))
					return true;
		}
		else
		{
			if ( @array_key_exists( $table, $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ] ))
			{
				$tableArray = $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ];
				return @array_key_exists( $field, $tableArray );
			}
		}
		/* ::To-Do:: Check if we need to add search for alias?...use [ $table ][ $field ][ FP_FIELD_NAME ] if we do */
		return false;
	}

	function encodeXML( &$XMLdoc )
	{
		/* remove any spaces between tags */
		$XMLdoc = eregi_replace( ">"."[[:space:]]+"."<","><",$XMLdoc );
		$XMLdoc = utf8_encode( $XMLdoc );
	}

	/* originally taken from www.php.net/print_r user comments */
	function showArray( $array, $string = 'contents of array' )
	{
		$out = '';
		if ( is_array( $array ) || is_object( $array ))
		{
			foreach ( $array as $key => $value )
			{
				/* format the string which stands next to the ' = '.  [] for arrays and -> for objects */
				if ( is_array( $array ))
					$new_string = $string . '[ ' . $key . ' ]';
				elseif ( is_object( $array ))
					$new_string = $string . '->' . $key;
				/* dive */
				$out .= $this->showArray( $value, $new_string );
			}
		} 
		else /* not object, not array */
		{
			$out .= $string . ' = ' . $array . chr( 10 );
		}

		return $out;
	}

	function getData()
	{
		/* initialise flashPash if this method is being called manually and not from execute method */
		$gotData = $this->_gotData;
		if ( $gotData === false )
		{
			if ( !$this->_initialise() )
				return false;
			if ( !$this->_checkdb() )
				return false;
			if ( !$this->_dbConnect() )
				return false;
		}

		$this->source = strtoupper( $this->source );
 
		if ( !isset( $this->source ) || ( is_null( $this->source )))
			return $this->_logError( FP_ERROR_INITIALISING, 'source property not set', true );
		elseif ( $this->source == FP_SOURCE_XML )
		{
			if ( !$gotData )
			{
				/* get XML from HTTP POST data using input stream (PHP 4.3+) */
				unset( $XMLdata );
				if ( version_compare( phpversion(), "4.3.0", "ge" ))
				{
					$XMLdata = @file_get_contents( "php://input" );
					$this->_debugLog( 'data read from input stream' );
				}
				elseif ( @isset( $GLOBALS[ 'HTTP_RAW_POST_DATA'] ))
				{
					/* only works if register globals are on, hence we try input stream first */
					$XMLdata = $GLOBALS[ 'HTTP_RAW_POST_DATA'];
					$this->_debugLog( 'data read from HTTP_RAW_POST_DATA' );
				}

				if ( $XMLdata === FALSE )
					return $this->_logError( FP_ERROR_INITIALISING, 'error whilst reading input stream', true );
				elseif ( !isset( $XMLdata ))
					return $this->_logError( FP_ERROR_INITIALISING, 'no data found?', true );
				else
					$this->_gotData = $this->_convertXMLtoArray( $XMLdata );
			}
			else
				$this->_debugLog( 'data already read by manual getData call' );
		}
		elseif ( $this->source == FP_SOURCE_XML_SOCKET )
		{
			if ( !$gotData )
			{
				/* get XML from socket, port number is in socketPort property */
				unset( $XMLdata );
				/* ::To-Do:: Read from socket */

				if ( $XMLdata === FALSE )
					return $this->_logError( FP_ERROR_INITIALISING, 'error whilst reading from socket', true );
				elseif ( !isset( $XMLdata ))
					return $this->_logError( FP_ERROR_INITIALISING, 'no data found?', true );
				else
					$this->_gotData = $this->_convertXMLtoArray( $XMLdata );
			}
			else
				$this->_debugLog( 'data already loaded by call to getData' );
		}
		elseif ( $this->source != FP_SOURCE_PHP )
			return $this->_logError( FP_ERROR_INITIALISING, 'source property is invalid', true );

		$this->_debugLog( 'getData okay' );
		return true;
	}

	/* private methods */ 
	function _doExecute( $SQLtype, $SQLcond = NULL )
	{
		$this->SQLtype = $SQLtype;
		if ( is_null( $SQLcond ))
		{
			$this->execute();
		}
		else
		{
			$oldSQLcond = $this->SQLcondition;
			$this->SQLcondition = $SQLcond;
			$this->execute();
			$this->SQLcondition = $oldSQLcond;
		}
		return ( $this->error == FP_ERROR_NONE );
	}

	function _initialise()
	{
		/* don't reinitialise */
		if ( $this->_gotData )
			return true;

		ob_start();

		/* need to initialise some properties regardless of source */
		$this->recordCount = 0;
		$this->error       = FP_ERROR_NONE;
		$this->errorText   = NULL;

		$this->_result = false;
		$this->_db     = false;

		$this->_dpi = 0;
		foreach ( $this->dp as $key => $value )
			$this->dp[ $key ][ FP_OUTPUT ] = NULL;

		if ( $this->source != FP_SOURCE_PHP )
		{
			$this->SQLtype        = NULL;
			$this->SQLcondition   = NULL;
			$this->recordLimit    = NULL;
			$this->limitFrom      = NULL;
			$this->dataSource     = NULL;
			$this->dataSourceType = NULL;
			$this->setDataSource  = NULL;
			$this->createNodes    = NULL;
			$this->disableKeys    = NULL;
			$this->metaType       = NULL;
		}

		/* if debugging is turned on, then open/create a new log file */
		if ( $this->debug !== false )
		{
			if (( $this->debug == FP_LOG_HTML ) || ( $this->debug == ( FP_LOG_HTML + FP_LOG_TEXT )))
			{
				$logHTML = $this->logfile . '.html';
				$this->_fpLogHTML = @fopen( $logHTML, 'w' );
				if ( $this->_fpLogHTML === false )
					return $this->_logError( FP_ERROR_INITIALISING, 'Unable to open html log file', true, $logHTML );

				$cdt = strftime( "@ %H:%M:%S on %Y-%m-%d" );
				if ( !@fwrite( $this->_fpLogHTML, '<html><head><title>flashPash log file' . '</title></head><body><font color="#000000" face="Arial, Helvetica, sans-serif"><BR>' ))
					return $this->_logError( FP_ERROR_INITIALISING, 'Unable to write log file', true, $logHTML );
			}
			if (( $this->debug == FP_LOG_TEXT ) || ( $this->debug == ( FP_LOG_HTML + FP_LOG_TEXT )))
			{
				$logText = $this->logfile . '.txt';
				$this->_fpLogText = @fopen( $logText, 'w' );
				if ( $this->_fpLogText === false )
					return $this->_logError( FP_ERROR_INITIALISING, 'Unable to text log file', true, $logText );
				$cdt = strftime( "@ %H:%M:%S on %Y-%m-%d" );
			}

			switch ( $this->source )
			{
				case FP_SOURCE_XML        : $this->_debugLog( 'flashPash initialised for XML ' . $cdt ); break;
				case FP_SOURCE_XML_SOCKET : $this->_debugLog( 'flashPash initialised for XML socket ' . $cdt ); break;
				case FP_SOURCE_PHP        : $this->_debugLog( 'flashPash initialised for PHP ' . $cdt ); break;
				default : $this->_debugLog( 'flashPash initialised from unknown source ' . $cdt );
			}
		}
		
		return true;
	}

	function _finalise()
	{
		if ( is_object( $this->_db ) && ( $this->_db !== false ))
		{
			$this->_db->Close();
			unset( $this->_db );
			$this->_debugLog( 'db closed' );
		}

		/* check contents of output buffer, it should be empty, if it's not, somethings gone wrong */
		if ( ob_get_length() > 0 )
		{
			$this->_logError( FP_ERROR_UNKNOWN, 'output buffer not empty, possible compile error?', true, ob_get_contents() );
		}

		if ( $this->debug !== false )
		{
			$cdt = strftime( "@ %H:%M:%S on %Y-%m-%d" );
			$this->_debugLog( 'flashPash finished ' . $cdt );
			
			if (( $this->debug == FP_LOG_HTML ) || ( $this->debug == ( FP_LOG_HTML + FP_LOG_TEXT )) && ( isset( $this->_fpLogHTML )))
			{
				@fwrite( $this->_fpLogHTML, '</font></body></html>' );
				@fclose( $this->_fpLogHTML );
				unset( $this->_fpLogHTML );
			}

			if (( $this->debug == FP_LOG_TEXT ) || ( $this->debug == ( FP_LOG_HTML + FP_LOG_TEXT )) && ( isset( $this->_fpLogText )))
			{
				@fclose( $this->_fpLogText );
				unset( $this->_fpLogText );
			}
		}

		$this->_dpi = 0;

		$this->_gotData = false;
	}

	function _debugLog( $message, $array = NULL, $logAsError = FALSE )
	{
		if ( $this->debug === false ) return;

		if (( $this->debug == FP_LOG_HTML ) || ( $this->debug == ( FP_LOG_HTML + FP_LOG_TEXT )) && ( isset( $this->_fpLogHTML )))
		{
			( $logAsError ) ? $prefix = 'debug><font color="#FF0000"> ' : $prefix = 'debug><font color="#000099"> ';

			$html = htmlentities( $message );

			if ( is_array( $array ))
				$html = nl2br( 'contents of array ' . $html . '<br /><font color="#0000FF">' . htmlentities( $this->showArray( $array, $html )) . '</font>' . '<br />' );
			elseif ( is_object( $array ))
				$html = nl2br( 'contents of object ' . $html . '<br /><font color="#0000FF">' . htmlentities( $this->showArray( $array, $html )) . '</font>' . '<br />' );
			elseif ( !is_null( $array ))
				$html .=  '<br /><font color="#0000FF"><pre>' . htmlentities( $array ) . '</pre></font>';
			else
				$html = nl2br( $html . '<br />' );

			@fwrite( $this->_fpLogHTML, $prefix . $html . '</font>' );
		}

		if (( $this->debug == FP_LOG_TEXT ) || ( $this->debug == ( FP_LOG_HTML + FP_LOG_TEXT )) && ( isset( $this->_fpLogText )))
		{
			if ( is_array( $array ))
				$message = 'contents of array ' . $message . chr( 10 ) . $this->showArray( $array, $message );
			elseif ( is_object( $array ))
				$message = 'contents of object ' . $message . chr( 10 ) . $this->showArray( $array, $message );
			elseif ( !is_null( $array ))
				$message = $message . chr( 10 ) . $array;

			@fwrite( $this->_fpLogText, 'debug>' . $message . chr( 10 ) );
		}
	}

	function _logError( $error, $errorText, $prepForDump = false, $verbose = NULL )
	{
		if ( $this->errorReporting != FP_ERROR_REPORTING_NONE )
		{
			$this->error = $error;
			if ( $this->errorReporting != FP_ERROR_REPORTING_ONLY_NUMBER )
			{
				$this->errorText = $errorText;
				if (( $verbose != NULL ) && ( $this->errorReporting == FP_ERROR_REPORTING_VERBOSE ))
					$this->errorText .= ' : ' . $verbose;
			}
		}

		$this->_debugLog( 'error ' . $error . ' ' . $errorText . ' ' . $verbose, NULL, true );

		if ( $prepForDump )
			$this->_convertArrayToXML();

		return false;
	}

	function _checkdb()
	{
		/* have the db connection properties been defined? */
		if ( trim( $this->dbType ) == '' ) return $this->_logError( FP_ERROR_INITIALISING, 'dbType property not set', true );
		if ( trim( $this->dbHost ) == '' ) return $this->_logError( FP_ERROR_INITIALISING, 'dbHost property not set', true );
		if ( trim( $this->dbName ) == '' ) return $this->_logError( FP_ERROR_INITIALISING, 'dbName property not set', true );
		if ( trim( $this->dbUser ) == '' ) return $this->_logError( FP_ERROR_INITIALISING, 'dbUser property not set', true );

		$this->_debugLog( 'checkdb okay' );
		return true;
	}

	function _dbConnect()
	{ 
		/* bail if already connected (getData was called independantly of execute) */
		if ( is_object( $this->_db ) && ( $this->_db !== false ))
		{
			$this->_debugLog( 'db already connected' );
			return true;
		}

		$ok = false;
		$this->_db = &ADONewConnection( $this->dbType );

		/* ::To-Do:: If transaction support requested check if db can handle it */
		switch ( $this->dbType )
		{
			case 'odbc' :
			case 'odbc_mssql' :
			case 'odbc_oracle' :
			case 'sqlanywhere' :
				$ok = @$this->_db->PConnect( $this->dbName, $this->dbUser, $this->dbPass );
				break;

			case 'ibase' :
			case 'firebird' :
			case 'borland_ibase' :
				$ok = @$this->_db->PConnect( $this->dbHost . ':' . $this->dbName, $this->dbUser, $this->dbPass );
				if ( $ok )
				{
					ibase_timefmt( "%Y-%m-%d %H:%M:%S", IBASE_TIMESTAMP ); /* ADOdb sets this to %Y-%m-%d  WTF? :) */
					ibase_timefmt( "%Y-%m-%d", IBASE_DATE );
					ibase_timefmt( "%H:%M:%S", IBASE_TIME );
				}
				break;
				
			default : 
				$ok = @$this->_db->PConnect( $this->dbHost, $this->dbUser, $this->dbPass, $this->dbName );
		}

		if ( !$ok )
			if ( $this->SQLtype != FP_GENERIC )
				return $this->_logError( FP_ERROR_DB_ACCESS, 'unable to get connection to database', true, $this->_db->ErrorMsg() );
			else
				$this->_debugLog( 'warning> unable to get connection to database: ' . $this->_db->ErrorMsg() );
		else
			$this->_debugLog( 'db connected' );

		return true;
	}

	function _initialiseDataPacket()
	{
		/* reset properties for when multiple datapackets are sent */
		$this->recordCount = 0;
		$this->error       = FP_ERROR_NONE;
		$this->errorText   = NULL;
		$this->SQLresult   = array();
		$this->_result     = false;

		$this->_SQL = array();
		$this->_qri = 0;
	}

	function _loadPropertiesFromDataPacket()
	{
		$this->_initialiseDataPacket();

		if ( @array_key_exists( FP_SQL_FIELDS, $this->dp[ $this->_dpi ] ))
			$this->SQLfields = $this->dp[ $this->_dpi ][ FP_SQL_FIELDS ];

		//$this->_debugLog( 'SQLfields: ', $this->SQLfields );

		if ( $this->source != FP_SOURCE_PHP )
		{
			if ( isset( $this->dp[ $this->_dpi ][ FP_SQL_TYPE ] ))         $this->SQLtype        = $this->dp[ $this->_dpi ][ FP_SQL_TYPE ];
			if ( isset( $this->dp[ $this->_dpi ][ FP_SQL_CONDITION ] ))    $this->SQLcondition   = $this->dp[ $this->_dpi ][ FP_SQL_CONDITION ];
			if ( isset( $this->dp[ $this->_dpi ][ FP_RECORD_LIMIT ] ))     $this->recordLimit    = $this->dp[ $this->_dpi ][ FP_RECORD_LIMIT ];
			if ( isset( $this->dp[ $this->_dpi ][ FP_LIMIT_FROM ] ))       $this->limitFrom      = $this->dp[ $this->_dpi ][ FP_LIMIT_FROM ];
			if ( isset( $this->dp[ $this->_dpi ][ FP_DATA_SOURCE ] ))      $this->dataSource     = $this->dp[ $this->_dpi ][ FP_DATA_SOURCE ];
			if ( isset( $this->dp[ $this->_dpi ][ FP_DATA_SOURCE_TYPE ] )) $this->dataSourceType = $this->dp[ $this->_dpi ][ FP_DATA_SOURCE_TYPE ];
			if ( isset( $this->dp[ $this->_dpi ][ FP_SET_DATA_SOURCE ] ))  $this->setDataSource  = $this->dp[ $this->_dpi ][ FP_SET_DATA_SOURCE ];
			if ( isset( $this->dp[ $this->_dpi ][ FP_CREATE_NODES ] ))     $this->createNodes    = $this->dp[ $this->_dpi ][ FP_CREATE_NODES ];
			if ( isset( $this->dp[ $this->_dpi ][ FP_DISABLE_KEYS ] ))     $this->disableKeys    = $this->dp[ $this->_dpi ][ FP_DISABLE_KEYS ];
			if ( isset( $this->dp[ $this->_dpi ][ FP_META_TYPE ] ))        $this->metaType       = $this->dp[ $this->_dpi ][ FP_META_TYPE ];
		}

		/* hint: This is a good spot to add other properties if you want to debug them...uncomment out as needed */
		//$this->_debugLog( 'dbType: ' . $this->dbType . ' SQLtype: ' . $this->SQLtype . ' SQLcondition: ' . $this->SQLcondition ); 
		//$this->_debugLog( 'dbName: ' . $this->dbName . ' dbUser: ' . $this->dbUser . ' dbPass: ' . $this->dbPass ); 
		//$this->_debugLog( 'role: ' . $this->role . ' checkRole: ' . $this->checkRole . ' errorReporting: ' . $this->errorReporting ); 

		return true;
	}

	function _checkProperties()
	{
		if (( $this->debug !== false )      &&
		    ( $this->debug != FP_LOG_HTML ) &&
		    ( $this->debug != FP_LOG_TEXT ) &&
		    ( $this->debug != ( FP_LOG_HTML + FP_LOG_TEXT )))
			return $this->_logError( FP_ERROR_INITIALISING, 'Invalid debug property ', true, $this->debug );

		if ( empty( $this->SQLtype ))
		{
			return $this->_logError( FP_ERROR_INITIALISING, 'SQLtype property not set', true );
		}
		elseif ( !(( $this->SQLtype == FP_SELECT )   ||
							 ( $this->SQLtype == FP_INSERT )   ||
							 ( $this->SQLtype == FP_UPDATE )   ||
							 ( $this->SQLtype == FP_DELETE )   ||
							 ( $this->SQLtype == FP_RESOLVE )  ||
							 ( $this->SQLtype == FP_GENERIC )  ||
							 ( $this->SQLtype == FP_META )))
		{
			return $this->_logError( FP_ERROR_INITIALISING, 'SQLtype property is incorrectly defined', true );
		}

		if (( !empty( $this->recordLimit )) && ( !is_numeric( $this->recordLimit )))
			return $this->_logError( FP_ERROR_INITIALISING, 'recordLimit property was set but is not an integer', true, $this->recordLimit );

		if (( !empty( $this->limitFrom )) && ( !is_numeric( $this->limitFrom )))
			return $this->_logError( FP_ERROR_INITIALISING, 'limitFrom property was set but is not an integer', true, $this->limitFrom );

		/* is SQLfields populated? required for SELECT/UPDATE/INSERT queries */
		if ( count( $this->SQLfields ) == 0 )
			if  (( $this->SQLtype != FP_DELETE ) && ( $this->SQLtype != FP_GENERIC ) && ( $this->SQLtype != FP_META ))
				return $this->_logError( FP_ERROR_INITIALISING, 'no fields were set', true );

		if ( $this->SQLtype == FP_META )
		{
			if ( empty( $this->metaType ))
				return $this->_logError( FP_ERROR_INITIALISING, 'no metaType property set', true );
			elseif (( $this->metaType != FP_META_TYPE_DATABASES ) &&
							( $this->metaType != FP_META_TYPE_TABLES )    &&
							( $this->metaType != FP_META_TYPE_COLUMNS )   &&
							( $this->metaType != FP_META_TYPE_SQL ))
				return $this->_logError( FP_ERROR_INITIALISING, 'invalid metaType property', true, $this->metaType );
			else if ((( $this->metaType == FP_META_TYPE_COLUMNS ) || ( $this->metaType == FP_META_TYPE_SQL )) && ( empty( $this->SQLcondition )))
				return $this->_logError( FP_ERROR_INITIALISING, 'Required parameter or property missing for meta-data request', true );
		}

		if ( !empty( $this->dataSourceType ) &&
		    (( $this->dataSourceType != FP_DATA_SOURCE_FIREFLY ) && ($this->dataSourceType != FP_DATA_SOURCE_RECORDSET)))
			return $this->_logError( FP_ERROR_INITIALISING, 'Invalid dataSourceType property', true, $this->dataSourceType );

		if (( $this->errorReporting != FP_ERROR_REPORTING_DEFAULT )     &&
		    ( $this->errorReporting != FP_ERROR_REPORTING_VERBOSE )     &&
		    ( $this->errorReporting != FP_ERROR_REPORTING_ONLY_NUMBER ) &&
		    ( $this->errorReporting != FP_ERROR_REPORTING_NONE ))
			return $this->_logError( FP_ERROR_INITIALISING, 'Invalid errorReporting property', true, $this->errorReporting );
		
		$this->_debugLog( 'properties check okay for data packet ' . $this->_dpi );

		return true;
	}

	function _checkRole()
	{
		/* first check if we have role permissions turned on */
		if ( !$this->checkRole )
			return true;

		/* load roles file using flashPash.php current file location */
		if ( !file_exists( dirname( __FILE__ ) . DIRECTORY_SEPARATOR .'flashPash.roles.xml' ))
			return $this->_logError( FP_ERROR_ROLE_FILE_MISSING, 'flashPash.roles.xml file not found', true );

		/* parse role file into an XML object using XML_Tree class */
		$roleXML = new XML_Tree( dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'flashPash.roles.xml' );
		$roleTree =& $roleXML->getTreeFromFile();
		
		if ( PEAR::isError( $roleTree ))
			return $this->_logError( FP_ERROR_ROLE_INVALID_XML, 'flashPash.roles.xml not a valid XML document', true );

		if ( strtolower( $roleTree->name ) != strtolower( FP_ROOT_NODE ))
			return $this->_logError( FP_ERROR_ROLE_INVALID_XML, 'role root node invalid', true, 'found ' . $roleTree->name . ' but was expecting ' . FP_ROOT_NODE );

		/* find role in role node and then test for validity */
		$roleIndex = $this->_searchRoleNode( $this->role, $roleTree, FP_ROLE_NODE );
		if ( $roleIndex === false )
			return false;  // Error gets set by _searchRoleNode so we can just bail
		elseif ( $roleIndex == FP_ROLE_NOT_FOUND )
			return $this->_logError( FP_ERROR_ROLE_INVALID, 'role check failed', true,  'user ' . $this->role . ' was not found' );
		else
			$roleNode = $roleTree->children[ $roleIndex ];

		/* do the same for database as for role, only this time it's okay to find nothing */
		$dbIndex = $this->_searchRoleNode( $this->dbName, $roleNode, FP_DATABASE_NODE );
		/* if no db was found, we know that role node checked out, so we can safely return */ 
		if ( $dbIndex === false )
			return false;  /* role failed, so bail immediately */
		elseif ( $dbIndex == FP_ROLE_NOT_FOUND )
			return true;   /* there won't be any table nodes, so we can safely bail */
		else
			$dbNode = $roleNode->children[ $dbIndex ];

		/* if SQLtype is GENERIC, then we can exit now as roles for this only apply at role and database level */
		if ( $this->SQLtype == FP_GENERIC )
			return true;
			
		/* check all tables in SQLfields to see if roles are defined, it's okay to find nothing */
		foreach ( $this->SQLfields as $tableName => $tableArray )
		{
			$tableIndex = $this->_searchRoleNode( $tableName, $dbNode, FP_TABLE_NODE );
			if ( $tableIndex === false )
				return false;  /* role failed, so bail immediately */
			elseif ( $dbIndex == FP_ROLE_NOT_FOUND )
				return true;   /* there won't be any field nodes, so we can safely bail */
		}

		/* if SQLtype is DELETE, since deletes occur at a table (row) level, we can exit now as role only at role and database and table level */
		if ( $this->SQLtype == FP_DELETE )
			return true;

		/* finally, we check all fields to see if roles are set, also okay to find nothing */
		foreach ( $this->SQLfields as $tableName => $tableArray )
		{
			$tableIndex = $this->_searchRoleNode( $tableName, $dbNode, FP_TABLE_NODE );
			if ( $tableIndex >= 0 )
			{
				$tableNode  = $dbNode->children[ $tableIndex ];
				foreach ( $tableArray as $fieldName => $fieldArray )
				{
					$fieldIndex = $this->_searchRoleNode( $fieldName, $tableNode, FP_FIELD_NODE );
					if ( $fieldIndex === false )
						return false;  /* role failed, so bail immediately */
				}
			}
		}

		$this->_debugLog( 'role check okay' );

		return true;
	}

	function _searchRoleNode( $searchFor, $node, $level )
	{
		if ( strtolower( get_class( $node )) == 'xml_tree_node' )
			foreach ( $node->children as $index => $childNode )
				if ( @strtolower( $childNode->attributes[ FP_FIELD_NAME ] ) == strtolower( $searchFor ))
					if ( @strtolower( $childNode->attributes[ $this->SQLtype ] ) == 'true' )
					{
						/* if SQLtype is FP_META, check that ALLOW contents are in MetaType property */
						if ( $this->SQLtype == FP_META )
						{
							/* if METAALLOW attribute exists on same node, check that contents are in SQLcondition property */
							if ( @isset( $childNode->attributes[ FP_META . 'Allow' ] ))
								if (( @array_search( strtolower( $this->metaType ), explode( ',', strtolower( $childNode->attributes[ 'metaAllow' ] )))) === false )
									return $this->_logError( FP_ERROR_ROLE_DENIED_ALLOW, 'access denied', true, 'type=' . $this->SQLtype . ' node=' . $level . ' value=' . $searchFor );

							/* now check that if DENY attribute exists, contents are not in MetaType property */
							if ( @isset( $childNode->attributes[ FP_META . 'Deny' ] ))
								if (( @array_search( strtolower( $this->metaType ), explode( ',', strtolower( $childNode->attributes[ 'metaDeny' ] )))) !== false )
									return $this->_logError( FP_ERROR_ROLE_DENIED_DENY, 'access denied', true, 'type=' . $this->SQLtype . ' node=' . $level . ' value=' . $searchFor );

							/* if FP_META_TYPE_SQL, see if GENERICALLOW or GENERICDENY attributes exist, if they do, validate against SQLcondition */
							if ( $this->metaType == FP_META_TYPE_SQL )
							{
								if ( @isset( $childNode->attributes[ FP_GENERIC . 'Allow' ] ))
								{
									$allowArray = explode( ',', strtolower( $childNode->attributes[ FP_GENERIC . 'Allow' ] ));
									foreach ( $allowArray as $allowValue )
										if (( @strpos( strtolower( $this->SQLcondition ), $allowValue )) === false )
											return $this->_logError( FP_ERROR_ROLE_DENIED_ALLOW, 'access denied', true, 'type=' . $this->SQLtype . ' node=' . $level . ' value=' . $searchFor );
								}
								if ( @isset( $childNode->attributes[ FP_GENERIC . 'Deny' ] ))
								{
									$denyArray = explode( ',', strtolower( $childNode->attributes[ FP_GENERIC . 'Deny' ] ));
									foreach ( $denyArray as $denyValue )
										if (( @strpos( strtolower( $this->SQLcondition ), $denyValue )) !== false )
											return $this->_logError( FP_ERROR_ROLE_DENIED_DENY, 'access denied', true, 'type=' . $this->SQLtype . ' node=' . $level . ' value=' . $searchFor );
								}
							}
						}
						else /* SQLtype is not FP_META */
						{
							/* if %SQLtype%ALLOW attribute exists on same node, check that contents are in SQLcondition property */
							if ( @isset( $childNode->attributes[ $this->SQLtype . 'Allow' ] ))
							{
								/* check that ALLOW contents are in SQLcondition property */
								$allowArray = explode( ',', strtolower( $childNode->attributes[ $this->SQLtype . 'Allow' ] ));
								foreach ( $allowArray as $allowValue )
									if (( @strpos( strtolower( $this->SQLcondition ), $allowValue )) === false )
										return $this->_logError( FP_ERROR_ROLE_DENIED_ALLOW, 'access denied', true, 'type=' . $this->SQLtype . ' node=' . $level . ' value=' . $searchFor );
							}

							/* if %SQLtype%DENY attribute exists on same node, check that contents are not in SQLcondition property */
							if ( @isset( $childNode->attributes[ $this->SQLtype . 'Deny' ] ))
							{
								/* check that DENY contents are not in SQLcondition property */
								$denyArray = explode( ',', strtolower( $childNode->attributes[ $this->SQLtype . 'Deny' ] ));
								foreach ( $denyArray as $denyValue )
									if (( @strpos( strtolower( $this->SQLcondition ), $denyValue )) !== false )
										return $this->_logError( FP_ERROR_ROLE_DENIED_DENY, 'access denied', true, 'type=' . $this->SQLtype . ' node=' . $level . ' value=' . $searchFor );
							}
						}
						/* okay! Everything checks out, so return the position of the node to test next child */
						return $index;
					}
					else
					{
						return $this->_logError( FP_ERROR_ROLE_DENIED, 'access denied', true, 'type=' . $this->SQLtype . ' node=' . $level . ' value=' . $searchFor );
					}
		/* not found so don't let through */
		return FP_ROLE_NOT_FOUND;
	}
	
	function _buildQuery()
	{
		$buildOkay = false;

		//if ( count( $this->SQLfields ) > 0 )
		//	$this->_debugLog( 'SQLfields', $this->SQLfields );

		switch ( $this->SQLtype )
		{
			case FP_INSERT :
				$tableArray = $this->_getTablesForInsert();
				foreach ( $tableArray as $table )
				{
					$buildOkay = $this->_buildInsertQuery( $table );
					if ( !$buildOkay )
						return false;
					$this->_qri++;
				}
				break;

				case FP_UPDATE :
				$tableArray = $this->_getTablesForUpdate();
				foreach ( $tableArray as $table )
				{
					$buildOkay = $this->_buildUpdateQuery( $table );
					if ( !$buildOkay )
						return false;
					$this->_qri++;
				}
				break;

			case FP_DELETE :
				$tableArray = $this->_getTablesForDelete();
				foreach ( $tableArray as $table )
				{
					$buildOkay = $this->_buildDeleteQuery( $table );
					if ( !$buildOkay )
						return false;
					$this->_qri++;
				}
				break;

			case FP_SELECT  : $buildOkay = $this->_buildSelectQuery();  break;
			case FP_GENERIC : $buildOkay = $this->_buildGenericQuery(); break;
			case FP_META    : $buildOkay = $this->_buildMetaQuery();    break;
		}

		if ( !$buildOkay )
			return false;

		if ( count( $this->_SQL ) > 0 )
			$this->_debugLog( '_SQL', $this->_SQL );

		$this->_debugLog( 'query build okay' );
		return true;
	}

	function _buildSelectQuery()
	{
		$fields = $this->_getFieldsForSelect( false );
		if ( $fields !== false )
			$this->_SQL[ $this->_qri ]  = 'SELECT ' . $fields;
		else
			return false;

		$tables = $this->_getTablesForSelect( true );
		if ( $tables !== false )
			$this->_SQL[ $this->_qri ] .= chr( 10 ) . 'FROM ' . $tables;
		else
			return false;

		$keys = $this->_getFieldsForSelect( true );
		if ( $keys !== false )
			$this->_SQL[ $this->_qri ] .= 'WHERE ' . $keys;
		/* no else as it's okay to have no `where` in sql */

		$this->_addSQLcondition();

		return true;
	}

	function _buildInsertQuery( $table )
	{
		$fields = $this->_getFieldsForInsert( $table, false );
		if ( $fields !== false )
			$this->_SQL[ $this->_qri ] = 'INSERT INTO ' . $table . ' ( ' . $fields . ' )' . chr( 10 );
		else
			$this->_SQL[ $this->_qri ] = 'INSERT INTO ' . $table . chr( 10 );

		$values = $this->_getFieldsForInsert( $table, true );
		if ( $values !== false ) 
			$this->_SQL[ $this->_qri ] .= 'VALUES ( ' . $values . ' )';

		$this->_addSQLcondition();

		return true;
	}

	function _buildUpdateQuery( $table )
	{
		$this->_SQL[ $this->_qri ] = 'UPDATE ' . $table;

		$set = $this->_getFieldsForUpdate( $table, false );
		if ( $set !== false )
			$this->_SQL[ $this->_qri ] .= chr( 10 ) . 'SET ' . $set;

		$keys = $this->_getFieldsForUpdate( $table, true );
		if ( $keys !== false )
			$this->_SQL[ $this->_qri ] .= chr( 10 ) . 'WHERE ' . $keys;

		$this->_addSQLcondition();

		return true;
	}

	function _buildDeleteQuery( $table )
	{
		$this->_SQL[ $this->_qri ] = 'DELETE FROM ' . $table;

		$keys = $this->_getFieldsForDelete( $table );
		if ( $keys !== false )
			$this->_SQL[ $this->_qri ] .= chr( 10 ) . ' WHERE ' . $keys;

		$this->_addSQLcondition();

		return true;
	}

	function _buildGenericQuery()
	{
		$this->_addSQLcondition();

		return true;
	}

	function _buildMetaQuery()
	{
		if ( $this->metaType == FP_META_TYPE_SQL )
		{
			$this->_addSQLcondition();
			/* look for any occurances of FP_SQL_ALIAS in query and if found convert to enable meta-data extraction */
			/* ie. tableName__FP__fieldName becomes tableName.fieldName tableName__FP__fieldName */
			/* this is used since most db's don't return table name as part of query result. */
			if ( strpos( strtoupper( $this->_SQL[ $this->_qri ] ), FP_SQL_ALIAS ) !== false )
			{
			/* s'cuz the obfuscation ;) but this swaps CR LF with spaces and then converts query into an array */
				$tempArray = explode( ' ', strtr( strtr( $this->_SQL[ $this->_qri ], chr( 10 ), chr( 32 )), chr( 13 ), chr( 32 ))); // zap!
				for ( $i = 0 ; $i < count( $tempArray ); $i++ )
				{
					$testValue = $tempArray[ $i ];
					if ( strpos( $testValue, ',' ) == ( strlen( $testValue ) - 1 ))
					{
						$addComma  = ',';
						$testValue = substr( $testValue, 0, ( strlen( $testValue ) - 1 ));
					}
					else
					{
						$addComma = '';
					}

					if ( strpos( strtoupper( $testValue ), FP_SQL_ALIAS ) !== false )
					{
						$tableName = substr( $testValue, 0, strpos( strtoupper( $testValue ), FP_SQL_ALIAS ));
						$fieldName = substr( $testValue, strpos( strtoupper( $testValue ), FP_SQL_ALIAS ) + strlen( FP_SQL_ALIAS ));
						$oldField  = $tableName . FP_SQL_ALIAS . $fieldName;
						$newField  = $tableName . '.' . $fieldName. ' ' . $tableName . FP_SQL_ALIAS . $fieldName;
						$tempArray[ $i ] = str_replace( $oldField, $newField, $testValue . $addComma );
					}
				}
				$this->_SQL[ $this->_qri ] = implode ( ' ', $tempArray ); /* kapow! */
			}
		}
		return true;
	}

	function _setTableAndField( $table, $field )
	{
		if ( !$this->findTable( $table ))
			$this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ] = array();

		if ( !$this->findField( $field, $table ))
			$this->dp[ $this->_dpi ][ FP_SQL_FIELDS ][ $table ][ $field ] = array();
	}

	function _getFieldsForSelect( $returnKeys = false )
	{
		$return = array();
		foreach ( $this->SQLfields as $table => $tableArray )
			foreach ( $tableArray as $field => $fieldArray )
			{
				unset( $result );
				( count( $return ) > 0 ) ? $prefix = chr( 10 ) : $prefix = '';
				( isset( $fieldArray[ FP_TABLE_ALIAS ] )) ? $useTable = $fieldArray[ FP_TABLE_ALIAS ] : $useTable = $table;
				( isset( $fieldArray[ FP_FIELD_NAME ] ))  ? $useField = $fieldArray[ FP_FIELD_NAME ]  : $useField = $field;

				if (( !$returnKeys ) && ( @$fieldArray[ FP_FIELD_ALIAS ] !== true )) /* confusing, but exclude old fields now aliased */
				{
					if ( isset( $fieldArray[ FP_FIELD_COMPUTE ] ))
						$result = $prefix . $fieldArray[ FP_FIELD_COMPUTE ] . ' AS ' . $useTable . FP_SQL_ALIAS . $field;
					else
						$result = $prefix . $useTable . '.' . $useField . ' AS ' . $useTable . FP_SQL_ALIAS . $field;
				}
				elseif (( $returnKeys === true ) && ( @$fieldArray[ FP_FIELD_KEY ] == 'true' )) /* only want keys */
				{
					if ( isset( $fieldArray[ FP_FIELD_OLDKEY ] ))
						$result = $prefix . $useTable . '.' . $useField . ' = ' . $this->_checkFieldValue( $table, $field, $fieldArray[ FP_FIELD_OLDKEY ], @$fieldArray[ FP_FIELD_TYPE ] );
					elseif ( isset( $fieldArray[ FP_FIELD_VALUE ] ))
						$result = $prefix . $useTable . '.' . $useField . ' = ' . $this->_checkFieldValue( $table, $field, $fieldArray[ FP_FIELD_VALUE ], @$fieldArray[ FP_FIELD_TYPE ] );
				}
				if ( isset( $result ))
					array_push( $return, $result );
			}

		if ( count( $return ) > 0 )
		{
			if ( !$returnKeys )
				return implode( ', ', $return ); /* SELECT */
			else
				return implode( ' AND ', $return ); /* WHERE */
		}
		else
			return false;
	}

	function _getFieldsForInsert( $table, $returnValues = false )
	{
		$return = array();
		foreach ( $this->SQLfields[ $table ] as $field => $fieldArray )
			if ( isset( $fieldArray[ FP_FIELD_VALUE ] ))
			{
				/* ::To-Do:: Still need to determine if it's best to grab the value of a joined key field or just current value? */
				if ( !$returnValues ) /* INSERT */
					array_push( $return, $field );
				else /* VALUES */
					array_push( $return, $this->_checkFieldValue( $table, $field, $fieldArray[ FP_FIELD_VALUE ], @$fieldArray[ FP_FIELD_TYPE ] ));
			}
			else
			{
				/*
				field value isn't set, but it may be joined to another field,
				if it is and that field has a value, include field in insert. 
				*/
				foreach ( $this->SQLfields as $joinTable => $tableArray )
					if ( $joinTable != $table )
						foreach ( $this->SQLfields[ $joinTable ] as $joinField => $joinFieldArray )
							if ( isset( $joinFieldArray[ FP_FIELD_JOIN ] ))
							{
								$join = explode( ',', $joinFieldArray[ FP_FIELD_JOIN ] );
								@$compareTable = $join[ 1 ];
								@$compareField = $join[ 2 ];
								/* only set value if table and field match our current loop */
								if (( $compareTable == $table ) && ( $compareField == $field ))
									if ( isset( $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_VALUE ] ))
									{
										if ( !$returnValues ) /* INSERT */
											array_push( $return, $field );
										else /* VALUES */
											array_push( $return, $this->_checkFieldValue( $table, $field, $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_VALUE ], @$fieldArray[ FP_FIELD_TYPE ] ));
										break;
									}
							}
			}
		if ( count( $return ) > 0 )
			return implode( ', ', $return );
		else
			return false;
	}

	function _getFieldsForUpdate( $table, $returnKeys = false )
	{
		$return = array();
		foreach ( $this->SQLfields[ $table ] as $field => $fieldArray )
		{
			unset( $result );
			( count( $return ) > 0 ) ? $prefix = chr( 10 ) : $prefix = '';
			if ( !$returnKeys ) /* SET */
			{
				/* only include key fields if it's value has changed, but always include non-key field whose value has changed */
				if ((( @$fieldArray[ FP_FIELD_KEY ] == 'true' ) && isset( $fieldArray[ FP_FIELD_OLDKEY ] )) ||
				    ( !isset( $fieldArray[ FP_FIELD_KEY ] ) && isset( $fieldArray[ FP_FIELD_VALUE ] )))
					$result = $prefix . $table . '.' . $field . ' = ' . $this->_checkFieldValue( $table, $field, $fieldArray[ FP_FIELD_VALUE ], @$fieldArray[ FP_FIELD_TYPE ] );
				else
					/*
					search fields in all other tables except this one to see if another field joins it,
					if we find one and that field's old key value has changed, use it's old key value instead.
					*/
					foreach ( $this->SQLfields as $joinTable => $tableArray )
						if ( $joinTable != $table )
							foreach ( $this->SQLfields[ $joinTable ] as $joinField => $joinFieldArray )
								if ( isset( $joinFieldArray[ FP_FIELD_JOIN ] ))
								{
									$join = explode( ',', $joinFieldArray[ FP_FIELD_JOIN ] );
									@$compareTable = $join[ 1 ];
									@$compareField = $join[ 2 ];
									/* only set value if table and field match our current loop */
									if (( $compareTable == $table ) && ( $compareField == $field ))
										if ( isset( $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_OLDKEY ] ))
										{
											$result = $prefix . $table . '.' . $field . ' = ' . $this->_checkFieldValue( $table, $field, $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_VALUE ], @$fieldArray[ FP_FIELD_TYPE ] );
											break;
										}
								}
			}
			else /* WHERE */
			{
				if ( isset( $fieldArray[ FP_FIELD_KEY ] ))
				{
					if ( isset( $fieldArray[ FP_FIELD_OLDKEY ] ))
						$result = $prefix . $table . '.' . $field . ' = ' . $this->_checkFieldValue( $table, $field, $fieldArray[ FP_FIELD_OLDKEY ], @$fieldArray[ FP_FIELD_TYPE ] );
					elseif ( isset( $fieldArray[ FP_FIELD_VALUE ] ))
						$result = $prefix . $table . '.' . $field . ' = ' . $this->_checkFieldValue( $table, $field, $fieldArray[ FP_FIELD_VALUE ], @$fieldArray[ FP_FIELD_TYPE ] );
					/*
					before adding value, check if field is joined to another table, or a field in another table
					is joined to this one, if it is and that field is a key field whose old value has changed,
					then use the joined field value instead of the value from this field, but only do this if
					this fields isn't a key field whose old value has changed...ayekarumba! :)
					*/
					else
					{
						if ( isset( $fieldArray[ FP_FIELD_JOIN ] ))
						{
							$join = explode( ',', $fieldArray[ FP_FIELD_JOIN ] );
							@$joinTable = $join[ 1 ];
							@$joinField = $join[ 2 ];
							if ( isset( $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_OLDKEY ] ))
								$result = $prefix . $table . '.' . $field . ' = ' . $this->_checkFieldValue( $table, $field, $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_VALUE ], @$fieldArray[ FP_FIELD_TYPE ] );
						}
						else /* search fields in all other tables except this one to see if another field joins it */
						{
							foreach ( $this->SQLfields as $joinTable => $tableArray )
								if ( $joinTable != $table )
									foreach ( $this->SQLfields[ $joinTable ] as $joinField => $joinFieldArray )
										if ( isset( $joinFieldArray[ FP_FIELD_JOIN ] ))
										{
											$join = explode( ',', $joinFieldArray[ FP_FIELD_JOIN ] );
											@$compareTable = $join[ 1 ];
											@$compareField = $join[ 2 ];
											/* only set value if table and field match our current loop, and that field's old key has changed */
											if (( $compareTable == $table ) && ( $compareField == $field ))
											{
												if ( isset( $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_OLDKEY ] ))
												{
													$result = $prefix . $table . '.' . $field . ' = ' . $this->_checkFieldValue( $table, $field, $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_OLDKEY ], @$fieldArray[ FP_FIELD_TYPE ] );
													break;
												}
												elseif ( isset( $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_VALUE ] ))
												{
													$result = $prefix . $table . '.' . $field . ' = ' . $this->_checkFieldValue( $table, $field, $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_VALUE ], @$fieldArray[ FP_FIELD_TYPE ] );
													break;
												}
											}
										}
						}
					}
				}
			}
			if ( isset( $result ))
				array_push( $return, $result );
		}
		if ( count( $return ) > 0 )
		{
			if ( !$returnKeys )
				return implode( ', ', $return ); /* UPDATE */
			else
				return implode( ' AND ', $return ); /* SET */
		}
		else
			return false;
	}

	function _getFieldsForDelete( $table )
	{
		$return = array();
		foreach ( $this->SQLfields[ $table ] as $field => $fieldArray )
		{
			unset( $result );
			( count( $return ) > 0 ) ? $prefix = chr( 10 ) : $prefix = '';
			if ( isset( $fieldArray[ FP_FIELD_KEY ] ))
			{
				if ( isset( $fieldArray[ FP_FIELD_VALUE ] ))
					$result = $prefix . $table . '.' . $field . ' = ' . $this->_checkFieldValue( $table, $field, $fieldArray[ FP_FIELD_VALUE ], @$fieldArray[ FP_FIELD_TYPE ] );
				/*
				before adding value, check if field is joined to another table, or a field in another table
				is joined to this one, if it is and that field is a key field whose old value has changed,
				then use the joined field value instead of the value from this field, but only do this if
				this fields isn't a key field whose old value has changed...ayekarumba! :)
				*/
				else
				{
					if ( isset( $fieldArray[ FP_FIELD_JOIN ] ))
					{
						$join = explode( ',', $fieldArray[ FP_FIELD_JOIN ] );
						@$joinTable = $join[ 1 ];
						@$joinField = $join[ 2 ];
						if ( isset( $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_VALUE ] ))
							$result = $prefix . $table . '.' . $field . ' = ' . $this->_checkFieldValue( $table, $field, $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_VALUE ], @$fieldArray[ FP_FIELD_TYPE ] );
					}
					else /* search fields in all other tables except this one to see if another field joins it */
					{
						foreach ( $this->SQLfields as $joinTable => $tableArray )
							if ( $joinTable != $table )
								foreach ( $this->SQLfields[ $joinTable ] as $joinField => $joinFieldArray )
									if ( isset( $joinFieldArray[ FP_FIELD_JOIN ] ))
									{
										$join = explode( ',', $joinFieldArray[ FP_FIELD_JOIN ] );
										@$compareTable = $join[ 1 ];
										@$compareField = $join[ 2 ];
										/* only set value if table and field match our current loop, and that field's old key has changed */
										if (( $compareTable == $table ) && ( $compareField == $field ))
											if ( isset( $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_VALUE ] ))
											{
												$result = $prefix . $table . '.' . $field . ' = ' . $this->_checkFieldValue( $table, $field, $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_VALUE ], @$fieldArray[ FP_FIELD_TYPE ] );
												break;
											}
									}
					}
				}
			}
			if ( isset( $result ))
				array_push( $return, $result );
		}
		if ( count( $return ) > 0 )
			return implode( ' AND ', $return );
		else
			return false;
	}

	function _getTablesForSelect()
	{
		$tables = array();
		$joins  = array();
		$joinedTables = array();

		foreach( $this->SQLfields as $table => $tableArray )
		{
			foreach ( $tableArray as $field => $fieldArray )
			{
				( isset( $fieldArray[ FP_TABLE_ALIAS ] )) ? $tableAlias = $fieldArray[ FP_TABLE_ALIAS ] : $tableAlias = $table;
				/* can't join fields on field alias */
				( isset( $fieldArray[ FP_FIELD_ALIAS ] ) && isset( $fieldArray[ FP_FIELD_NAME ] )) ? $fieldAlias = $fieldArray[ FP_FIELD_NAME ]  : $fieldAlias = $field;

				if ( isset( $fieldArray[ FP_FIELD_JOIN ] ))
				{
					$join = explode( ',', $fieldArray[ FP_FIELD_JOIN ] );
					$joinType  = @$join[ 0 ];
					( @isset( $join[ 1 ] )) ? $joinTable = $join[ 1 ] : $joinTable = $table;
					( @isset( $join[ 2 ] )) ? $joinField = $join[ 2 ] : $joinField = $field;
					switch ( $joinType )
					{
						case FP_JOIN_INNER : $join = chr( 10 ) . 'INNER JOIN '; break;
						case FP_JOIN_LEFT  : $join = chr( 10 ) . 'LEFT OUTER JOIN '; break;
						case FP_JOIN_RIGHT : $join = chr( 10 ) . 'RIGHT OUTER JOIN '; break;
						default : return $this->_logError( FP_ERROR_INITIALISING, 'invalid join type ' . $joinType . ' for table ' . $table . ' on field ' . $field, true );
					}

					if ( $tableAlias == $table )
						$join .= $tableAlias . ' ON ' . $tableAlias . '.' . $fieldAlias . ' = ' . $joinTable . '.' . $joinField;
					else
						$join .= $table . ' AS ' . $tableAlias . ' ON ' . $tableAlias . '.' . $field . ' = ' . $joinTable . '.' . $joinField;

					array_push( $joins, $join );
					/* now add to our list of tables which shouldn't be in FROM clause (as they are joined) */
					if ( array_search( $tableAlias, $joinedTables ) === false )
						array_push( $joinedTables, $tableAlias );
				}

				( $tableAlias != $table ) ? $fromTable = $table . ' AS ' . $tableAlias : $fromTable = $table;

				if (( array_search( $tableAlias, $joinedTables ) === false ) && ( array_search( $fromTable, $tables ) === false ))
					array_push( $tables, $fromTable );
			}
		}

		if ( count( $joins ) > 0 )
		{
			if ( count( $tables ) > 0 ) 
				return implode( ',' . chr( 10 ), $tables ) . ' ' . implode( ' ', $joins ) . chr( 10 );
			else
			{
				/* ::To-Do:: This needs to return all tables (csv'd) not found in table reference in joins */
				$notJoined = @array_diff( $tables, $joinedTables ); 
$this->_debugLog( 'tables: ', $tables );
$this->_debugLog( 'joinedTables: ', $joinedTables );
$this->_debugLog( 'notJoined: ', $notJoined );
				return implode( ', ', $notJoined ) . ' ' . implode( ' ', $joins ) . chr( 10 );
			}
		}
		elseif ( count( $tables ) > 0 )
			return implode( ', ', $tables );
		else
			return false;
	}

	function _getTablesForInsert()
	{
		/* return an array of all tables which have a field containing a value */
		$return = array();

		foreach ( $this->SQLfields as $table => $tableArray )
			foreach ( $tableArray as $field => $fieldArray )
				if ( isset( $fieldArray[ FP_FIELD_VALUE ] ))
				{
					( isset( $fieldArray[ FP_TABLE_ALIAS ] )) ? $addTable = $fieldArray[ FP_TABLE_ALIAS ] : $addTable = $table;
					if ( in_array ( $addTable, $return ) === false )
						array_push( $return, $addTable );
				}
		if ( count( $return ) > 0 )
			return $return;
		else
			return false;
	}

	function _getTablesForUpdate()
	{
		/* return an array of tables (including aliases) which have fields needing updating */
		$return = array();

		foreach ( $this->SQLfields as $table => $tableArray )
			foreach ( $tableArray as $field => $fieldArray )
				if (( isset( $fieldArray[ FP_FIELD_VALUE ] ) && !isset( $fieldArray[ FP_FIELD_KEY ] )) ||
				    isset( $fieldArray[ FP_FIELD_OLDKEY ] ))
				{
					( isset( $fieldArray[ FP_TABLE_ALIAS ] )) ? $addTable = $fieldArray[ FP_TABLE_ALIAS ] : $addTable = $table;
					if ( in_array ( $addTable, $return ) === false )
						array_push( $return, $addTable );
					/*
					see if field is joined to a key field, if it is and this fields old value
					has changed, include joined table if it's not already in result array.  easy huh? :)
					*/
					if ( isset( $fieldArray[ FP_FIELD_JOIN ] ))
					{
						$join = explode( ',', $fieldArray[ FP_FIELD_JOIN ] );
						if ( @isset( $join[ 1 ] ) && @isset( $join[ 2 ] ))
						{
							$joinTable = $join[ 1 ];
							$joinField = $join[ 2 ];
							if ( isset( $fieldArray[ FP_FIELD_OLDKEY ] ) || isset( $this->SQLfields[ $joinTable ][ $joinField ][ FP_FIELD_OLDKEY ] ))
								if ( array_search( $joinTable, $return ) === false )
									array_push( $return, $joinTable );
						}
					}
				}
		if ( count( $return ) > 0 )
			return $return;
		else
			return false;
	}

	function _getTablesForDelete()
	{
		/* return an array of tables which have a key field */
		$return = array();

		foreach ( $this->SQLfields as $table => $tableArray )
			foreach ( $tableArray as $field => $fieldArray )
				if ( isset( $fieldArray[ FP_FIELD_KEY ] ))
				{
					( isset( $fieldArray[ FP_TABLE_ALIAS ] )) ? $addTable = $fieldArray[ FP_TABLE_ALIAS ] : $addTable = $table;
					if ( in_array ( $addTable, $return ) === false )
						array_push( $return, $addTable );
				}
		if ( count( $return ) > 0 )
			return $return;
		else
			return false;
	}

	function _checkFieldValue( $table, $field, $value = NULL, $type = NULL )
	{
		if ( is_null( $type )) 
			$type = @$this->SQLfields[ $table ][ $field ][ FP_FIELD_TYPE ];

		switch ( $type )
		{
			case FP_ADODB_CHARACTER :
			case FP_ADODB_CLOB :
				$value = $this->_db->Quote( $value );
				break;

			case FP_ADODB_DATE :      /* expecting format yyyy-mm-dd */
				$value = $this->_db->DBDate( $value );
				break;

			case FP_ADODB_TIMESTAMP : /* expecting format yyyy-mm-dd hh:mm:ss */
				$value = $this->_db->DBTimeStamp( $value );
				break;

			case FP_ADODB_BOOLEAN :
				/* should we force all booleans to either 1 (true) or false (0)?  
				   Prefer not to since different db's treat booleans differently */
				//switch ( strToLower( $value ))
				//{
				//	case "y" : case "t" : case "true"  : $value = 1;  break;
				//	case "n" : case "f" : case "false" : $value = 0; break;
				//}
				break;

			case FP_ADODB_NUMERIC :   /* numeric types are not quoted */
			case FP_ADODB_INTEGER :   /* we also leave integers alone */
			case FP_ADODB_COUNTER :   /* ::To-Do:: If flashPash implements it's own counter/sequencing, grab next sequence here if field value 0 or null */
				if ( is_null( $value ))
					$value = '""';
					//$value = 'NULL';
				break;
				
			case FP_ADODB_BLOB    :
				if ( is_null( $value ))
					$value = '""';
				break;

			default : /* unknown field type, ::To-Do:: ensure that it okay to take a guess and quote */
				$value = $this->_db->Quote( $value );
				break;
		}

		return $value;
	}

	function _addSQLcondition()
	{
		if ( !empty( $this->SQLcondition ))
		{
			/*
			::To-Do:: May be necessary to make this smarter and test for {ORDER,GROUP,HAVING,etc} 
			at start of condition...if not found then and `WHERE` found in SQL, then prefix with ` AND ` */
			if ( empty( $this->_SQL[ $this->_qri ] ))
				$this->_SQL[ $this->_qri ] = $this->SQLcondition;
			else
				$this->_SQL[ $this->_qri ] .= chr( 10 ) . ' ' . $this->SQLcondition;
		}
	}

	function _runQuery()
	{
		switch ( $this->SQLtype )
		{
			case FP_SELECT :
			case FP_GENERIC :
				if ( $this->recordLimit > 0 )
				{
					if (( empty( $this->limitFrom )) || ( is_null( $this->limitFrom )))
						$this->limitFrom = 0;
					$this->_result = @$this->_db->SelectLimit( $this->_SQL[ $this->_qri ], $this->recordLimit, $this->limitFrom );
				}
				else
				{
					$this->_result = @$this->_db->Execute( $this->_SQL[ $this->_qri ] );
				}
				if ( $this->_result !== false )
					$this->recordCount = $this->_result->RecordCount();
				break;

				case FP_INSERT :
					foreach ( $this->_SQL as $key => $query )
					{
						$this->_result = @$this->_db->Execute( $query );
						if ( $this->_result !== false )
							$this->recordCount++; /* inserts can only create 1 record at a time */
						else
							break;
					}
					break;

			case FP_UPDATE  :
			case FP_DELETE  :
			case FP_RESOLVE :
				foreach ( $this->_SQL as $key => $query )
				{
					$this->_result = @$this->_db->Execute( $query );
					/*
					some databases (eg. ibase/firebird) can't set affect_records in ADOdb, so set count to -1 (not zero),
					this means programmer has the ability to check error property for success or failure
					*/
					if ( $this->_result !== false )
						( $this->_db->Affected_Rows() === false ) ? $this->recordCount = -1 : $this->recordCount += $this->_db->Affected_Rows();
					else
						break;
				}
				break;

			case FP_META :
				switch ( $this->metaType )
				{
					case FP_META_TYPE_DATABASES :
						$this->_result = $this->_db->MetaDatabases();
						if ( $this->_result !== false )
						{
							$this->recordCount = count( $this->_result );
							$this->_debugLog( 'database count: ' . $this->recordCount );
						}
						break;

					case FP_META_TYPE_TABLES :
						$this->_result = $this->_db->MetaTables();
						if ( $this->_result !== false )
						{
							$this->recordCount = count( $this->_result );
							$this->_debugLog( 'table count: ' . $this->recordCount );
						}
						break;

					case FP_META_TYPE_COLUMNS :
						$this->_result = $this->_db->MetaColumnNames( $this->SQLcondition );
						if ( $this->_result !== false )
						{
							$this->recordCount = count( $this->_result );
							$this->_debugLog( 'column count: ' . $this->recordCount );
						}
						break;

					case FP_META_TYPE_SQL :
						$this->_result = $this->_db->SelectLimit( $this->_SQL[ $this->_qri ], 1, 0 );
						if ( $this->_result !== false )
						{
							$this->recordCount = $this->_result->FieldCount();
							$this->_debugLog( 'column count: ' . $this->recordCount );
						}
						break;
				}
				break;
		}

		if ( $this->_result === false )
		{
			/* fail transaction if supported */
			if (( $this->_wrapInTrans ) && ( method_exists( $this->_db, 'FailTrans' )))
			{
				$this->_db->FailTrans();
				$this->_debugLog( 'Failed Transaction', false, $this->_db->_transOK );
			}
			return $this->_logError( FP_ERROR_DB_ACCESS, 'query failed', true, $this->_db->ErrorMsg() );
		}

		$this->_debugLog( 'query run okay' );
		return true;
	}

	function _buildResult()
	{
		switch ( $this->SQLtype )
		{
			case FP_INSERT :
			case FP_UPDATE :
			case FP_DELETE :
				$this->_buildModifyResult();
				break;

			case FP_GENERIC :
			case FP_SELECT  :
				$this->_buildSelectResult();
				$this->_buildXMLoutput();
				break;

			case FP_META :
				$this->_buildMetaResult();
				break;
		}
		
		/* result is now built so we can close the query...not really needed but be nice anyway... */
		if ( is_object( $this->_result ) && ( $this->_result !== false ))
		{
			$this->_result->Close();
			$this->_debugLog( 'query closed' );
		}
	}

	function _buildModifyResult()
	{
		$this->_convertArrayToXML();
		$this->_debugLog( 'affected records: ' . $this->recordCount );
	}

	function _buildSelectResult()
	{
		$fieldCount = $this->_result->FieldCount();

		$rowPos = 0;
		$recCount = 0;
		while ( $row = $this->_result->FetchRow() )
		{
			/* If recordLimit set, only load necessary records into result */
			( $this->recordLimit > 0 ) ? $addRecord = (( $recCount < $this->recordLimit )) : $addRecord = true;
			if ( $addRecord )
			{
				$this->SQLresult[ $recCount ] = array();
				for ( $colPos = 0; $colPos < $fieldCount; $colPos++ )
				{
					$fieldObject = $this->_result->FetchField( $colPos );
					$aliasPos    = strpos( strtoupper( $fieldObject->name ), FP_SQL_ALIAS );
					if ( $aliasPos === false )
					{
						( is_object( $fieldObject->table )) ? $table = $fieldObject->table : $table = FP_OUTPUT;
						$field = $fieldObject->name;
					}
					else
					{
						/* table is everything before FP_SQL_ALIAS */
						$table = substr( $fieldObject->name, 0, $aliasPos );
						/* field is everything after FP_SQL_ALIAS  */
						$field = substr( $fieldObject->name, $aliasPos + strlen( FP_SQL_ALIAS ));
					}
					$this->SQLresult[ $recCount ][ $table ][ $field ] = $row[ $colPos ];
				}
				$recCount++;
			}
			else
			{
				break; /* no need to continue as we have grabbed all the records required */
			}
			$rowPos++;
		}
		$this->recordCount = $recCount;  /* not really needed, but could be useful for debugging if problems found */

		$this->_debugLog( 'record count: ' . $this->recordCount . '  field count: ' . $fieldCount );
		$this->_debugLog( 'SQLresult', $this->SQLresult );
	}

	function _buildMetaResult()
	{
		$tree = new XML_Tree;
		$flashPashRoot =& $tree->addRoot( FP_ROOT_NODE );

		$dataPacketElement =& $flashPashRoot->addChild( $this->SQLtype );
		$dataPacketElement->attributes[ FP_RECORD_COUNT ] = $this->recordCount;
		$dataPacketElement->attributes[ FP_META_TYPE ] = $this->metaType;

		if ( strtolower( $this->setDataSource ) == 'true' )
			$dataPacketElement->attributes[ FP_SET_DATA_SOURCE ] = 'true';

		if ( $this->error <> FP_ERROR_NONE )
			$dataPacketElement->attributes[ FP_ERROR ] = $this->error;

		if ( $this->errorText <> NULL )
			$dataPacketElement->attributes[ FP_ERROR_TEXT ] = rawurlencode( $this->errorText );

		switch ( $this->metaType )
		{
			case FP_META_TYPE_DATABASES :
				$dbElement =& $dataPacketElement->addChild( FP_META_TYPE_DATABASES );
				$dbElement->attributes = $this->_buildMetaArray( $this->_result, 'db' );
				break;

			case FP_META_TYPE_TABLES :
				$tableElement =& $dataPacketElement->addChild( $this->dbName );
				$tableElement->attributes = $this->_buildMetaArray( $this->_result, 'table' );
				break;

			case FP_META_TYPE_COLUMNS :
				$columnElement =& $dataPacketElement->addChild( $this->SQLcondition );
				$columnElement->attributes = $this->_buildMetaArray( $this->_result, 'column' );
				break;

			case FP_META_TYPE_SQL :
				switch ( $this->dataSourceType )
				{
					case FP_DATA_SOURCE_FIREFLY :
						if ( function_exists( '_buildFireflyMetaResult' ))
						{
							if ( _buildFireflyMetaResult( $this, $tree, $dataPacketElement ) === false )
								return false;
						}
						else
						{
							return $this->_logError( FP_ERROR_INITIALISING, 'Unable to call _buildFireflyMetaResult as flashPash.firefly.php not included', true );
						}
						break;

					case FP_DATA_SOURCE_RECORDSET :
						if ( function_exists( '_buildRecordsetMetaResult' ))
						{
							if ( _buildRecordsetMetaResult( $this, $tree, $dataPacketElement ) === false )
								return false;
						}
						else
						{
							return $this->_logError( FP_ERROR_INITIALISING, 'Unable to call _buildRecordsetMetaResult as flashPash.recordset.php not included', true );
						}
						break;
				}
				break;
		}

		$this->dp[ $this->_dpi ][ FP_OUTPUT ] = $dataPacketElement->get();
		$this->_debugLog( 'dp[ ' . $this->_dpi . ' ][ ' . FP_OUTPUT . ' ]', $this->dp[ $this->_dpi ][ FP_OUTPUT ] );
	}

	function _buildMetaArray( $metaArray, $metaType )
	{
		$return = array();
		$i = 1;
		foreach( $metaArray as $key => $value )
		{
			$return[ $metaType . $i ] = $value;
			$i++;
		}
		if ( count( $return ) > 0 )
			return $return;
		else
			return false;
	}

	function _buildXMLoutput()
	{    
		$tree = new XML_Tree;

		$attributes = array( FP_RECORD_COUNT => $this->recordCount );

		if ( $this->error <> FP_ERROR_NONE )
			$attributes[ FP_ERROR ] = $this->error;

		if ( $this->errorText <> NULL )
			$attributes[ FP_ERROR_TEXT ] = rawurlencode( $this->errorText );

		if ( $this->recordLimit > 0 )
		{
			$attributes[ FP_RECORD_LIMIT ] = $this->recordLimit;
			$attributes[ FP_LIMIT_FROM ]   = $this->limitFrom;
		}

		$flashPashRoot     =& $tree->addRoot( FP_ROOT_NODE );
		$dataPacketElement =& $flashPashRoot->addChild( $this->SQLtype, NULL, $attributes );

		if ( $this->recordCount > 0 )
		{
			/*
			create an array mapping pathing for all tables and fields (and their aliases if defined).
			We then use this to array to compare against, this is far more efficent than trying to
			look at each field (or it's field alias) for each record to see what the path is.
			*/
			$aliasArray = array();
			foreach( $this->SQLfields as $table => $tableArray )
				foreach ( $tableArray as $field => $fieldArray )
					if ( @$fieldArray[ FP_FIELD_PATH ] == "@" )
						( isset( $fieldArray[ FP_TABLE_ALIAS ] )) ? $aliasArray[ $fieldArray[ FP_TABLE_ALIAS ] ][ $field ] = array( FP_FIELD_PATH => "@" ) :
						                                                $aliasArray[ $table ][ $field ] = array( FP_FIELD_PATH => "@" ); /* array mania! :) */

			/* build XML */
			for ( $i = 0; $i < $this->recordCount; $i++ )
			{
				$rowElement =& $dataPacketElement->addChild( FP_ROW_NODE );
				foreach ( $this->SQLresult[ $i ] as $table => $value )
				{
					$tableElement =& $rowElement->addChild( $table );
					foreach ( $this->SQLresult[ $i ][ $table ] as $field => $value )
						( @$aliasArray[ $table ][ $field ][ FP_FIELD_PATH ] == "@" ) ? $tableElement->attributes[ $field ] = rawurlencode( $value ) :
						                                                                   $fieldElement =& $tableElement->addChild( $field, $this->_checkForCDATA( $value ));
				}
			}
		}
		$this->dp[ $this->_dpi ][ FP_OUTPUT ] = $dataPacketElement->get();
		$this->_debugLog( 'dp[ ' . $this->_dpi . ' ][ ' . FP_OUTPUT . ' ]', $this->dp[ $this->_dpi ][ FP_OUTPUT ] );
	}

	function _convertArrayToXML()
	{
		$tree = new XML_Tree;
		$flashPashRoot =& $tree->addRoot( FP_ROOT_NODE );

		if ( isset( $this->SQLtype ))
			$dataPacketElement =& $flashPashRoot->addChild( $this->SQLtype );
		elseif ( isset( $this->dp[ $this->_dpi ][ FP_SQL_TYPE ] ))
			$dataPacketElement =& $flashPashRoot->addChild( $this->dp[ $this->_dpi ][ FP_SQL_TYPE ] );
		else
			$dataPacketElement =& $flashPashRoot->addChild( FP_DATA_PACKET );

		if ( !is_null( $this->recordCount ))
			$dataPacketElement->attributes[ FP_RECORD_COUNT ] = $this->recordCount;

		if ( $this->error <> FP_ERROR_NONE )
			$dataPacketElement->attributes[ FP_ERROR ] = $this->error;

		if ( $this->errorText <> NULL )
			$dataPacketElement->attributes[ FP_ERROR_TEXT ] = rawurlencode( $this->errorText );

		$this->dp[ $this->_dpi ][ FP_OUTPUT ] = $dataPacketElement->get();
		$this->_debugLog( 'dp[ ' . $this->_dpi . ' ][ ' . FP_OUTPUT . ' ]', $this->dp[ $this->_dpi ][ FP_OUTPUT ] );
	}

	function _convertXMLtoArray( $XMLdoc )
	{
		$XMLobject = new XML_Tree;
		$XMLtree =& $XMLobject->getTreeFromString( $XMLdoc );

		if ( PEAR::isError( $XMLtree ))
			return $this->_logError( FP_ERROR_PARSING_XML, 'not valid XML', false, $XMLdoc );
		else
			$this->_debugLog( 'successfully parsed XML', @$XMLtree->get() );

		if ( strtolower( $XMLtree->name ) != strtolower( FP_ROOT_NODE ))
			return $this->_logError( FP_ERROR_PARSING_XML, 'XML root node invalid', false, 'found ' . $XMLtree->name . ' expecting ' . FP_ROOT_NODE );

		if ( isset( $XMLtree->attributes[ FP_WRAP_IN_TRANS ] ) &&
		    ( strtolower( $XMLtree->attributes[ FP_WRAP_IN_TRANS ] ) == 'true' ))
		{
			$this->_wrapInTrans = true;
		}

		foreach ( $XMLtree->children as $dpIndex => $dataPacketNode )
		{
			$this->_dpi = $dpIndex;
			if (( $dataPacketNode->name == FP_SELECT ) ||
			    ( $dataPacketNode->name == FP_INSERT ) ||
			    ( $dataPacketNode->name == FP_UPDATE ) ||
			    ( $dataPacketNode->name == FP_DELETE ) ||
			    ( $dataPacketNode->name == FP_GENERIC ) ||
			    ( $dataPacketNode->name == FP_META ))
			{
				$this->dp[ $this->_dpi ][ FP_SQL_TYPE ] = strtolower( $dataPacketNode->name );
				foreach ( $dataPacketNode->attributes as $property => $value )
				{
					switch ( $property )
					{
						case FP_SQL_CONDITION    : $this->dp[ $this->_dpi ][ FP_SQL_CONDITION ]    = rawurldecode( $value ); break;
						case FP_RECORD_LIMIT     : $this->dp[ $this->_dpi ][ FP_RECORD_LIMIT ]     = $value; break;
						case FP_LIMIT_FROM       : $this->dp[ $this->_dpi ][ FP_LIMIT_FROM ]       = $value; break;
						case FP_DATA_SOURCE      : $this->dp[ $this->_dpi ][ FP_DATA_SOURCE ]      = rawurldecode( $value ); break;
						case FP_DATA_SOURCE_TYPE : $this->dp[ $this->_dpi ][ FP_DATA_SOURCE_TYPE ] = $value; break;
						case FP_SET_DATA_SOURCE  : $this->dp[ $this->_dpi ][ FP_SET_DATA_SOURCE ]  = $value; break;
						case FP_CREATE_NODES     : $this->dp[ $this->_dpi ][ FP_CREATE_NODES ]     = $value; break;
						case FP_DISABLE_KEYS     : $this->dp[ $this->_dpi ][ FP_DISABLE_KEYS ]     = $value; break;
						case FP_META_TYPE        : $this->dp[ $this->_dpi ][ FP_META_TYPE ]        = $value; break;
						default : return $this->_logError( FP_ERROR_PARSING_XML, 'invalid XML', false, 'unknown ' . $dataPacketNode->name . ' attribute ' . $property . ' value ' . $value );
					}
				}

				foreach ( $dataPacketNode->children as $fieldIndex => $fieldNode )
				{
					$field = NULL;
					$type  = NULL;
					$value = NULL;
					$key   = NULL;
					$join  = NULL;
					$oldKeyValue = NULL;
					$tableAlias  = NULL;
					$fieldAlias  = NULL;
					$compute     = NULL;
					$useNode     = TRUE;

					foreach ( $fieldNode->attributes as $property => $propValue )
					{
						switch ( $property )
						{
							case FP_FIELD_NAME    : $field   = $propValue; break;
							case FP_FIELD_TYPE    : $type    = $this->_convertFieldType( $propValue ); break;
							case FP_FIELD_PATH    : $useNode = ( $propValue != "@" ); break;
							case FP_FIELD_VALUE   : $value   = rawurldecode( $propValue ); break;
							case FP_FIELD_JOIN    : $join    = rawurldecode( $propValue ); break;
							case FP_FIELD_KEY     : $key     = strtolower( $propValue ); break;
							case FP_FIELD_OLDKEY  : $oldKeyValue = rawurldecode( $propValue ); break;
							case FP_FIELD_ALIAS   : $fieldAlias  = rawurldecode( $propValue );
							case FP_TABLE_ALIAS   : $tableAlias  = rawurldecode( $propValue );
							case FP_FIELD_COMPUTE : $compute     = rawurldecode( $propValue );
							default : return $this->_logError( FP_ERROR_PARSING_XML, 'invalid XML', false, 'unknown row attribute ' . $property . ' value ' . $propValue );
						}
					}
					

					if ( isset( $field ))
					{
						$this->setField( $fieldNode->name, $field, $type, $useNode, $value );

						if ( !is_null( $key ) || !is_null( $oldKeyValue ))
							$this->setKey( $fieldNode->name, $field, $key, $oldKeyValue );

						if ( !is_null( $tableAlias ) || !is_null( $fieldAlias ))
							$this->setAlias( $fieldNode->name, $field, $tableAlias, $fieldAlias, $compute );

						if ( !is_null( $join ))
						{
							$join = explode( ',', $join );
							$joinType  = @$join[ 0 ];
							$joinTable = @$join[ 1 ];
							$joinField = @$join[ 2 ];
							$this->setJoin( $fieldNode->name, $field, $joinType, $joinTable, $joinField );
						}
					}
				}
			}
			else
			{
				return $this->_logError( FP_ERROR_PERMISSION_INVALID_XML, 'invalid node', false, $dataPacketNode->name );
			}
		}
		return true;
	}

	function _convertFieldType( $type )
	{
		/* hint: to save an hour of frustration, DO NOT use $this->dataSourceType...as it's not loaded yet...D'OH! :) */
		switch ( $this->dp[ $this->_dpi ][ FP_DATA_SOURCE_TYPE ] )
		{
			case FP_DATA_SOURCE_FIREFLY :
				if ( function_exists( '_convertFireflyFieldType' ))
					return _convertFireflyFieldType( $fp, $type );
				else
					return $this->_logError( FP_ERROR_INITIALISING, 'Unable to call _convertFireflyFieldType as flashPash.firefly.php not included', true );
				break;

			case FP_DATA_SOURCE_RECORDSET :
				if ( function_exists( '_convertRecordsetFieldType' ))
					return _convertRecordsetFieldType( $fp, $type );
				else
					return $this->_logError( FP_ERROR_INITIALISING, 'Unable to call _convertRecordsetFieldType as flashPash.recordset.php not included', true );
				break;
		}
	}

	function _checkForCDATA( $searchString, $rawurlencode = true )
	{
		/* add CDATA tag if we find CR, LF, ", %, &, <, >, /  */
		/* ::To-Do:: Check if any other chars need CDATA tag  */
		if (( strpos( $searchString, chr( 10 )) !== false) ||
				( strpos( $searchString, chr( 13 )) !== false) ||
				( strpos( $searchString, chr( 34 )) !== false) ||
				( strpos( $searchString, chr( 37 )) !== false) ||
				( strpos( $searchString, chr( 38 )) !== false) ||
				( strpos( $searchString, chr( 47 )) !== false) ||
				( strpos( $searchString, chr( 60 )) !== false) ||
				( strpos( $searchString, chr( 62 )) !== false))
			if ( $rawurlencode )
				return "<![CDATA[" . rawurlencode( $searchString ) . "]]>";
			else
				return "<![CDATA[" . $searchString . "]]>";
		else
			if ( $rawurlencode )
				return rawurlencode( $searchString );
			else
				return $searchString;
	}
}
?>
Return current item: flashPash