AnonSec Shell
Server IP : 54.36.91.62  /  Your IP : 216.73.217.112
Web Server : Apache
System : Linux webm013.cluster127.gra.hosting.ovh.net 5.15.206-ovh-vps-grsec-zfs-classid #1 SMP Fri May 15 02:41:25 UTC 2026 x86_64
User : coopiak ( 151928)
PHP Version : 8.3.23
Disable Function : _dyuweyrj4,_dyuweyrj4r,dl
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /home/coopiak/amisdesseniors-fr/previous-website/06/libraries/CBLib/CBLib/AhaWow/Model/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ HOME ]     

Current File : /home/coopiak/amisdesseniors-fr/previous-website/06/libraries/CBLib/CBLib/AhaWow/Model/XmlQuery.php
<?php
/**
* CBLib, Community Builder Library(TM)
* @version $Id: 11/25/13 5:33 PM $
* @package CBLib\AhaWow\Model
* @copyright (C) 2004-2023 www.joomlapolis.com / Lightning MultiCom SA - and its licensors, all rights reserved
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU/GPL version 2
*/

namespace CBLib\AhaWow\Model;

use CBLib\Database\Table\CheckedOrderedTable;
use CBLib\Registry\ParamsInterface;
use CBLib\Xml\SimpleXMLElement;
use CBLib\Database\DatabaseDriverInterface;
use CBLib\Database\Table\TableInterface;
use CBLib\Language\CBTxt;

defined('CBLIB') or die();

/**
 * CBLib\AhaWow\Model\XmlQuery Query Compiler from AHA-WOW XML formal query description language Class implementation
 *
 */
class XmlQuery {
	/** main table "AS" alias (should stay same throughout object lifetime)
	 *  @var string */
	private $maintableAs				=	'a';
	/** next joined table "AS" alias (should be incremented using incrementTableAs() method after each use )
	 *  @var string */
	private $tableAs					=	'a';
	/** next joined table "AS" alias (should be incremented using incrementTableAs() method after each use )
	 *  @var string */
	private $_currentTableAs			=	'a';
	/** next joined table "AS" alias (should be incremented using incrementTableAs() method after each use )
	 *  @var string[] */
	private $_currentTableAsStack		=	array( 'a' );
	/** next joined table "AS" alias (should be incremented using incrementTableAs() method after each use )
	 *  @var int */
	private $_currentTableAsStackIdx	=	0;
	/** individual fields (or formula) expressions for SELECT
	 *  @var array of string */
	private $fieldsArray				=	array();
	/** individual fields types for UPDATE and INSERT
	 *  @var array of string */
	private $fieldsTypesArray			=	array();
	/** array of individual table-alias => "LEFT JOIN ... ON ... " expressions for SELECT
	 *  @var array of string */
	private $leftJoinArray				=	array();
	/** array of individual table-alias => table
	 *  @var array of string */
	private $leftJoinAsArray			=	array();
	/** array of individual [table][key][value] -alias => "LEFT JOIN ... ON ... " expressions for SELECT
	 *  @var array of string */
	private $leftJoindTableKeyValueArray =	array();
	/** Array Maps individual fieldname => leftjoin table alias (if it's leftjoined)
	 *  Used for subsequent leftjoins
	 *  @var array of string */
	private $leftJoinedFieldsTable		=	array();
	/** array of individual table-alias => table that must be joined for count query
	 *  @var array */
	private $joinsNeededForCount		=	array();
	/** individual fields (or formula) expressions for "WHERE" (will be imploded with "AND")
	 *  @var array of string */
		private $where						=	array();
	/** array of individual expressions used for "GROUP BY" (will be imploded with "AND")
	 *  @var array of string */
	private $groupByArray				=	array();
	/** individual fields (or formula) expressions for "HAVING"
	 *  @var array of string */
	private $having						=	array();
	/** individual fields (or formula) with "ASC/DESC" expressions for "ORDER BY"
	 *  @var array of string */
	private $orderArray					=	array();
	/** database object
	 * @var DatabaseDriverInterface */
	private $_db;
	/** name of main table (with "#__" prefix for Joomla
	 *  @var string */
	private $_table;
	/** CB plugin parameters for "param:" statements
	 * @var ParamsInterface */
	private $_pluginParams;
	/** array holding references to external data models (objects or arrays for "ext:datatype:xxx:yyy" valuetype)
	 * @var ParamsInterface[] */
	private $_extDataModels				=	array();
	/** Internal state for XML->SQL traversal and query-generation mode
	 * @var boolean */
	private $_reverse					=	false;
	/**
	 * Did we not yet bump group_concat_max_len ?
	 * @var boolean
	 */
	protected $_group_concat_max_len_todo	=	true;

	/**
	 * Constructor
	 *
	 * @param  DatabaseDriverInterface  $db            Database object
	 * @param  string                   $table         Name of main table (with "#__" prefix for Joomla
	 * @param  ParamsInterface          $pluginParams  CB plugin parameters for "param:" statements
	 */
	public function __construct( $db, $table, $pluginParams ) {
		$this->_db				=	$db;
		$this->_table			=	$table;
		$this->_currentTableAs	=	'a';						//TBD: CHECK IF I CAN ADD THIS HERE...
		$this->_pluginParams	=	$pluginParams;
	}

	/**
	 * Prepares query from a single element <data> with all needed children nodes (<orderby>, <rows>, <where> and if existing <groupby>)
	 *
	 * @param  SimpleXmlElement  $data
	 * @return void
	 */
	public function prepare_query( $data ) {
		$this->process_orderby( $data->getElementByPath( 'orderby') );			// <data><orderby><field> fields
		$this->process_fields(  $data->getElementByPath( 'rows') );				// <data><rows><field> fields
		$this->process_where(   $data->getElementByPath( 'where') );			// <data><where><column> fields
		$this->process_groupby( $data->getElementByPath( 'groupby' ) );			// <data><groupby><field> fields
	}

	/**
	 * Treats a <fields> node and its children
	 *
	 * @param SimpleXmlElement $fields
	 */
	public function process_fields( $fields ) {
		if ( $fields ) {
			foreach ( $fields->children() as $field ) {
				/** @var $field SimpleXmlElement */
				if ( $field->getName() == 'field' ) {
					$this->process_field( $field );
				} elseif ( $field->getName() == 'data' ) {
					$this->process_data( $field );
				} elseif ( ! in_array( $field->getName(), array( 'attributes' ) ) ) {
					trigger_error( 'SQLXML::process_field: child type ' . $field->getName() . ' of fields is not implemented !', E_USER_NOTICE );
				}
			}
		}
	}

	/**
	 * Treats a <orderby> node and its children <field> nodes
	 *
	 * @param SimpleXmlElement $orderby
	 * @param string           $selectedOrderingGroup  The ordergroup to select and use for ordering
	 */
	public function process_orderby( $orderby, $selectedOrderingGroup = null ) {
		if ( $orderby ) {
			if ( $selectedOrderingGroup ) {
				$this->process_orderby( $orderby->getChildByNameAttr( 'ordergroup', 'name', $selectedOrderingGroup ) );
				return;
			}

			foreach ( $orderby->children() as $o ) {
				/** @var $o SimpleXmlElement */
				if ( $o->getName() == 'field' ) {
					list( $fieldsArray )	=	$this->get_field( $o );

//					$this->processJoinsNeededForCount( $fieldsArray ); the count query doesn't output the order by

					$this->orderArray[]		=	array_pop( $fieldsArray ) . ( $o->attributes( 'ordering' ) === 'DESC' ? ' DESC' : '' );
				} elseif ( $o->getName() == 'ordergroup' ) {
					continue;
				} else {
					trigger_error( 'SQLXML::process_orderby: child type ' . $o->getName() . ' of orderby is not implemented !', E_USER_NOTICE );
				}
			}
		}
	}
	/**
	 * Adds an extra "group by $groupby"
	 * <groupby>
	 *
	 * @param  SimpleXmlElement|string  $groupby
	 * @return void
	 */
	public function process_groupby( $groupby ) {
		if ( $groupby ) {
			if (is_string( $groupby ) ) {
				$this->groupByArray[]			=	$groupby;
			} elseif ( is_object( $groupby ) ) {
				// Treats each <field>
				foreach ( $groupby->children() as $o ) {
					/** @var $o SimpleXmlElement */
					if ( $o->getName() == 'field' ) {
						list( $fieldsArray )	=	$this->get_field( $o );

						$this->processJoinsNeededForCount( $fieldsArray );

						$groupByField			=	array_pop( $fieldsArray );
						$this->groupByArray[]	=	$groupByField;
					} else {
						trigger_error( 'SQLXML::process_groupby: child type ' . $o->getName() . ' of groupby is not implemented !', E_USER_NOTICE );
					}
				}
			} else {
				trigger_error( 'SQLXML::process_groupby does implement only strings and xml objects', E_USER_NOTICE );
			}
		}
	}
	/**
	 * Treats a <quicksearch> node and its children <field> nodes with a given $searchString
	 *
	 * @param  SimpleXmlElement  $quicksearch
	 * @param  string              $searchString
	 */
	public function process_search_string( $quicksearch, $searchString ) {
		if ( $searchString ) {
			$quicksearchfields			=	array();
			foreach ( $quicksearch->children() as $o ) {
				/** @var $o SimpleXmlElement */
				if ( $o->getName() == 'field' ) {
					list( $fieldsArray )				=	$this->get_field( $o );

					$this->processJoinsNeededForCount( $fieldsArray );

					$searchField						=	array_pop( $fieldsArray );

					$quicksearchfields[$searchField]	=	$o->attributes( 'valuetype' );
				} else {
					trigger_error( 'SQLXML::process_search_string: child type ' . $o->getName() . ' of quicksearchfields is not implemented !', E_USER_NOTICE );
				}
			}
			$qs							=	array();
			$cleanedSearch				=	$this->_db->getEscaped( trim( strtolower( $searchString ) ), true );
			foreach ( $quicksearchfields as $fieldName => $fieldType ) {
				if ( $fieldType ) {
					$cleanedValue		=	$this->sqlCleanQuote( $searchString, $fieldType );
					if ( $cleanedValue || ( ( $cleanedValue === 0 ) && ( $cleanedSearch === '0' ) ) ) {
						$qs[]			=	$fieldName . " = " . $cleanedValue;
					}
				} else {
					$qs[]				=	"( " . $fieldName . " LIKE '%" . $cleanedSearch . "%' )";
				}
			}
			if ( count( $qs ) > 0 ) {
				$this->where[]			=	implode( ' OR ', $qs );
			}

		}
	}

	/**
	 * Proccesses private $fieldsArray to toggle joinsNeededForCount
	 * For Quick Search, Order By, and Group By
	 *
	 * @param array $fieldsArray
	 */
	private function processJoinsNeededForCount( $fieldsArray ) {
		if ( count( $this->leftJoinArray ) > 0 ) {
			foreach( array_keys( $fieldsArray ) as $fieldName ) {
				if ( ( ! $fieldName ) || ( ! array_key_exists( $fieldName, $this->leftJoinedFieldsTable ) ) ) {
					continue;
				}

				$tableAs								=	$this->leftJoinedFieldsTable[$fieldName];

				if ( ( ! array_key_exists( $tableAs, $this->leftJoinArray ) ) || ( ! array_key_exists( $tableAs, $this->leftJoinAsArray ) ) ) {
					continue;
				}

				$this->joinsNeededForCount[$tableAs]	=	$this->leftJoinAsArray[$tableAs];
			}
		}
	}

	/**
	 * Adds a simple WHERE clause
	 *
	 * @param string $fieldName
	 * @param string $operator
	 * @param string $fieldValue
	 * @param string $type        ( 'sql:string' (default), 'sql:int', 'sql:float' )
	 */
	public function addWhere( $fieldName, $operator, $fieldValue, $type ) {
		if ( ( ! $type ) || ( $type == 'sql:field' ) ) {
			$value		=	$this->tableAs . '.' . $this->_db->NameQuote( $fieldValue );
		} elseif ( $type == 'sql:parentfield' ) {
			$value		=	$this->_currentTableAsStack[0] . '.' . $this->_db->NameQuote( $fieldValue );
		} else {
			$value		=	$this->sqlCleanQuote( $fieldValue, $type );
		}
		if ( substr( $operator, -8 ) == '||ISNULL' ) {
			$this->where[]	=	'('
				.	$this->_currentTableAs . '.`' . $this->_db->getEscaped( $fieldName ) . "` " . substr( $operator, 0, -8 ) . " " . $value		//BM maintableAs
				.	' OR '
				.	'ISNULL(' . $this->_currentTableAs . '.`' . $this->_db->getEscaped( $fieldName ) . '`)'
				.	')';
		} else {
			$this->where[]	=	( $this->_currentTableAs ? $this->_currentTableAs : $this->maintableAs ) . '.`' . $this->_db->getEscaped( $fieldName ) . "` " . $operator . " " . $value;		//BM maintableAs
		}
	}

	/**
	 * Adds a simple HAVING clause
	 *
	 * @param string $fieldName
	 * @param string $operator
	 * @param string $fieldValue
	 * @param string $type        ( 'sql:string' (default), 'sql:int', 'sql:float' )
	 */
	public function addHaving( $fieldName, $operator, $fieldValue, $type ) {
		$value		=	$this->sqlCleanQuote( $fieldValue, $type );
		$this->having[] = $this->_currentTableAs . '.`' . $this->_db->getEscaped( $fieldName ) . "` " . $operator . " " . $value;		//BM maintableAs
	}

	/**
	 * Executes the query to count total number of rows and returns count
	 *
	 * @return int|null   null if error
	 */
	public function queryCount() {
		/*
				$sql = "SELECT COUNT(*)"
					. " FROM `" . $this->_db->getEscaped( $this->_table ) . "` AS " . $this->maintableAs
					.		( ( count( $this->leftJoinsNeededForWhere ) > 0 ) ? "\n " 		  . implode( "\n ", $this->leftJoinsNeededForWhere ) : '' )
					.		( ( count( $this->where ) > 0 )		 			  ? "\n WHERE ( " . implode( ' ) AND ( ', $this->where ) . " )"		 : '' )
					//  .	( ( count( $this->groupByArray )  > 0 )			  ? "\n GROUP BY " . implode( ', ', $this->groupByArray )			 : '' )
					//	.	( ( count( $this->having ) > 0 )		 		  ? "\n HAVING ( " . implode( ' ) AND ( ', $this->having ) . " )"		 : '' )
					;
		*/

		if ( count( $this->groupByArray )  == 0 ) {
			$sql = "SELECT COUNT(*)";
		} else {
			$sql = "SELECT COUNT( DISTINCT " . implode( ', ', $this->groupByArray ) . " )";
		}

		// We need to loop through the list of joins needed for the count query and add their queries based off their table alias:
		$tableJoins			=	array();

		// We need to check for any necessary join keys and maintain their order:
		foreach ( array_keys( array_intersect_key( $this->leftJoinArray, $this->joinsNeededForCount ) ) as $joinAs ) {
			$tableJoins[]	=	$this->leftJoinArray[$joinAs];
		}

		$sql	.=	" FROM `" . $this->_db->getEscaped( $this->_table ) . "` AS " . $this->maintableAs
			.		( $tableJoins														  ? "\n " 		   . implode( "\n ", $tableJoins )		: '' )
			.		( ( count( $this->where ) > 0 )		 								  ? "\n WHERE ( "  . implode( ' ) AND ( ', $this->where ) . " )"		 : '' )
			//		.		( ( count( $this->groupByArray )  > 0 )						  ? "\n GROUP BY " . implode( ', ', $this->groupByArray )			 : '' )
			.		( ( count( $this->having ) > 0 )		 							  ? "\n HAVING ( " . implode( ' ) AND ( ', $this->having ) . " )"		 : '' )
		;

		$this->_db->setQuery( $sql );

		try {
			$total	=	$this->_db->loadResult();
		} catch ( \RuntimeException $e ) {
			trigger_error( 'SQLXML::queryCount error returned: ' . $e->getMessage(), E_USER_NOTICE );
		}
		return $total;
	}

	/**
	 * Executes the query to load the rows and returns them
	 *
	 * @param  SimpleXmlElement  $dataModel
	 * @param  int $limitstart
	 * @param  int $limit
	 * @return array of stdClass objects with ->_tbl set properly.
	 */
	public function & queryLoadObjectsList( $dataModel, $limitstart = 0, $limit = 0 ) {
		$sql								=	$this->_buildSQLquery();
		if ( $sql === null ) {
			return $sql;
		}
		$this->_db->setQuery( $sql, ( $limitstart ? (int) $limitstart : 0 ), ( $limit ? (int) $limit : 0 ) );

		$dataModelClass						=	$dataModel->attributes( 'class' );
		$dataModelKey						=	$dataModel->attributes( 'key' );
		$dataModelUseLoad					=	( $dataModel->attributes( 'useload' ) == 'true' );
		if ( ( ! $dataModelClass ) || ( $dataModelClass == 'stdClass' ) ) {
			try {
				$rows						=	$this->_db->loadObjectList();
			} catch ( \RuntimeException $e ) {
				trigger_error( 'SQLXML::queryObjectList: error: ' . $e->getMessage(), E_USER_NOTICE );
			}

			if ( isset( $rows ) && is_array( $rows ) ) {
				for ( $i = 0, $n = count( $rows ); $i < $n; $i++ ) {
					if ( ! ( $rows[$i] instanceof TableInterface ) ) {
						$rows[$i]->_tbl	=	$this->_table;
					}
				}
			}
		} else {
			try {
				$rowsArray					=	$this->_db->loadAssocList();
			} catch ( \RuntimeException $e ) {
				trigger_error( 'SQLXML::queryObjectList: error: ' . $e->getMessage(), E_USER_NOTICE );
			}

			if ( $rowsArray === null ) {
				$rows						=	null;
			} else {
				if ( strpos( $dataModelClass, '::' ) === false ) {
					$rows					=	array();
					foreach ( $rowsArray as $k => $rarr ) {
						$rows[$k]			=	new $dataModelClass( $this->_db );

						if ( $dataModelUseLoad && $dataModelKey && isset( $rarr[$dataModelKey] ) ) {
							if ( $rows[$k] instanceof TableInterface ) {
								/** @var TableInterface[] $rows */
								if ( $rows[$k]->getKeyName() == $dataModelKey ) {
									$rows[$k]->load( $rarr[$dataModelKey] );
								}
							}
						}

						foreach ( $rarr as $kk => $vv ) {
							$rows[$k]->$kk	=	$vv;
						}
					}
				} else {
					$dataModelSingleton		=	explode( '::', $dataModelClass );
					$rows					=	call_user_func_array( $dataModelSingleton, array( &$rowsArray ) );
				}
				unset( $rowsArray );
			}
		}
		return $rows;
	}
	/**
	 * Executes the query to load the rows and returns them
	 * If an object is passed to this function, the returned row is bound to the existing elements of <var>object</var>.
	 * If <var>object</var> has a value of null, then all of the returned query fields returned in the object.
	 *
	 * @param  \stdClass|null  $object  IN+OUT: The address of variable
	 * @return boolean                  True if the object got loaded
	 */
	public function queryLoadObject( & $object ) {
		$sql	=	$this->_buildSQLquery();
		$this->_db->setQuery( $sql );

		try {
			$result		=	$this->_db->loadObject( $object );
		} catch ( \RuntimeException $e ) {
			trigger_error( 'SQLXML::queryLoadObject: error returned: ' . $e->getMessage(), E_USER_NOTICE );
			return false;
		}
		if ( $result ) {
			if ( ! ( $object instanceof TableInterface ) ) {
				$object->_tbl	=	$this->_table;
			}
		}
		return $result;
	}

	/**
	 * Loads the PHP object corresponding to the XML SQL request description
	 *
	 * @param  SimpleXmlElement  $dataModel
	 * @param  int $limitstart
	 * @param  int $limit
	 * @return \stdClass                         in fact it returns an object of the exact type/class
	 */
	public function loadObjectFromData( $dataModel, $limitstart = 0, $limit = 0 ) {
		$data	=	null;

		$dataModelClass			=	$dataModel->attributes( 'class' );
		$dataModelType			=	$dataModel->attributes( 'type' );
		switch ( $dataModelType ) {
			case 'sql:row':													// <data name="planrow" type="sql:row" table="#__plans" class="cbpaidPlan" key="id" value="parameter:tid" />
				$this->process_data( $dataModel );							// process any nested <where> statements or subqueries first
				$this->process_multiplerows( $dataModel );					// <data> datas
				$results		=	$this->queryLoadObjectsList( $dataModel, 0, 1 );
				if ( count( $results ) == 1 ) {
					$data		=	$results[0];
				} else {
					if ( strpos( $dataModelClass, '::' ) === false ) {
						$data				=	new $dataModelClass( $this->_db );
					} else {
						$dataModelSingleton	=	explode( '::', $dataModelClass );
						$rowsArray			=	array( array( ) );
						$rows				=	call_user_func_array( $dataModelSingleton, array( &$rowsArray ) );		// & needed for PHP 5.3.
						$data				=	$rows[0];
					}
				}
				break;
			case 'sql:multiplerows':										// <data name="subscriptionstable" type="sql:multiplerows" table="#s_subscriptions" class="cbpaidUsersubscriptionRecord" key="plan_id" value="parameter:pid">
				$this->process_multiplerows( $dataModel );					// <data> datas
				$data			=	$this->queryLoadObjectsList( $dataModel, $limitstart, $limit );
				break;
			case 'sql:field':												// <data name="params" type="sql:field" table="#_config" class="cbpaidConfig" key="id" value="1" valuetype="sql:int" />
				$this->process_data( $dataModel );
				$data			=	$this->queryloadResult();				// get the resulting field
				break;
			case 'parameters':												// <data name="pluginparams" type="parameters" />		//TBD make sure we have only 1 word for params
			case 'params':													// <data name="pluginparams" type="parameters" />
				$data			=	$this->_pluginParams;       			// object
				break;
			default:
				trigger_error( 'SQLXML::loadObjectFromData: Data model type ' . htmlspecialchars( $dataModelType ) . ' is not implemented !', E_USER_NOTICE );
				break;
		}
		/*
				// $this->setExternalDataTypeValues( 'modelofdata', $modelOfData );
				$this->process_orderby( $data->getElementByPath( 'orderby') );			// <data><orderby><field> fields
					// $this->process_fields( $data->getElementByPath( 'rows') );			// <data><rows><field> fields
					$this->process_where( $data->getElementByPath( 'where') );				// <data><where><column> fields
					// $this->process_groupby( 'value' );
		*/
		return $data;
	}
	/**
	 * process a <data type="sql:multiplerows" ...>
	 *
	 * @param  SimpleXmlElement  $dataModel
	 * @return void
	 */
	protected function process_multiplerows( $dataModel ) {
		/* $formula			=	*/
		$this->process_sql_field( $dataModel );		// throw away the select formula, as we select *
		$this->fieldsArray	=	array( '*' );
	}

	/**
	 * This method loads the first field of the first row returned by the query.
	 *
	 * @return  string|null  The value returned in the query or null if the query failed.
	 */
	public function queryloadResult( ) {
		$sql	=	$this->_buildSQLquery();
		$this->_db->setQuery( $sql );

		try {
			$result		=	$this->_db->loadResult();
		} catch ( \RuntimeException $e ) {
			trigger_error( 'SQLXML::queryloadResult: error returned: ' . $e->getMessage(), E_USER_NOTICE );
			return null;
		}
		return $result;
	}
	/**
	 * Updates object or array
	 *
	 * @param  object|array  $values   object( 'fieldname'->'content ) or array ( 'fieldName' => 'content' )
	 * @param  boolean       $updateNulls  TRUE: update all NULLs too, FALSE: don't update null values
	 * @return boolean
	 */
	public function queryUpdate( $values, $updateNulls = true ) {
		return $this->_queryUpdateInsert( 'UPDATE', $values, null, $updateNulls );
		//TBD LATER: add the logging in history
	}
	/**
	 * Inserts object or array
	 *
	 * @param  object|array  $values   object( 'fieldname'->'content ) or array ( 'fieldName' => 'content' )
	 * @param  string        $keyName  or NULL
	 * @return boolean
	 */
	public function queryInsert( $values, $keyName = null ) {
		return $this->_queryUpdateInsert( 'INSERT', $values, $keyName, false );
		//TBD LATER: add the logging in history
	}
	/**
	 * Inserts or updates object or array
	 * @access private
	 *
	 * @param  string        $command 'UPDATE' or 'INSERT'
	 * @param  object|array  $values   object( 'fieldname'->'content ) or array ( 'fieldName' => 'content' )
	 * @param  string        $keyName  or NULL
	 * @param  boolean       $updateNulls  TRUE: update all NULLs too, FALSE: don't update null values
	 * @return boolean
	 */
	protected function _queryUpdateInsert( $command, $values, $keyName = null, $updateNulls = true ) {
		$fields				=	array();
		if ( is_object( $values ) ) {
			foreach ( get_object_vars( $values ) as $k => $v ) {
				if ( is_array( $v ) || is_object( $v ) || ( $v === null ) ) {
					continue;
				}
				if ( $k[0] == '_' ) {		// private variable
					continue;
				}
				if ( ( $v === null ) && ! $updateNulls ) {
					continue;
				}
				$fields[$k]	=	$v;
			}
			$values			=	$fields;
		} elseif ( is_array( $values ) ) {
			foreach ( $values as $k => $v ) {
				if ( is_array( $v ) || is_object( $v ) || ( $v === null ) || ( $k[0] == '_' ) || ( ( $v === null ) && ! $updateNulls ) ) {
					continue;
				}
				$fields[$k]	=	$v;
			}
		} else {
			trigger_error( 'SQLXML::_queryUpdateInsert: Error queryInsert without object or array.', E_USER_NOTICE );
		}

		$sql	=	$this->_buildSQLqueryUpdateInsert( $command, $fields );
//ECHO "XMLSQL WRITE-QUERY: " . $sql;
//EXIT;
		$this->_db->setQuery( $sql );
		if ( $this->_db->query() ) {

			if ( substr( $command, 0, 6 ) == 'INSERT' ) {
				$id	=	$this->_db->insertid();
				if ($keyName && $id) {
					if ( is_object( $values ) ) {
						$values->$keyName	=	$id;
					} elseif ( is_array( $values ) ) {
						$values[$keyName]	=	$id;
					}
				}
			}
			return true;
		} else {
			return false;
		}
	}
	/**
	 * Returns id of last INSERT
	 *
	 * @return mixed  autoincrement id of last INSERT
	 */
	public function insertid( ) {
		return $this->_db->insertid();
	}
	/**
	 * Builds the SQL query for the main content-getting query
	 * @access protected
	 * (for internal use of this class only)
	 *
	 * @return string  SQL query
	 */
	public function _buildSQLquery() {
		if ( count( $this->fieldsArray ) > 0 ) {
			$sql =	"SELECT " . implode( ', ', $this->fieldsArray )
				.		( $this->_table ? "\n FROM `" . $this->_db->getEscaped( $this->_table ) . "` AS " . $this->maintableAs	: '' )
				.		( ( count( $this->leftJoinArray ) > 0 )	? "\n " 		 . implode( "\n ", $this->leftJoinArray )		: '' )
				.		( ( count( $this->where ) > 0 )			? "\n WHERE ( "  . implode( ' ) AND ( ', $this->where ) . " )"	: '' )
				.		( ( count( $this->groupByArray )  > 0 ) ? "\n GROUP BY " . implode( ', ', $this->groupByArray )        : '' )
				.		( ( count( $this->having ) > 0 ) 		? "\n HAVING ( " . implode( ' ) AND ( ', $this->having ) . " )"	: '' )
				.		( ( count( $this->orderArray )  > 0 )	? "\n ORDER BY " . implode( ', ', $this->orderArray )			: '' );
		} else {
			$sql = null;
		}
		return $sql;
	}
	/**
	 * Builds the SQL query for UPDATE or INSERT
	 *
	 * @param  string  $command  'UPDATE' or 'INSERT'
	 * @param  array   $content  keyed array ( 'fieldName' => 'content' )
	 * @return string  SQL query
	 */
	protected function _buildSQLqueryUpdateInsert( $command, $content ) {
		$sql	=	null;
		if ( ( count( $this->leftJoinArray ) == 0 ) && ( count( $this->groupByArray )  == 0 ) ) {
			if ( count( $this->fieldsArray ) > 0 ) {
				$setFields					=	array();
				foreach ( $this->fieldsArray as $fieldName => $sqlStatement ) {
					if ( isset( $content[$fieldName] ) ) {
						// always sql:field type, which is wrong, but we are waiting for table descriptors implementation...
						/*
						if ( isset( $this->fieldsTypesArray[$fieldName] ) ) {
							$type			=	$this->fieldsTypesArray[$fieldName];
						} else {
							$type			=	'sql:string';
						}
						*/
						$type					=	'sql:string';
						$value					=	$this->sqlCleanQuote( $content[$fieldName], $type );
						$setFields[$fieldName]	=	$sqlStatement . ' = ' . $value;
					}
				}
				if ( count( $setFields ) > 0 ) {
					$sql		=	$command . " `" . $this->_db->getEscaped( $this->_table ) . "` AS " . $this->maintableAs
						.	 "\n SET " . implode( ', ', $setFields );
					if ( substr( $command, 0, 6 ) != 'INSERT' ) {
						$sql		.=	( ( count( $this->where ) > 0 )			? "\n WHERE ( "  . implode( ' ) AND ( ', $this->where ) . " )"	: '' )
							.	( ( count( $this->orderArray )  > 0 )	? "\n ORDER BY " . implode( ', ', $this->orderArray )			: '' );
					}
				}
			}
		} else {
			trigger_error( 'SQLXML::_buildSQLqueryUpdateInsert: SQL Update query with LEFT JOIN or GROUP BY is not supported.', E_USER_NOTICE );
		}
		return $sql;
	}
	/**
	 * Performs a merged query between this object and an additional object $addXmlSql :
	 * the fields are taken from $addXmlSql, while the joins, where and groupby are merged.
	 *
	 * @param  XmlQuery $addXmlSql
	 * @return object               with ->_tbl set properly.
	 */
	protected function & queryObjectMergedXmlSql( $addXmlSql ) {
		$sql	= "SELECT " . implode( ', ', $addXmlSql->fieldsArray )
			. " FROM `" . $this->_db->getEscaped( $this->_table ) . "` AS " . $this->maintableAs
			.		( ( ( count( $this->leftJoinArray ) + count( $addXmlSql->leftJoinArray ) )	> 0 ) ? "\n " 		   . implode( "\n ",	   array_merge( $this->leftJoinArray, $addXmlSql->leftJoinArray ) ) : '' )
			.		( ( ( count( $this->where )		    + count( $addXmlSql->where ) )			> 0 ) ? "\n WHERE ( "  . implode( ' ) AND ( ', array_merge( $this->where,		  $addXmlSql->where ) ) . " )"	: '' )
			.		( ( ( /*count( $this->groupByArray )  + */ count( $addXmlSql->groupByArray ) )	> 0 ) ? "\n GROUP BY " . implode( ', ',		 /*  array_merge( $this->groupByArray, */ $addXmlSql->groupByArray )	: '' )
			.		( ( ( count( $this->having )		+ count( $addXmlSql->having ) )			> 0 ) ? "\n HAVING ( " . implode( ' ) AND ( ', array_merge( $this->having,		  $addXmlSql->having ) ) . " )"	: '' )
		;
		$this->_db->setQuery( $sql );

		$array			=	$this->_db->loadAssoc();

		$statisticObj	=	new CheckedOrderedTable( $this->_db, $this->_table, 'id' );

		foreach ( $array as $k => $v ) {
			$statisticObj->$k	=	$v;
		}

		return $statisticObj;
	}

	/**
	 * Treats a <statistic> node and its children <where><column> and <model><data> nodes
	 *
	 * @param  SimpleXmlElement $statistic
	 * @return object                         with ->_tbl set properly.
	 */
	public function processQuery_statistic( $statistic ) {
		$result	=	null;
		//	$classname	=	get_class( $this );
		//	$addXmlSql	=	new $classname( $this->_db, $this->_table );
		$addXmlSql	=	new self( $this->_db, $this->_table, $this->_pluginParams );
		$addXmlSql->tableAs	=	$this->tableAs;

		// <statistic><where> ...
		$additionalWhere	=	$statistic->getElementByPath( 'where' );
		$addXmlSql->process_where( $additionalWhere );

		// <statistic><model> ...
		$model				=	$statistic->getElementByPath( 'model' );
		if ( $model ) {
			$addXmlSql->process_data( $model );
			$result			=	$this->queryObjectMergedXmlSql( $addXmlSql );
		}
		return $result;
	}
	/**
	 * Returns error message of query if any.
	 *
	 * @return string   The error message for the most recent query
	 */
	public function getErrorMsg( ) {
		return $this->_db->getErrorMsg();
	}
	/**
	 * Treats a <where> node and its children <column> nodes
	 *
	 * @param  SimpleXmlElement  $where
	 * @param  array               $filterValuesArray for reverse traversals and columns of type sql:formula: ( 'name' => colName (must match), 'internalvalue' => colValue (value to compare in where) )
	 */
	public function process_where( $where, $filterValuesArray = null ) {
		if ( $where ) {
			$doGroupby				=	( $where->attributes( 'dogroupby' ) != 'false' );
			foreach ( $where->children() as $column ) {
				/** @var $column SimpleXmlElement */
				if ( $column->getName() == 'column' ) {
					$this->process_column( $column, $filterValuesArray, false, $doGroupby );

					$this->processJoinsNeededForCount( array( $column->attributes( 'name' ) => null ) );
				} else {
					trigger_error( 'SQLXML::process_where: child type ' . $column->getName() . ' of where xml tag is not implemented !', E_USER_NOTICE );
				}
			}
		}
	}

	/**
	 * Treats a <joinkeys> node and its children <column> nodes
	 *
	 * @param  SimpleXmlElement  $joinkeys
	 * @param  array               $filterValuesArray for reverse traversals and columns of type sql:formula: ( 'name' => colName (must match), 'internalvalue' => colValue (value to compare in where) )
	 * @return null|string
	 */
	public function process_joinkeys( $joinkeys, $filterValuesArray = null ) {
		if ( $joinkeys ) {
			$doGroupby				=	( $joinkeys->attributes( 'dogroupby' ) != 'false' );
			$expression				=	array();
			foreach ( $joinkeys->children() as $column ) {
				/** @var $column SimpleXmlElement */
				if ( $column->getName() == 'column' ) {
					$expression[]	=	$this->process_column( $column, $filterValuesArray, true, $doGroupby );
				} else {
					trigger_error( 'SQLXML:process_joinkeys: child type ' . $column->getName() . ' of where xml tag is not implemented !', E_USER_NOTICE );
				}
			}
			return implode( ' AND ', $expression );
		} else {
			return null;
		}
	}

	/**
	 * Treats a <filter> node and its children <data> nodes
	 *
	 * @param  SimpleXmlElement  $filter
	 * @param  array               $filterValuesArray for reverse traversals and columns of type sql:formula: ( 'name' => colName (must match), 'internalvalue' => colValue (value to compare in where) )
	 * @param  string              $valueType
	 * @return void
	 */
	public function process_filter( $filter, $filterValuesArray, $valueType )
	{
		if ( ! $filter ) {
			return;
		}

		// Process the filter data:
		$data								=	$filter->getElementByPath( 'data');

		if ( $data ) {
			$where							=	$data->getElementByPath( 'where');

			if ( $where ) {
				if ( cbStartOfStringMatch( $valueType, 'xml:' ) ) {
					// this is a quick fix to make the baskets plan filter still work, as it's very different
					$saveReverse			=	$this->setReverse( true );

					$this->process_where( $where, $filterValuesArray );
					$this->setReverse( $saveReverse );

					return;
				}
			}

			// Only parse data for joins if it hasn't been processed yet:
			if ( $data->attributes( 'dataprocessed' ) != 'true' ) {
				// Process the joins to ensure fields array is correct:
				$this->_addGetJoinAs( $data );

				// Check if the data has a join that needs to be a part of the count:
				$this->processJoinsNeededForCount( array( $data->attributes( 'name' ) => null ) );

				// Ensure this datas join is inner and not left:
				$this->_changeJoinType( $data->attributes( 'name' ) );
			}
		}

		// Process a single filter:
		if ( ! is_array( $filterValuesArray['valuefield'] ) ) {
			$saveAs							=	$this->_currentTableAs;

			if ( isset( $this->fieldsArray[$filterValuesArray['valuefield']] ) ) {
				if ( preg_match( '/^[a-z]\./i', $this->fieldsArray[$filterValuesArray['valuefield']] ) ) {
					$this->_currentTableAs	=	substr( $this->fieldsArray[$filterValuesArray['valuefield']], 0, 1 );
				} else {
					$this->_currentTableAs	=	null;
				}
			} elseif ( isset( $this->leftJoinedFieldsTable[$filterValuesArray['valuefield']] ) ) {
				// Field has already been joined; lets use its tableAs:
				$this->_currentTableAs		=	$this->leftJoinedFieldsTable[$filterValuesArray['valuefield']];
			}

			$this->addWhere( $filterValuesArray['valuefield'], $filterValuesArray['operator'], $filterValuesArray['internalvalue'], $valueType );

			$this->processJoinsNeededForCount( array( $filterValuesArray['valuefield'] => null ) );

			$this->_currentTableAs			=	$saveAs;

			return;
		}

		// Process a repeat filter:
		for ( $i = 0, $n = count( $filterValuesArray['valuefield'] ); $i < $n; $i++ ) {
			$saveAs							=	$this->_currentTableAs;

			$this->_currentTableAs			=	$this->findTableAs( $filterValuesArray['table'][$i], $filterValuesArray['table_key'][$i], 'id', 'sql:field', 'sql:field' );

			if ( $this->_currentTableAs !== false ) {
				$this->addWhere( $filterValuesArray['valuefield'][$i], $filterValuesArray['operator'][$i], $filterValuesArray['internalvalue'][$i], 'const:string' );

				$this->processJoinsNeededForCount( array( $filterValuesArray['valuefield'][$i] => null ) );
			} else {
				$this->processJoinsNeededForCount( array( $filterValuesArray['table'][$i] => null ) );
			}

			$this->_currentTableAs			=	$saveAs;
		}
	}

	/**
	 * Changes the JOIN type from LEFT to INNER if required
	 *
	 * @param  string  $name  Name of the JOIN AS or column name
	 */
	protected function _changeJoinType( $name ) {
		if ( isset( $this->leftJoinedFieldsTable[$name] ) && isset( $this->leftJoinArray[$this->leftJoinedFieldsTable[$name]] ) ) {
			$previous										=	$this->leftJoinArray[$this->leftJoinedFieldsTable[$name]];

			if ( substr( $previous, 0, 10 ) == 'LEFT JOIN ' ) {
				$tableAs									=	$this->leftJoinedFieldsTable[$name];

				$this->leftJoinArray[$tableAs]				=	'INNER JOIN ' . substr( $previous, 10 );

				if ( isset( $this->leftJoinAsArray[$tableAs] ) ) {
					$this->joinsNeededForCount[$tableAs]	=	$this->leftJoinAsArray[$tableAs];
				}
			}
		}
	}

	/**
	 * Treats a <column> node and its children <data> and <where> nodes
	 *
	 * @param  SimpleXmlElement  $column
	 * @param  array    $filterValuesArray  for reverse traversals and columns of type sql:formula: ( 'name' => colName (must match), 'internalvalue' => colValue (value to compare in where) )
	 * @param  boolean  $returnExpression   returns the conditions expression instead of adding it to WHERE or HAVING arrays
	 * @param  boolean  $doGroupBy          returns the conditions expression instead of adding it to WHERE or HAVING arrays
	 * @return null|string|array
	 */
	protected function process_column( $column, $filterValuesArray = null, $returnExpression = false, $doGroupBy = true ) {
		if ( count( $column->children() ) == 0 ) {
			$expression			=	$this->_composeSQLformula( $column );
		} elseif ( $column->attributes( 'valuetype' ) == 'sql:subquery' ) {
			// If valuetype="sql:subquery" then the <column><data> is for the value subquery, processed further down, treat column name normally:
			$expression			=	$this->_composeSQLformula( $column );
			$data				=	$column->getElementByPath( 'data' );
		} else /* if ( count( $column->children() ) == 1 ) */ {
			$data				=	$column->getElementByPath( 'data' );
			if ( $data ) {
				$expression		=	$this->process_column_data( $data );
			} else {
				trigger_error( 'SQLXML::process_column: child of column ' . $column->attributes( 'name' ) . 'is not data !', E_USER_NOTICE );
				return null;
			}
		}

		$colVal					=	$column->attributes( 'value' );
		$colValueType			=	$column->attributes( 'valuetype' );
		$colType				=	$column->attributes( 'type' );
		$addInBrackets			=	true;
		if ( ( $colValueType == 'sql:formula' ) || ( $colValueType == 'sql:expression' ) ) {
			if ( $this->_reverse && isset( $filterValuesArray['name']) && ( $filterValuesArray['name'] == $colVal ) ) {
				$addInBrackets	=	( ! is_array( $filterValuesArray['internalvalue'] ) );
				$colVal			=	$this->sqlCleanQuote( $filterValuesArray['internalvalue'], isset( $filterValuesArray['specialvaluetype'] ) ? $filterValuesArray['specialvaluetype'] : $colType );

				$this->processJoinsNeededForCount( array( $filterValuesArray['name'] => null ) );
			} else {
				if ( count( $column->children() ) != 0 ) {
					$colVal		=	null;
				}
			}
		} elseif ( $colValueType == 'sql:field' ) {
			if ( $this->_reverse && isset( $filterValuesArray['name']) && ( $filterValuesArray['name'] == $colVal ) ) {
				$addInBrackets	=	( ! is_array( $filterValuesArray['internalvalue'] ) );
				$colVal			=	$this->sqlCleanQuote( $filterValuesArray['internalvalue'], $colType );

				$this->processJoinsNeededForCount( array( $filterValuesArray['name'] => null ) );
			} else {
				if ( $doGroupBy ) {
					$this->groupByArray[$this->_currentTableAs . '.' . $colVal]	=	$this->_currentTableAs . '.`' . $colVal . '`';		// this is useful for counts, TBD later: add it only for counts.
				}
				$colVal			=	$this->_currentTableAs . '.' . $this->_db->NameQuote( $colVal );
			}
		} elseif ( $colValueType == 'sql:parentfield' ) {
			if ( $this->_reverse && isset( $filterValuesArray['name']) && ( $filterValuesArray['name'] == $colVal ) ) {
				$addInBrackets	=	( ! is_array( $filterValuesArray['internalvalue'] ) );
				$colVal			=	$this->sqlCleanQuote( $filterValuesArray['internalvalue'], $colType );

				$this->processJoinsNeededForCount( array( $filterValuesArray['name'] => null ) );
			} else {
				$colVal			=	$this->_currentTableAsStack[0] . '.' . $this->_db->NameQuote( $colVal );
			}
		} elseif ( ( $colValueType == 'sql:subquery' ) && isset( $data ) ) {
			$colVal				=	$this->process_subquery( $column, true );
		} else {
			// 'const:string', 'const:int', 'const:float' and much more:
			$addInBrackets		=	( ! is_array( $colVal ) );
			$colVal				=	$this->sqlCleanQuote( $colVal, $colValueType );
			if ($colVal === false ) {
				trigger_error( 'SQLXML::process_column: where column valuetype ' . $colValueType . ' not implemented !', E_USER_NOTICE );
			}
		}
		if ( ( $colVal !== false ) && ( ( $colValueType != 'sql:formula' ) || ( $colValueType == 'sql:expression' ) || ( $colVal !== null ) ) ) {		// in case of formula children, the condition is embedded in joins, except for reverse join traversals.
			// Join expressions have e.g. '`id`=h.`plan_id`' in them. Here we want only the second field for the selector:
			if ( $colValueType != 'sql:expression' ) {
				$joinEqualInExpr	=	strpos( $expression, '=' );
				if ( $joinEqualInExpr !== false ) {
					$expression		=	substr( $expression, $joinEqualInExpr + 1 );
				}

				$operator			=	isset( $filterValuesArray['operator'] ) ? $filterValuesArray['operator'] : $column->attributes( 'operator' );

				if ( ! $addInBrackets ) {
					if ( $operator == '=') {
						$operator	=	'IN';
					} elseif ( $operator == '!=') {
						$operator	=	'NOT IN';
					}
				}

				$expression			=	$expression
									.	' ' . $operator . ' ';
				if ( in_array( strtolower( $operator ), array( 'in', 'not in' ) ) && $addInBrackets ) {
					$expression		.=	'(' . $colVal . ')';
				} else {
					$expression		.=	$colVal;
				}
			}

			if ( $returnExpression ) {
				return $expression;
			} else{
				if ( $doGroupBy && ( $column->attributes( 'tablefield' ) == 'false' ) ) {
					$this->having[]	=	$expression;
					return null;
				}
				if ( substr( $expression, 0, 6 ) == 'COUNT(' ) {
					$this->having[]	=	$expression;
				} else {
					$this->where[]	=	$expression;
				}
			}
		}
		return null;
	}
	/**
	 * Treats a <data> node and its <data> and <where> children
	 *
	 * @param SimpleXmlElement $data
	 * @return array|null|string
	 */
	protected function process_column_data( $data ) {
		$childrenFormulas			=	$this->_composeSQLformula( $data );
		if ( $childrenFormulas != null && is_array( $childrenFormulas ) ) {
			if ( count( $childrenFormulas ) == 1 ) {
				$childrenFormulas	=	implode( '', $childrenFormulas );
			} else {
				trigger_error( 'SQLXML::process_column_data: more than one data in column ' . $data->attributes( 'name' ), E_USER_NOTICE );
			}
		}
		return $childrenFormulas;
	}

	/**
	 * Treats a <field> node and its <data> children and adds it to the fieldsarray
	 *
	 * @param  SimpleXmlElement $field
	 */
	public function process_field( $field ) {
		list( $fieldsArray, $fieldsTypesArray )		=	$this->get_field( $field );

		$this->fieldsArray							=	array_merge( $this->fieldsArray, $fieldsArray );
		$this->fieldsTypesArray						=	array_merge( $this->fieldsTypesArray, $fieldsTypesArray );
	}

	/**
	 * Treats a <field> node and its <data> children
	 *
	 * @param  SimpleXmlElement $field
	 * @return array                    The array of fields added by $field
	 */
	private function get_field( $field ) {
		$cnt_name			=	$field->attributes( 'name' );
		$cnt_type			=	$field->attributes( 'type' );
		$cnt_as				=	$field->attributes( 'as' );
		$fieldsArray		=	array();
		$fieldsTypesArray	=	array();

		if ( $cnt_name === '' ) {
			return array( $fieldsArray, $fieldsTypesArray );
		}

		if ( ! $this->_has_data_children( $field ) ) {
			if ( isset( $this->fieldsArray[$cnt_name] ) ) {
				if ( preg_match( '/^[a-z]\./i', $this->fieldsArray[$cnt_name] ) ) {
					$tableAs = substr( $this->fieldsArray[$cnt_name], 0, 1 );
				} elseif ( preg_match( '/^[a-z]*\(/i', $this->fieldsArray[$cnt_name] ) ) {
					// formulas start with '(' or with a 'FUNCTION(' and thus have no associated tables:
					$tableAs	=	null;
				} else {
					$tableAs	=	$this->_currentTableAs;
				}
			} elseif ( isset( $this->leftJoinedFieldsTable[$cnt_name] ) ) {
				// Field has already been joined; lets use its tableAs:
				$tableAs		=	$this->leftJoinedFieldsTable[$cnt_name];
			} else {
				// Field belongs to the current table; lets use its tableAs:
				$tableAs		=	$this->_currentTableAs;
			}

			$fieldsArray[( $cnt_as ? $cnt_as : $cnt_name )]			=	( $tableAs ? $this->_db->getEscaped( $tableAs ) . '.' : '' ) . '`' . $this->_db->getEscaped( $cnt_name ) . '`'
																	.	( $cnt_as ? ' AS `' . $cnt_as . '`' : '' );
			$fieldsTypesArray[( $cnt_as ? $cnt_as : $cnt_name )]	=	$cnt_type;
		} else {
			// special field type: instructions in child element <data>:
			foreach ( $field->children() as $data ) {
				/** @var $data SimpleXmlElement */
				if ( $data->getName() == 'data' ) {
					list( $dataFieldsArray, $dataFieldsTypesArray )		=	$this->get_data( $data );

					$fieldsArray			=	array_merge( $fieldsArray, $dataFieldsArray );
					$fieldsTypesArray		=	array_merge( $fieldsTypesArray, $dataFieldsTypesArray );

					// process <field paramvalues="field1 field2">
					$paramValues	=	$field->attributes( 'paramvalues' );				// not sure if it's even used !!!!
					if ( $paramValues ) {
						$paramValuesTypes	=	(string) $field->attributes( 'paramvaluestypes' );
						$paramValues		=	explode( ' ', $paramValues );
						$paramValuesTypes	=	explode( ' ', $paramValuesTypes );
						foreach ( $paramValues as $k => $p ) {
							if ( ! ( isset( $paramValuesTypes[$k] ) && $paramValuesTypes[$k] ) ) {
								$paramValuesTypes[$k]			=	'sql:string';
							}
							$paramTypeArr	=	explode( ':', $paramValuesTypes[$k], 2 );
							if ( $paramTypeArr[0] == 'sql' ) {
								if ( ! ( isset( $fieldsArray[$p] ) || isset( $this->fieldsArray[$p] ) || isset( $this->leftJoinedFieldsTable[$p] ) ) ) {
									$fieldsArray[$p]		=	$this->_db->getEscaped( $this->_currentTableAs ) . '.`' . $this->_db->getEscaped( $p ) . '`';
									$fieldsTypesArray[$p]	=	( isset( $paramValuesTypes[$k] ) ? $paramValuesTypes[$k] : 'sql:string' );
								}
							}
						}
					}

				} elseif ( ! ( ( ( $field->attributes( 'type' ) == 'ordering' ) && ( $data->getName() == 'orderinggroup' ) ) || ( $data->getName() == 'attributes' ) || ( $data->getName() == 'option' ) ) ) {
					trigger_error( 'SQLXML::process_field: child ' . $data->getName() . ' of field is not implemented !', E_USER_NOTICE );
				}
			}
		}

		return array( $fieldsArray, $fieldsTypesArray );
	}
	/**
	 * Checks if a <...> node has <data> children
	 *
	 * @param  SimpleXmlElement  $field
	 * @return boolean             TRUE if at least one child is <data>, FALSE otherwise
	 */
	protected function _has_data_children( $field ) {
		$dataChilds		=	 ( count( $field->children() ) > 0 );
		if ( $dataChilds ) {
			$dataChilds	=	false;
			foreach ( $field->children() as $child ) {
				/** @var $child SimpleXmlElement */
				if ( $child->getName() == 'data' ) {
					$dataChilds	=	true;
					break;
				}
			}
		}
		return $dataChilds;
	}
	/**
	 * Treats a <data> node and its <data> and <where> children and adds it to the fieldsarray
	 *
	 * @param  SimpleXmlElement $data
	 */
	public function process_data( $data ) {
		list( $fieldsArray, $fieldsTypesArray )		=	$this->get_data( $data );

		$this->fieldsArray							=	array_merge( $this->fieldsArray, $fieldsArray );
		$this->fieldsTypesArray						=	array_merge( $this->fieldsTypesArray, $fieldsTypesArray );
	}
	/**
	 * Treats a <data> node and its <data> and <where> children
	 *
	 * @param  SimpleXmlElement $data
	 * @return array                   The array of fields added by $data
	 */
	private function get_data( $data ) {
//BB	if ( ! $this->_table ) {
//BB  		$this->_table	=	$data->attributes( 'table' );													//TBD: CHECK IF REALLY NOT NEEDED ! it breaks the COUNT(*) by adding a left join on main screen !
//BB	}
		$fieldsArray					=	array();
		$fieldsTypesArray				=	array();

		if ( $data->attributes( 'type' ) == 'sql:subquery' ) {
			$childrenFormulas			=	$this->process_subquery( $data, false );
		} else {
			$childrenFormulas			=	$this->_composeSQLformula( $data );
		}

		if ( $childrenFormulas != null ) {
			if ( ! is_array( $childrenFormulas ) ) {
				$cnt_name				=	$data->attributes( 'name' );
				$cnt_as					=	$data->attributes( 'as' );
				$cnt_type				=	$data->attributes( 'type' );
				$childrenFormulas		=	array( ( $cnt_as ? $cnt_as : $cnt_name ) => $childrenFormulas );
				$childrenFormulasType	=	array( ( $cnt_as ? $cnt_as : $cnt_name ) => $cnt_type );
				$fieldsTypesArray		=	array_merge( $fieldsTypesArray, $childrenFormulasType );		// otherwise done inside _composeSQLformula in process_sql_field.
			}

			$fieldsArray				=	array_merge( $fieldsArray, $childrenFormulas );
		}

		return array( $fieldsArray, $fieldsTypesArray );
	}
	/**
	 * Treats a <data type="sql:subquery"> node and its <data> and <where> children
	 *
	 * @param  SimpleXmlElement $data
	 * @param  boolean          $suppressAs  [optional] if true, do not output the AS statement (e.g. if used inside formulas it's not allowed)
	 * @return string
	 */
	protected function process_subquery( $data, $suppressAs = false ) {
		$subqueryData				=	$data->getChildByNameAttributes( 'data' );
		$subqueryTable				=	$subqueryData->attributes( 'table' );
		$subqueryName				=	$subqueryData->attributes( 'name' );
		$queryAs					=	$data->attributes( 'as' );
		$this->_levelPush();
		$this->incrementTableAs();
		$xmlsql						=	new self( $this->_db, $subqueryTable, $this->_pluginParams );
		$xmlsql->syncSubQueryTablesIndexes( $this );
//		$xmlsql->_currentTableAs	=	$this->tableAs;
		$xmlsql->maintableAs		=	$this->tableAs;
//		$xmlsql->_table				=	$subqueryTable;
		$xmlsql->process_data( $subqueryData );
		$childrenFormulas			=	'( ' . $xmlsql->_buildSQLquery() . ' )'
			.	( ( $queryAs || $subqueryName ) && ! $suppressAs ? ' AS ' . $this->_db->NameQuote( $queryAs ? $queryAs : $subqueryName ) : '' );
		$this->_levelPop();
		return $childrenFormulas;
	}
	/**
	 * Treats recursively a <data> or <field> node and its children
	 *
	 * @access private
	 * @param  SimpleXmlElement  $data
	 * @return array|null|string
	 */
	protected function _composeSQLformula( $data ) {
		$this->_levelPush();

		$dType				=	$data->attributes( 'type' );

		$formula			=	null;				// should not be used here
		$moreFormulas		=	null;

		if ( ! $this->_reverse ) {
			if ( $dType == 'sql:field' ) {
				$formula	=	$this->process_sql_field( $data );
			} elseif ( $dType == 'sql:count' ) {
				$formula	=	$this->process_sql_count( $data );
			}
		}
		$subFormula									=	array();

		if ( ( ! $this->_reverse ) || ( $data->attributes( 'table' ) && ( $data->attributes( 'table' ) != $this->_table ) ) ) {

			/** @var $child SimpleXmlElement */
			foreach ( $data->children() as $child ) {
				switch ( $child->getName() ) {
					case 'data':
						// recurse to process bottom-up
						if ( $child->attributes( 'type' ) == 'sql:subquery' ) {
							$childrenFormulas			=	$this->process_subquery( $child, true );
						} else {
							$childrenFormulas			=	$this->_composeSQLformula( $child );
						}
						if ( is_array( $childrenFormulas ) ) {
							$childrenFormulas	=	implode( ', ', $childrenFormulas );
						}
						$subFormula[]			=	$childrenFormulas;

						if ( ( ! $this->_reverse ) && $child->attributes( 'select' ) == 'true' ) {
							$moreFormulas		.=	', ' . $childrenFormulas;
						}
						break;
					case 'where':
						$this->_levelPush();
						$this->process_where( $child );
						$this->_levelPop();
						break;
					default:
						break;
				}
			}
		}
		switch ( $dType ) {
			case 'sql:count':						// count of related records in other table:
				//			if ( $this->_reverse ) {
				//				$formula	=	$this->process_sql_count( $data );
				//			}
				break;
			case 'sql:field':						// field value taken from related record in other table:
				if ( $this->_reverse ) {
					$formula	=	$this->process_reverse_sql_field( $data, $subFormula );
				}
				break;
			case 'sql:row':				// multiple field values taken from related record in other table:
			case 'sql:multiplerows':				// multiple field values taken from related record in other table:
				//			if ( $this->_reverse ) {
				//				$formula	=	$this->process_multiplerows( $data );
				//			}
				break;
			case 'sql:operator':					// any SQL operator between fields ( +, -, *, /, ...)
				$cnt_name	=	$this->_db->getEscaped( $data->attributes( 'name' ) );
				$operator	=	$data->attributes( 'operator' );
				$formula	=	'( ' . implode( ' ' . $operator . ' ', $subFormula ) . ' )' . ( $cnt_name ? ' AS `' . $cnt_name . '`' : '' );
				break;
			case 'sql:function':					// any SQL function of fields ( SUM( f1, f2 ), AVG(...) )
				$cnt_name	=	$this->_db->getEscaped( $data->attributes( 'name' ) );
				$operator	=	$data->attributes( 'operator' );
				$formula	=	$operator . '( ' . implode( ', ', $subFormula ) . ' ) ' . ( $cnt_name ? 'AS `' . $cnt_name . '`' : '' );
				if ( $operator == 'GROUP_CONCAT' ) {
					// Normally GROUP_CONCAT is only 1 kB, increase it to maximum value:
					$this->increaseGroupConcatMaxLen();
				}
				break;
			case 'sql:formula':					// any SQL formula of fields ( GROUP_CONCAT( f1 f2 f3 ), SUM(...) )
			case 'sql:expression':
				$cnt_name	=	$this->_db->getEscaped( $data->attributes( 'name' ) );
				$operator	=	$data->attributes( 'operator' );
				$formula	=	$operator . '( ' . implode( ' ', $subFormula ) . ' ) ' . ( $cnt_name ? 'AS `' . $cnt_name . '`' : '' );
				if ( $operator == 'GROUP_CONCAT' ) {
					// Normally GROUP_CONCAT is only 1 kB, increase it to maximum value:
					$this->increaseGroupConcatMaxLen();
				}
				break;
			case null:								// top-level probably, no type...
				$formula	=	$subFormula;		// if not at top level, it will get imploded at level above.
				break;
			case 'param':							// a plugin parameter of type string
			case 'param:string':					// a plugin parameter of type string
			case 'param:int':						// a plugin parameter of type int
			case 'param:float':						// a plugin parameter of type float
			case 'param:datetime':					// a plugin parameter of type datetime
				$formula	=	$this->process_param( $data );
				break;
			default:
				// 'const:string', 'const:int', 'const:float' and much more:
				$name		=	$data->attributes( 'name' );
				if ( $data->attributes( 'translate' ) == 'yes' ) {
					$name	=	CBTxt::T( $name );
				}
				$formula	=	$this->sqlCleanQuote( $name, $dType );
				if ($formula === false ) {
					trigger_error( 'SQLXML::_composeSQLformula: data type ' . $dType . ' is not implemented !', E_USER_NOTICE );
				}
				break;
		}

		$this->_levelPop();

		if ( is_array( $formula ) ) {
			if ( $moreFormulas ) {
				$formula[]	=	$moreFormulas;
			}
		} else {
			$formula		.=	$moreFormulas;
		}

		return $formula;
	}

	/**
	 * Treats a <data type="sql:field"> node and its children:
	 * Field value taken from related record in other table
	 *
	 * @access private
	 * @param  SimpleXmlElement  $data
	 * @return string
	 */
	protected function process_sql_field( $data ) {
		$cnt_name			=	$data->attributes( 'name' );
		$cnt_as				=	$data->attributes( 'as' );
		$cnt_table			=	$data->attributes( 'table' );
		$cnt_key			=	$data->attributes( 'key' );
		$cnt_val			=	$data->attributes( 'value' );
		$cnt_valtype		=	$data->attributes( 'valuetype' );

		$tableAs			=	$this->_addGetJoinAs( $data );
		if ( $tableAs ) {

			$formula		=	$tableAs . '.`' . $cnt_name . '`'
				.	( $cnt_as ? ' AS `' . $cnt_as . '`' : '' );

		} else {
			if ( $cnt_table && ! $this->_table ) {
				$this->_table							=	$cnt_table;
				$this->_currentTableAs					=	$this->tableAs;		//TBD: CHECK IF I CAN ADD THIS HERE...
			}

			$formula		=	$this->_currentTableAs . '.`' . $cnt_name . '`'
				.	( $cnt_as ? ' AS `' . $cnt_as . '`' : '' );

			// collect field type for UPDATE or INSERT use:
			if ( $cnt_valtype ) {
				$this->fieldsTypesArray[$cnt_name]		=	$cnt_valtype;
			}

			// collect implicit where statement with <field name="xxx" ... key="id" value="1 valuetype="const:int"> :
			if ( $cnt_key && $cnt_val && $cnt_valtype ) {
				$this->addWhere( $cnt_key, '=', $cnt_val, $cnt_valtype );
			}

		}
		return $formula;
	}

	/**
	 * Treats a <data type="sql:count"> node and its children:
	 * Count of related records in other table.
	 *
	 * @access private
	 * @param  SimpleXmlElement  $data
	 * @return string
	 */
	protected function process_sql_count( $data ) {
		if ( $data->getElementByPath( 'joinkeys' ) ) {
			return $this->process_sql_count_join( $data );
		}
		$cnt_name				=	$data->attributes( 'name' );
		$cnt_table				=	$data->attributes( 'table' );
		$cnt_key				=	$data->attributes( 'key' );
		$cnt_val				=	$data->attributes( 'value' );
		$cnt_valtype			=	$data->attributes( 'valuetype' );
		$cnt_distinct			=	$data->attributes( 'distinct' );

		if ( ! $cnt_table ) {
			if ( $cnt_distinct ) {
				$formula		=	'COUNT( DISTINCT ' . $this->_currentTableAs . '.`' . $cnt_distinct . '` )';
			} else {
				$formula		=	'COUNT(*)';
			}
			if ( $cnt_name ) {
				$formula		.=	' AS `' . $cnt_name . '`';
			}
			if ( $cnt_key && $cnt_val && $cnt_valtype ) {
				$this->addWhere( $cnt_key, '=', $cnt_val, $cnt_valtype );
			}
		} elseif ( ( $cnt_table == $this->_table ) || ! $this->_table ) {
			if ( $cnt_distinct ) {
				$formula		=	'COUNT( DISTINCT ' . $this->_currentTableAs . '.`' . $cnt_distinct . '` )';
			} else {
				$formula		=	'COUNT(*)';
			}
			if ( $cnt_name ) {
				$formula		.=	' AS `' . $cnt_name . '`';
			}
			if ( $cnt_table && ! $this->_table ) {
				$this->_table	=	$cnt_table;
			}

			// collect implicit HAVING statement with <field name="xxx" ... key="id" value="1 valuetype="const:int"> :
			if ( ! $cnt_valtype ) {
				$cnt_valtype	=	'sql:parentfield';
			}
			if ( $cnt_key && $cnt_val ) {
				$this->addWhere( $cnt_key, '=', $cnt_val, $cnt_valtype );
			}
		} else {

			//		return $this->process_subquery( $data, false );
			$subqueryTable				=	$data->attributes( 'table' );
			$subqueryName				=	$data->attributes( 'name' );
			$this->_levelPush();
			$this->incrementTableAs();
			$xmlsql						=	new self( $this->_db, $subqueryTable, $this->_pluginParams );
			$xmlsql->syncSubQueryTablesIndexes( $this );
			$xmlsql->maintableAs		=	$this->tableAs;
			$xmlsql->process_data( $data );
			$formula					=	'( ' . $xmlsql->_buildSQLquery() . ' )'
				.	( $subqueryName ? ' AS ' . $this->_db->NameQuote( $subqueryName ) : '' );
			$this->_levelPop();
		}
		return $formula;
	}

	/**
	 * Treats a <data type="sql:count"> node and its children:
	 * Count of related records in other table.
	 *
	 * @access private
	 * @param  SimpleXmlElement  $data
	 * @return string
	 */
	protected function process_sql_count_join( $data ) {
		$cnt_name				=	$data->attributes( 'name' );
		$cnt_table				=	$data->attributes( 'table' );
		$cnt_key				=	$data->attributes( 'key' );
		$cnt_val				=	$data->attributes( 'value' );
		$cnt_valtype			=	$data->attributes( 'valuetype' );
		$cnt_distinct			=	$data->attributes( 'distinct' );
		/*
				if ( ( $cnt_table && $this->_table ) && ( $cnt_table != $this->_table ) ) {
					// count of related records in other table:
					$this->incrementTableAs();

					$this->leftJoinArray[$this->tableAs]	= 'LEFT JOIN `' . $cnt_table . '` AS ' . $this->tableAs
															. ' ON ' . $this->tableAs . '.`' . $cnt_key . '` = ' . $this->_currentTableAs . '.`' . $cnt_val . '`';
					$this->leftJoinedFieldsTable[$cnt_name]	=	$this->tableAs;
		*/
		$tableAs				=	$this->_addGetJoinAs( $data );
		if ( $tableAs ) {
			if ( $cnt_distinct ) {
				$formula		=	'COUNT( DISTINCT ' . $tableAs . '.`' . $cnt_distinct . '` )';
			} else {
				$formula		=	'COUNT( ' . $tableAs . '.`' . $cnt_key . '` )';
			}
			if ( $cnt_name ) {
				$formula		.=	' AS `' . $cnt_name . '`';
			}
			if ( $cnt_val ) {
				$this->groupByArray[$this->_currentTableAs . '.' . $cnt_val]	=	$this->_currentTableAs . '.`' . $cnt_val . '`';
			}
		} else {
			if ( $cnt_distinct ) {
				$formula		=	'COUNT( DISTINCT ' . $this->_currentTableAs . '.`' . $cnt_distinct . '` )';
			} else {
				$formula		=	'COUNT(*)';
			}
			if ( $cnt_name ) {
				$formula		.=	' AS `' . $cnt_name . '`';
			}
			if ( $cnt_table && ! $this->_table ) {
				$this->_table	=	$cnt_table;
			}

			// collect implicit HAVING statement with <field name="xxx" ... key="id" value="1 valuetype="const:int"> :
			if ( $cnt_key && $cnt_val && $cnt_valtype ) {
				$this->addWhere( $cnt_key, '=', $cnt_val, $cnt_valtype );
			}
		}
		return $formula;
	}

	/**
	 * Treats a <data type="sql:field"> node and its children:
	 * Field value taken from related record in other table
	 *
	 * @access private
	 * @param  SimpleXmlElement  $data
	 * @param  array               $subFormula
	 * @return null|string
	 */
	protected function process_reverse_sql_field( $data, $subFormula )
	{
		$formula			=	null;

		$cnt_name			=	$data->attributes( 'name' );
		$cnt_table			=	$data->attributes( 'table' );
		$cnt_key			=	$data->attributes( 'key' );
		$cnt_val			=	$data->attributes( 'value' );
		$cnt_valtype		=	$data->attributes( 'valuetype' );
		$cnt_tablefield		=	$data->attributes( 'tablefield' );

		if ( $cnt_table && $this->_table ) {
			if  ( $cnt_table == $this->_table ) {

				if ( ( ! $cnt_valtype ) || ( $cnt_valtype == 'sql:field' ) ) {

					$formula	=	/* $this->tableAs . '.' . */ '`' . $cnt_val . '`'											// small shortcut allowing only one level for now
						.	' = '
						.	$this->_currentTableAs . '.`' . $cnt_key . '`';
				}
			} else {
				$this->_addGetJoinAs( $data, $subFormula );

				$formula		=	( $cnt_val !== null ? $this->_db->NameQuote( $cnt_val ) . ' = ' : '' )
								.	$this->tableAs . '.`' . $cnt_name . '`';
			}
		} else {
			$tableAs			=	$this->_addGetJoinAs( $data );
			if ( $tableAs ) {
				$formula		=	$tableAs . '.`' . $cnt_name . '`';
			} else {
				if ( $cnt_tablefield != 'false' ) {
					$formula	=	$this->tableAs . '.`' . $cnt_name . '`';
				} else {
					$formula	=	'`' . $cnt_name . '`';
				}
			}
		}
		return $formula;
	}

	/**
	 * @param  string                    $joinTable The table being joined
	 * @param  string                    $joinAS    The table alias for this join
	 * @param  SimpleXMLElement|boolean  $joinkeys  <joinkeys> node (null means LEFT JOIN)
	 * @return string                            LEFT JOIN  or any other JOIN type
	 */
	protected function computeJoinKeyword( $joinTable, $joinAS, $joinkeys = null )
	{
		$joinKeyword		=	'LEFT JOIN';

		if ( is_object( $joinkeys ) ) {
			$joinType				=	$joinkeys->attributes( 'type' );
			if ( $joinType ) {
				if ( in_array( $joinType, array( 'inner', 'left', 'right', 'outer', 'left outer', 'right outer', 'cross', 'natural left', 'natural right', 'natural left outer', 'natural right outer', 'straight' ) ) ) {
					$joinKeyword		=	strtoupper( $joinType ) . ( $joinType == 'straight' ? '_' : ' ' ) . 'JOIN';
					if ( $joinType != 'left' ) {
						$this->joinsNeededForCount[$joinAS]	=	$joinTable;
					}
				} else {
					trigger_error( sprintf( 'SQLXML::computeJoinKeyword joinkeys type="%s" unknown JOIN type: Using LEFT JOIN instead.', $joinType ), E_USER_NOTICE );
				}
			}
		}

		return $joinKeyword;
	}

	/**
	 * If a left join is needed by the <data> $data element:
	 * Adds if not yet added a JOIN statement for the <data> element $data
	 * and returns the table AS alias for accessing the corresponding table.
	 * Otherwise returns NULL.
	 *
	 * @param  SimpleXmlElement  $data
	 * @param  array             $subFormula  For process_reverse_sql_field ONLY (if we decide to use): SQL conditions in array which are imploded by AND for the merge
	 * @return string|null                    Name of table alias (a to z) if the <data> element required a left join.
	 */
	protected function _addGetJoinAs( $data, $subFormula = null ) {
		$tableAs			=	null;

		$cnt_name			=	$data->attributes( 'name' );
		$cnt_as				=	$data->attributes( 'as' );
		$cnt_table			=	$data->attributes( 'table' );
		$cnt_key			=	$data->attributes( 'key' );
		$cnt_val			=	$data->attributes( 'value' );
		$cnt_valtype		=	$data->attributes( 'valuetype' );
		$cnt_joinkeys		=	$data->getElementByPath( 'joinkeys' );

		if ( ( $cnt_table && $this->_table )
			&& ( ( $cnt_table != $this->_table ) || ( $cnt_key && $cnt_val ) || $cnt_joinkeys ) )
		{
			if ( $cnt_joinkeys ) {
				if ( ( ! $cnt_key ) && ( ! $cnt_val ) ) {
					// compute the array-indexes for the leftJoindTableKeyValueArray:
					foreach ( $cnt_joinkeys->children() as $column ) {
						/** @var $column SimpleXmlElement */
						$cnt_key[]		=	$column->attributes( 'name' );
						$cnt_val[]		=	$column->attributes( 'value' );
						$cnt_valtype[]	=	$column->attributes( 'valuetype' );
						$subFormula[]	=	$column->attributes( 'operator' ) . $column->attributes( 'type' );
						// Could be this but that doesn't work for group-search in CB User Management, to check later why:
						// $subFormula[]	=	$this->_db->NameQuote( $column->attributes( 'name' )) . ' ' . $column->attributes( 'operator' ) . ' ' . $this->_currentTableAs . '.'
						// 	.	( in_array( $column->attributes( 'valuetype' ), array( 'sql:field' ) ) ? $this->_db->NameQuote( $column->attributes( 'value' ) ) : $column->attributes( 'value' ) );
					}
					$cnt_key			=	implode( '&', $cnt_key );
					$cnt_val			=	implode( '&', $cnt_val );
					$cnt_valtype		=	implode( '&', $cnt_valtype );			// Above change would make this look like WHERE statements: sql:field`user_id` = a.`id`
					// done below: $subFormulaArrayKey	=	implode( '&', $subFormula );
				} else {
					trigger_error( sprintf( 'SQLXML::addGetJoinAs notice: data %s has joinkeys and key="%s" and/or value="%s" at same time. Ignoring key and value.', $cnt_name, $cnt_key, $cnt_val ), E_USER_NOTICE );
				}
			}

			$subFormulaArrayKey		=	is_array( $subFormula ) ? implode( '&', $subFormula ) : $subFormula;

			// if different table or same table but a key and a value are specified as self-join,
			// field value is taken from related record in other or self-joined table:

			if ( isset( $this->leftJoindTableKeyValueArray[$cnt_table][$cnt_key][$cnt_val][$cnt_valtype . $subFormulaArrayKey] ) ) {
				$tableAs				=	$this->leftJoindTableKeyValueArray[$cnt_table][$cnt_key][$cnt_val][$cnt_valtype . $subFormulaArrayKey];

				// We've a cached query, but lets goahead and check if we need its table for joins:
				$this->computeJoinKeyword( $cnt_table, $tableAs, $cnt_joinkeys );
			} else {
				$this->incrementTableAs();
				$tableAs	=	$this->tableAs;

				// Compute JOIN keyword, e.g. LEFT JOIN; and see if we need joins for count query:
				$joinKeyword	=	$this->computeJoinKeyword( $cnt_table, $tableAs, $cnt_joinkeys );

				if ( $cnt_joinkeys ) {
					$subFormulaReal		=	$this->process_joinkeys( $cnt_joinkeys );
					$this->leftJoinArray[$tableAs]	= $joinKeyword . ' ' . $this->_db->NameQuote( $cnt_table ) . ' AS ' . $tableAs
						. ' ON ' . $subFormulaReal;

					$this->leftJoinAsArray[$tableAs]	= $cnt_table;
				} elseif ( ( ! $cnt_valtype ) || ( $cnt_valtype == 'sql:field' ) || ( $cnt_valtype == 'sql:parentfield' ) ) {

					$this->leftJoinArray[$this->tableAs]	= $joinKeyword . ' ' . $this->_db->NameQuote( $cnt_table ) . ' AS ' . $this->tableAs
						. ' ON ' . $this->tableAs . '.'
						. ( ( $subFormula === null ) ? '`' . $cnt_key . '` = ' . ( $cnt_valtype == 'sql:parentfield' ? $this->_currentTableAsStack[0] : $this->_currentTableAs ) . '.`' . $cnt_val . '`'
							: implode( ' AND ', $subFormula ) );

					$this->leftJoinAsArray[$this->tableAs]	= $cnt_table;
					//TBD this $subFormula is a temporary simplification/hack for process_reverse_sql_field ONLY: not even sure if it's needed !!!	: check if really needed.

				} elseif ( $cnt_key && $cnt_val && $cnt_valtype )  {
					$value		=	$this->sqlCleanQuote( $cnt_val, $cnt_valtype );

					$this->leftJoinArray[$this->tableAs]	= $joinKeyword . ' ' . $this->_db->NameQuote( $cnt_table ) . ' AS ' . $this->tableAs
						. ' ON ' . $this->tableAs . '.`' . $cnt_key . '` = ' . $value;

					$this->leftJoinAsArray[$this->tableAs]	= $cnt_table;
				}

				$this->leftJoindTableKeyValueArray[$cnt_table][$cnt_key][$cnt_val][$cnt_valtype . $subFormulaArrayKey]	=	$this->tableAs;
			}

			$this->leftJoinedFieldsTable[( $cnt_as ? $cnt_as : $cnt_name )]		=	$tableAs;
		}
		return $tableAs;
	}
	/**
	 * Treats a <data type="param:..."> node and its children:
	 * Count of related records in other table.
	 *
	 * @access private
	 * @param  SimpleXmlElement  $data
	 * @return string
	 */
	protected function process_param( $data ) {
		$cnt_name		=	$data->attributes( 'name' );
		$cnt_val		=	$data->attributes( 'value' );
		$cnt_valtype	=	$data->attributes( 'valuetype' );

		$formula		=	$this->sqlCleanQuote( $cnt_val, $cnt_valtype );

		if ( $cnt_name ) {
			$formula	.=	' AS `' . $cnt_name . '`';
		}
		return $formula;
	}

	/**
	 * Treats a <data> node and its <data> and <where> children
	 * NOT USED !?
	 *
	 * @param SimpleXmlElement  $data
	 * @param string              $fieldName
	 * @param string              $operator
	 * @param string              $fieldValue
	 * @param string              $type        ( 'sql:string' (default), 'sql:int', 'sql:float' )
	 */
	protected function addReverseWhere( $data, $fieldName, $operator, $fieldValue, $type ) {
		$childrenFormulas			=	$this->_composeSQLformula( $data );
		if ( $childrenFormulas != null ) {
			if ( ! is_array( $childrenFormulas ) ) {
				$cnt_name			=	$data->attributes( 'name' );
				$cnt_as				=	$data->attributes( 'as' );
				$childrenFormulas	=	array( ( $cnt_as ? $cnt_as : $cnt_name ) => $childrenFormulas );
			}
			$this->fieldsArray		=	array_merge( $this->fieldsArray, $childrenFormulas );
		}
		$this->_levelPush();
		$this->addWhere( $fieldName, $operator, $fieldValue, $type );
		$this->_levelPop();
	}

	/**
	 * Increments the "b" table alias name of "AS b" for joined tables
	 *
	 * @access private
	 */
	protected function incrementTableAs( ) {
		$this->tableAs	= chr( ord( $this->tableAs ) + 1 );
		$this->_currentTableAsStack[$this->_currentTableAsStackIdx]		=	$this->tableAs;
	}
	/**
	 * Pushes stack level one level down
	 */
	protected function _levelPush( ) {
		$this->_currentTableAs			=	$this->_currentTableAsStack[$this->_currentTableAsStackIdx];
		$this->_currentTableAsStack[]	=	$this->_currentTableAs;
		++$this->_currentTableAsStackIdx;
	}
	/**
	 * Pops stack level one level up
	 */
	protected function _levelPop( ) {
		--$this->_currentTableAsStackIdx;
		array_pop( $this->_currentTableAsStack );
		if ( $this->_currentTableAsStackIdx ) {
			$this->_currentTableAs			=	$this->_currentTableAsStack[$this->_currentTableAsStackIdx - 1];
		} else {
			$this->_currentTableAs			=	$this->maintableAs;
		}
	}


	/**
	 * Finds a left-joined $tableName with $key of $keyType = $value with $valueType and returns its 'AS' table-alias
	 *
	 * @param  string  $tableName
	 * @param  string  $key
	 * @param  string  $value
	 * @param  string  $keyType
	 * @param  string  $valueType
	 * @return boolean|string
	 */
	protected function findTableAs( $tableName, $key, $value, $keyType, $valueType )
	{
		if ( $tableName == $this->_table ) {
			return $this->maintableAs;
		}

		if ( isset( $this->leftJoindTableKeyValueArray[$tableName] )
			&& isset( $this->leftJoindTableKeyValueArray[$tableName][$key] )
			&& isset( $this->leftJoindTableKeyValueArray[$tableName][$key][$value] )
			&& isset( $this->leftJoindTableKeyValueArray[$tableName][$key][$value][$keyType . '=' . $valueType] )
		)
		{
			return $this->leftJoindTableKeyValueArray[$tableName][$key][$value][$keyType . '=' . $valueType];
		}

		return false;
	}

	/**
	 * Makes the tableAs entries references to the main query $mainQuery
	 *
	 * @param  XmlQuery  $mainQuery
	 * @return void
	 */
	public function syncSubQueryTablesIndexes( $mainQuery ) {
		//	$this->maintableAs					=	$mainQuery->maintableAs;
		$this->tableAs						=	$mainQuery->tableAs;
		//	$this->_currentTableAs				=	$mainQuery->_currentTableAs;
		$this->_currentTableAsStack			=	$mainQuery->_currentTableAsStack;
		$this->_currentTableAsStackIdx		=	$mainQuery->_currentTableAsStackIdx;
		//	$this->fieldsArray					=	$mainQuery->fieldsArray;
		//	$this->fieldsTypesArray				=	$mainQuery->fieldsTypesArray;
		$this->leftJoinedFieldsTable		=	$mainQuery->leftJoinedFieldsTable;
		//	$this->_table						=	$mainQuery->_table;
		$this->_pluginParams				=	$mainQuery->_pluginParams;
		$this->_extDataModels				=	$mainQuery->_extDataModels;
		// not referenced:
		/** array of individual table-alias => "LEFT JOIN ... ON ... " expressions for SELECT
		 *  @access private
		 *  @var array of string */
//	private $leftJoinArray				=	array();
		/** array of individual [table][key][value] -alias => "LEFT JOIN ... ON ... " expressions for SELECT
		 *  @access private
		 *  @var array of string */
//	private $leftJoindTableKeyValueArray =	array();
		/** array of individual table-alias => "LEFT JOIN ... ON ... " expressions used for SELECT COUNT(*),
		 *  as they are needed by the "WHERE" statements (these are duplicated in the ->leftJoinArray.
		 *  @access private
		 *  @var array of string */
//	private $leftJoinsNeededForWhere	=	array();
		/** individual fields (or formula) expressions for "WHERE" (will be imploded with "AND")
		 *  @access private
		 *  @var array of string */
//	private $where						=	array();
		/** array of individual expressions used for "GROUP BY" (will be imploded with "AND")
		 *  @access private
		 *  @var array of string */
//	private $groupByArray				=	array();
		/** individual fields (or formula) expressions for "HAVING"
		 *  @access private
		 *  @var array of string */
//	private $having						=	array();
		/** individual fields (or formula) with "ASC/DESC" expressions for "ORDER BY"
		 *  @access private
		 *  @var array of string */
//	private $orderArray					=	array();
		/** database object
		 * @access private
		 * @var DatabaseDriverInterface */
//	private $_db;
		/** Internal state for XML->SQL traversal and query-generation mode
		 * @access private
		 * @var boolean */
//	private $_reverse					=	false;
	}
	/**
	 * Cleans the field value by type in a secure way for SQL
	 *
	 * @param  mixed  $fieldValue
	 * @param  string $type         const,sql,param : string,int,float,datetime,formula
	 * @return string or boolean FALSE in case of type error
	 */
	public function sqlCleanQuote( $fieldValue, $type ) {
		return XmlTypeCleanQuote::sqlCleanQuote( $fieldValue, $type, $this->_pluginParams, $this->_db, $this->_extDataModels );
	}
	/**
	 * Putes the XML to SQL conversion into reverse mode, for joining instead of selecting
	 * Useful for search/filtering functions.
	 *
	 * @param  boolean $reverse  New state of reverse (straight=FALSE)
	 * @return boolean           Previous state
	 */
	public function setReverse( $reverse = null ) {
		$oldReverse				=	$this->_reverse;
		if ( $reverse !== null ) {
			$this->_reverse		=	$reverse;
		}
		return $oldReverse;
	}
	/**
	 * Adds a reference to external data models (objects or arrays for "ext:datatype:xxx:yyy" valuetype)
	 *
	 * @param  string  $dataName  Name of the ext: datatype
	 * @param  mixed   $data      Reference to external datatype (type: object, array, or string, int, float)
	 */
	public function setExternalDataTypeValues( $dataName, $data ) {
		$this->_extDataModels[$dataName]	=	$data;
	}
	/**
	 * Fixes the default 1 kB GROUP_CONCAT result to 64 kB (which is MySQL 5.0's maximum)
	 */
	protected function increaseGroupConcatMaxLen( ) {
		if ( $this->_group_concat_max_len_todo ) {
			$this->_db->query( 'SET SESSION group_concat_max_len = 65536;' );
			$this->_group_concat_max_len_todo	=	false;
		}
	}
}

Anon7 - 2022
AnonSec Team