<?php
/**
 * @package Joomla
 * @subpackage Fabrik
 * @copyright Copyright (C) 2005 Rob Clayburn. All rights reserved.
 * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL, see LICENSE.php
 */

// Check to ensure this file is included in Joomla!
defined('_JEXEC') or die();

require_once(COM_FABRIK_FRONTEND.DS.'helpers'.DS.'pagination.php');
require_once(COM_FABRIK_FRONTEND.DS.'helpers'.DS.'string.php');
require_once(COM_FABRIK_FRONTEND.DS.'helpers'.DS.'joomfish.php');


class FabrikModelTable extends JModel {

	/** @var object the tables connection object */
	var $_oConn = null;

	/** @var object table Table */
	var $_table = null;

	/** @var object table's database connection object (loaded from connection object) */
	var $_oConnDB = null;

	/** @var object table's form model */
	var $_oForm = null;

	/** @var array joins */
	var $_aJoins = null;

	/** @var array column calculations */
	var $_aRunCalculations = array();

	/** @var string table output format - set to rss to collect correct element data within function gettData()*/
	var $_outPutFormat = 'html';

	var $_isMambot = false;

	/** @var object to contain access rights **/
	var $_access = null;

	/** @var int the id of the last inserted record (or if updated the last record updated) **/
	var $_lastInsertId = null;

	/** @var array store data to create joined records from */
	var $_joinsToProcess = null;

	/** @var array database fields */
	var $_dbFields = null;

	/** @var bol force reload table calculations **/
	var $_reloadCalculations = false;

	/** @var array data contains request data **/
	var $_aData = null;

	/** @var string method to use when submitting form data // post or ajax*/
	var $_postMethod = null;

	/** @var int package id */
	var $_packageId = null;

	/** @var object plugin manager */
	var $_pluginManager = null;

	/** @var int id of table to load */
	var $_id = null;

	/** @var string join sql **/
	var $_joinsSQL = null;

	/** @var array order by column names **/
	var $orderByFields = null;

	/** @var bol is the object inside a package? */
	//var $_inPackage  = false;

	var $_joinsToThisKey = null;

	var $_real_filter_action = null;

	/** array merged request and session data used to potentially filter the table **/
	var $_request = null;

	var $_aRow = null;

	/** array rows to delete **/
	var $_rowsToDelete = null;

	/** @var array original table data BEFORE form saved - used to ensure uneditable data is stored */
	var $_origData = null;

	/** @var bol set to true to load records STARTING from a random id (used in the getPageNav func) **/
	var $_randomRecords = false;

	var $_data = null;
	var $nav = null;
	var $fields = null;
	var $prefilters = null;
	var $filters = null;
	var $aJoinsToThisKey = null;
	var $canSelectRows = null;
	var $asfields = null;
	var $_temp_db_key_addded = false;
	var $_group_by_added = false;
	var $_pluginQueryWhere = array();
	var $_pluginQueryGroupBy = array();

	/** @var bol is the table a view **/
	var $_isview = null;

	/** @var array index objects **/
	var $_indexes = null;

	/** @var array previously submitted advanced search data */
	var $advancedSearchRows = null;

	/** @var string table action url */
	var $tableAction = null;

	/** @var bool doing CSV import */
	var $_importingCSV = false;

	var $encrypt = array();

	/** @var int which record to start showing from */
	var $limitStart  = null;

	/** @var int # records to show */
	var $limitLength = null;

	var $tableLinks = null;

	/** 28/02/2011 */
	var $randomLimitStart = null;
	/**
	 * Constructor
	 *
	 * @since 1.5
	 */

	function __construct()
	{
		parent::__construct();
		$usersConfig = &JComponentHelper::getParams('com_fabrik');
		$id = JRequest::getInt('tableid', $usersConfig->get('tableid'));
		$this->setId($id);
		$this->_access = new stdClass();
	}

	/**
	 * process the table plug-ins
	 * @return array of table plug-in result messages
	 */

	function processPlugin()
	{
		$pluginManager =& $this->getPluginManager();
		$pluginManager->runPlugins('process', $this, 'table');
		return $pluginManager->_data;
	}

	/**
	 * get the html that is outputted by table plug-in buttons
	 *
	 * @return array buttons
	 */

	function getPluginButtons()
	{
		$pluginManager =& $this->getPluginManager();
		$pluginManager->getPlugInGroup('table');
		$pluginManager->runPlugins('button', $this, 'table');
		$buttons = $pluginManager->_data;
		//$pluginManager->runPlugins('getJavascriptClass', $this, 'table');
		$this->getPluginJsClasses();
		return $buttons;
	}

	function getPluginJsClasses()
	{
		$pluginManager =& $this->getPluginManager();
		$pluginManager->getPlugInGroup('table');
		$pluginManager->runPlugins('loadJavascriptClass', $this, 'table');
	}

	function getPluginJsObjects($container = null)
	{
		if (is_null($container)) {
			$container = "tableform_{$this->_id}";
		}
		$pluginManager =& $this->getPluginManager();
		$pluginManager->getPlugInGroup('table');
		$pluginManager->runPlugins('loadJavascriptInstance', $this, 'table', $container);
		return $pluginManager->_data;
	}

	/**
	 * main query to build table
	 *
	 */

	function render()
	{
		FabrikHelperHTML::debug($_POST, 'render:post');
		global $_PROFILER;
		if (is_null($this->_id) || $this->_id == '0') {
			return JError::raiseError(500, JText::_('TABLE ID NOT SET CANT RENDER'));
		}
		$this->_outPutFormat = JRequest::getVar('format', 'html');
		if ($this->_outPutFormat == 'fabrikfeed') {
			$this->_outPutFormat = 'feed';
		}
		$pluginManager =& $this->getPluginManager();

		$table 		=& $this->getTable();
		if ($table->db_table_name == '') {
			return JError::raiseError(500, JText::_('COM_FABRIK_INCORRECT_TABLE_ID'));
		}
		//cant set time limit in safe mode so suppress warning
		@set_time_limit(60);

		//$this->getRequestData();
		JDEBUG ? $_PROFILER->mark('About to get table filter') : null;
		$filters =& $this->getFilterArray();

		$this->setLimits();

		$this->getData();
		JDEBUG ? $_PROFILER->mark('got data') : null;
		//think we really have to do these as the calc isnt updated when the table is filtered
		$this->doCalculations();
		JDEBUG ? $_PROFILER->mark('done calcs') : null;
		$this->getCalculations();
		JDEBUG ? $_PROFILER->mark('got cacls') : null;
		$table->hit();
	}

	/**
	 * set the navigation limit and limitstart
	 */

	function setLimits()
	{
		$app =& JFactory::getApplication();
		$table=& $this->getTable();
		$params =& $this->getParams();
		$context = 'com_fabrik.table'. $this->_id.'.list.';
		$limitStart = $this->_randomRecords ? $this->getRandomLimitStart() : 0;
		// deal with the fact that you can have more than one table on a page so limitstart has to be
		// specfic per table
		$limitLength 			= $app->getUserStateFromRequest($context.'limitlength'.$this->_id, 'limit'.$this->_id, $table->rows_per_page);
		if (!$this->_randomRecords) {
			$limitStart				= $app->getUserStateFromRequest($context.'limitstart'.$this->_id, 'limitstart'.$this->_id, $limitStart, 'int');
		}
		if ($this->_outPutFormat == 'feed') {
			$limitLength = JRequest::getVar('limit',  $params->get('rsslimit',150));
			$maxLimit = $params->get('rsslimitmax', 2500);
			if ($limitLength > $maxLimit) {
				$limitLength = $maxLimit;
			}
		}
		$this->limitLength = $limitLength;
		$this->limitStart = $limitStart;
	}

	/**
	 * this merges session data for the fromForm with any request data
	 * allowing us to filter data results from both search forms and filters
	 *
	 * @return array
	 */

	function getRequestData()
	{
		return $this->getFilterModel()->getFilters();
	}

	/**
	 * get the table's filter model
	 * @return model filter model
	 */

	function &getFilterModel()
	{
		if (!isset($this->filterModel)) {
			$this->filterModel = & JModel::getInstance('Tablefilter', 'FabrikModel');
			$this->filterModel->setTableModel($this);
		}
		return $this->filterModel;
	}

	function setBigSelects()
	{
		$fabrikDb =& $this->getDb();
		$params =& $this->getParams();
		if ($params->get('enable_big_selects', 0)) {
			$fabrikDb->setQuery("SET OPTION SQL_BIG_SELECTS=1");
			$fabrikDb->query();
		}
	}

	/**
	 * get the table's data
	 *
	 * @return array of objects (rows)
	 */

	function getData()
	{
		global $_PROFILER;
		$pluginManager =& $this->getPluginManager();
		$fbConfig =& JComponentHelper::getParams('com_fabrik');
		$pluginManager->runPlugins('onPreLoadData', $this, 'table');
		if (isset($this->_data) && !is_null($this->_data)) {
			return $this->_data;
		}
		$traceModel = ini_get('mysql.trace_mode');
		ini_set ('mysql.trace_mode', 'off'); // needs to be off for FOUND_ROWS() to work
		$fabrikDb =& $this->getDb();
		JDEBUG ? $_PROFILER->mark('query build start') : null;
		$query 		= $this->_buildQuery();
		JDEBUG ? $_PROFILER->mark('query build end') : null;

		// $$$ hugh - once we have a few table joins, our select statements are
		// getting big enough to hit default select length max in MySQL.  Added per-table
		// setting to enable_big_selects, 3/16/2010.
		$this->setBigSelects();

		// $$$ rob - if merging joined data then we don't want to limit
		// the query as we have already done so in _buildQuery()
		if ($this->mergeJoinedData()) {
			$fabrikDb->setQuery($query);
		} else {
			$fabrikDb->setQuery($query, $this->limitStart, $this->limitLength);
		}

		FabrikHelperHTML::debug($fabrikDb->getQuery(), 'table GetData:' . $this->getTable()->label);
		JDEBUG ? $_PROFILER->mark('before query run') : null;
		$this->_data = $fabrikDb->loadObjectList();

		if ($fabrikDb->getErrorNum() != 0) {
			JError::raiseNotice(500,  'getData: ' . $fabrikDb->getErrorMsg());
			jexit;
		}
		/// $$$ rob better way of getting total records
		if ($this->mergeJoinedData()) {
			$this->totalRecords = $this->getTotalRecords();
		} else {
			$fabrikDb->setQuery("SELECT FOUND_ROWS()");
			$this->totalRecords = $fabrikDb->loadResult();
		}
		if ($this->_randomRecords) {
			shuffle($this->_data);
		}
		ini_set ('mysql.trace_mode', $traceModel);
		$nav =& $this->getPagination($this->totalRecords, $this->limitStart, $this->limitLength);

		JDEBUG ? $_PROFILER->mark('query run and data loaded') : null;
		if ($fbConfig->get('use_wip')) {
			$this->translateData($this->_data);
		}
		if ($fabrikDb->getErrorNum() != 0) {
			JError::raiseNotice(500,  'getData: ' . $fabrikDb->getErrorMsg());
		}
		//append the cursor & total to the data
		// $$$rob - do we really need to do this? need to test record srolling without it
		if (in_array($this->_outPutFormat, array('html','raw','pdf', 'phocapdf'))) {
			for ($i=0; $i<count($this->_data); $i++) {
				$this->_data[$i]->_cursor = $i + $this->limitStart;
				$this->_data[$i]->_total = $nav->total;
			}
		}
		JDEBUG ? $_PROFILER->mark('start format data') : null;
		$this->formatData($this->_data);
		JDEBUG ? $_PROFILER->mark('start format for joins') : null;
		$this->formatForJoins($this->_data);
		JDEBUG ? $_PROFILER->mark('data formatted') : null;
		$pluginManager->runPlugins('onLoadData', $this, 'table');
		return $this->_data;
	}

	function translateData(&$data)
	{
		$params =& $this->getParams();
		if (!JPluginHelper::isEnabled('system', 'jfdatabase')) {
			return;
		}
		if (defined('JOOMFISH_PATH') && $params->get('allow-data-translation')) {
			$db = JFactory::getDBO();
			$jf =& JoomFishManager::getInstance();
			$config =& JFactory::getConfig();
			$tableName = str_replace($config->getValue('dbprefix'), '', $this->getTable()->db_table_name);
			$contentElement =& $jf->getContentElement($tableName);
			if (!is_object($contentElement)) {
				return;
			}

			$title =  Fabrikstring::shortColName($params->get('joomfish-title'));
			$activelangs = $jf->getActiveLanguages();
			$registry =& JFactory::getConfig();
			$langid = $activelangs[$registry->getValue("config.jflang")]->id;
			$db->setQuery($contentElement->createContentSQL($langid));
			if ($title == '') {
				$contentTable = $contentElement->getTable();
				foreach ($contentTable->Fields as $tableField) {
					if ($tableField->Type == 'titletext') {
						$title = $tableField->Name;
					}
				}
			}
			$longKey = FabrikString::safeColNameToArrayKey($this->getTable()->db_primary_key);
			$res =& $db->loadObjectList($this->_shortKey(null, true));
			foreach ($data as &$row) {
				//$$$ rob if the id isnt published fall back to __pk_val
				$translateRow =  array_key_exists($longKey, $row) ? $res[$row->$longKey] : $res[$row->__pk_val];
				foreach ($row as $key => $val) {
					$shortkey = array_pop(explode("___", $key));
					if ($shortkey === $title) {
						$row->$key = $translateRow->titleTranslation;
						$key = $key ."_raw";
						$row->$key = $translateRow->titleTranslation;
					} else {
						if (array_key_exists($shortkey, $translateRow)) {
							$row->$key = $translateRow->$shortkey;
							$key = $key ."_raw";
							if (array_key_exists($key, $row)) {
								$row->$key = $translateRow->$shortkey;
							}
						}
					}
				}
			}
		}
	}

	/**
	 * run the table data through element filters
	 *
	 * @param array $data
	 */

	function formatData(&$data)
	{
		global $_PROFILER;
		jimport('joomla.filesystem.file');
		$form =& $this->getForm();
		$tableParams =& $this->getParams();
		$table =& $this->getTable();
		$pluginManager =& $this->getPluginManager();
		$method 				= 'renderTableData_' . $this->_outPutFormat;
		$this->_aLinkElements = array();

		// $$$ hugh - temp foreach fix
		$groups = $form->getGroupsHiarachy();
		$ec = count($data);
		foreach ($groups as $groupModel) {
			//$$$ rob pointless getting elemetsnnot shown in the table view?
			// $$$ hugh - oops, they might be using elements in group-by template not shown in table
			// http://fabrikar.com/forums/showthread.php?p=102600#post102600
			// $$$ rob in that case lets test that rather than loading blindly
			// $$$ rob 15/02/2011 or out put may be csv in which we want to format any fields not shown in the form
			if (($tableParams->get('group_by_template') !== '' && $this->getGroupBy() != '') || $this->_outPutFormat == 'csv' || $this->_outPutFormat == 'feed') {
				$elementModels =& $groupModel->getPublishedElements();
			} else {
				$elementModels =& $groupModel->getPublishedTableElements();
			}
			foreach ($elementModels as $elementModel) {
				$e =& $elementModel->getElement();
				$elementModel->setContext($groupModel, $form, $this);
				$params =& $elementModel->getParams();
				$col 	= $elementModel->getFullName(false, true, false);

				//check if there is  a custom out put handler for the tables format
				// currently supports "renderTableData_csv", "renderTableData_rss", "renderTableData_html", "renderTableData_json"
				if (!empty($data) && array_key_exists($col, $data[0])) {
					if (method_exists($elementModel, $method)) {
						for ($i=0; $i<count($data); $i++) {
							$thisRow = $data[$i];
							$coldata = $thisRow->$col;
							$data[$i]->$col = $elementModel->$method($coldata, $col, $thisRow);
						}

					} else {
						JDEBUG ? $_PROFILER->mark('elements renderTableData: ' ."($ec)". " talbeid = $table->id " .  $col) : null;
						for ($i=0; $i < $ec; $i++) {
							$thisRow =& $data[$i];
							$coldata = $thisRow->$col;
							$data[$i]->$col = $elementModel->renderTableData($coldata, $thisRow);
							$rawCol = $col . "_raw";
							// Not sure if this works, as far as I can tell _raw will always exist, even if
							// the element model hasn't explicitly done anything with it (except mayeb unsetting it?)
							// For instance, the calc element needs to set _raw.  For now, I changed $thisRow above to
							// be a =& reference to $data[$i], and in renderTableData() the calc element modifies
							// the _raw entry in $thisRow.  I guess it could simply unset the _raw in $thisRow and
							// then implement a renderRawTableData.  Anyway, just sayin'.
							if (!array_key_exists($rawCol, $thisRow)) {
								$data[$i]->$rawCol = $elementModel->renderRawTableData($coldata, $thisRow);
							}
						}
					}
				}
			}
		}
		JDEBUG ? $_PROFILER->mark('elements rendered for table data') : null;
		$this->_aGroupInfo = array();
		$groupTitle = array();

		$this->grouptemplates = array();
		//check if the data has a group by applied to it
		$groupBy = $this->getGroupBy();
		if ($groupBy != '' && $this->_outPutFormat != 'csv') {
			$w = new FabrikWorker();
			$groupTemplate = $tableParams->get('group_by_template');
			$groupedData = array();
			$thisGroupedData = array();
			$groupBy = FabrikString::safeColNameToArrayKey($groupBy);

			// $$$ rob commenting this out as if you group on a date then the group by value doesnt correspond
			// to the keys found in the calculation array

			//see if we can use a raw value instead
			/*if (!empty($data) && array_key_exists($groupBy . "_raw", $data[0])) {
			$groupBy = $groupBy . "_raw";
			}*/
			$groupTitle = null;
			$aGroupTitles = array();
			$groupId = 0;
			for ($i=0; $i <count($data); $i++) {
				if (!in_array($data[$i]->$groupBy , $aGroupTitles)) {
					$aGroupTitles[] = $data[$i]->$groupBy;
					$grouptemplate = $w->parseMessageForPlaceHolder($groupTemplate, JArrayHelper::fromObject($data[$i]));
					$this->grouptemplates[$data[$i]->$groupBy] = nl2br($grouptemplate);
					$groupedData[$data[$i]->$groupBy] = array();
				}
				$data[$i]->_groupId = $data[$i]->$groupBy;
				$gKey = $data[$i]->$groupBy;
				//	if the group_by was added in in getAsFields remove it from the returned data set (to avoid mess in package view)
				if ($this->_group_by_added) {
					unset($data[$i]->$groupBy);
				}
				if ($this->_temp_db_key_addded) {
					$k = $table->db_primary_key;
				}
				$groupedData[$gKey][] = $data[$i];

			}
			$data = $groupedData;
		} else {
			for ($i=0; $i<count($data); $i++) {
				if ($this->_temp_db_key_addded) {
					$k = $table->db_primary_key;
				}
			}
			//make sure that the none grouped data is in the same format
			$data = array($data);
		}
		JDEBUG ? $_PROFILER->mark('table groupd by applied') : null;
		if ($this->_outPutFormat != 'pdf' && $this->_outPutFormat != 'csv' && $this->_outPutFormat != 'feed') {
			$this->addSelectBoxAndLinks($data);
			FabrikHelperHTML::debug($data, 'table:data');
		}
		JDEBUG ? $_PROFILER->mark('end format data') : null;
	}

	/**
	 * add the select box and various links into the data array
	 * @param array table row objects
	 */

	function addSelectBoxAndLinks(&$data)
	{
		$table 		=& $this->getTable();
		$app 			=& JFactory::getApplication();
		$db 			=& JFactory::getDBO();
		$params 	=& $this->getParams();
		$nextview = ($this->canEdit()) ? "form" : "details";
		$tmpKey 	= '__pk_val';

		$aExisitngLinkedTables 	= $params->get('linkedtable', '', '_default', 'array');
		$aExisitngLinkedForms 	= $params->get('linkedform', '', '_default', 'array');
		$linkedform_linktype 		= $params->get('linkedform_linktype', '', '_default', 'array');
		$linkedtable_linktype 	= $params->get('linkedtable_linktype', '', '_default', 'array');
		$aExistingTableHeaders 	= $params->get('linkedtableheader', '', '_default', 'array');
		$aExistingFormHeaders 	= $params->get('linkedformheader', '', '_default', 'array');
		$linkedTableText 				= $params->get('linkedtabletext', '', '_default', 'array');
		$linkedFormText 				= $params->get('linkedformtext', '', '_default', 'array');

		//get a list of fabrik tables and ids for view table and form links
		$linksToForms =  $this->getLinksToThisKey();
		$action = $app->isAdmin() ? "task" : "view";
		$sql = "SELECT id, label, db_table_name FROM #__fabrik_tables";
		$db->setQuery($sql);
		$aTableNames = $db->loadObjectList('label');
		$cx = count($data);
		$viewLinkAdded = false;

		//get pk values
		$pks = array();
		foreach ($data as $groupKey=>$group) {
			$cg = count($group);
			for ($i=0; $i < $cg; $i++) {
				$pks[] = @$data[$groupKey][$i]->$tmpKey;
			}
		}

		$joins =& $this->getJoins();
		//for ($x=0; $x<$cx; $x++) { //if grouped data then the key is not numeric
		foreach ($data as $groupKey=>$group) {
			//$group =& $data[$key]; //Messed up in php 5.1 group positioning in data became ambiguous
			$cg = count($group);
			for ($i=0; $i < $cg; $i++) {
				$row =& $data[$groupKey][$i];
				$viewLinkAdded = false;
				//done each row as its result can change
				$canEdit = $this->canEdit($row);
				$canView = $this->canView($row);
				$canDelete = $this->canDelete($row);

				$nextview = $canEdit ? "form" : "details";
				$pKeyVal = (array_key_exists($tmpKey, $row)) ? $row->$tmpKey : '';
				$pkcheck = "<div style=\"display:none\">";
				foreach ($joins as $join) {
					if ($join->table_id !== '0') {
						$fkey = $join->table_join_alias.'___'.$join->table_key;
						if (isset($row->$fkey)) {
							$fKeyVal= $row->$fkey;
							$pkcheck .= "<input type=\"checkbox\" class=\"fabrik_joinedkey\" value=\"$fKeyVal\" name=\"{$join->table_join_alias}[{$row->_cursor}]\">\n";
						}
					}
				}
				$pkcheck .= "</div>";
				$row->fabrik_delete = $this->canSelectRow($row) ? '<input type="checkbox" id="id_'.$row->_cursor .'" name="ids['.$row->_cursor.']" value="' . $pKeyVal . '" />'.$pkcheck : '';
				//add in some default links if no element choosen to be a link
				$link = $this->viewDetailsLink($data[$groupKey][$i]);//dont use $row as it generates a pas by ref error
				$edit_link = $this->editLink($data[$groupKey][$i]);
				$row->fabrik_view_url = $link;
				$row->fabrik_edit_url = $link;

				$row->fabrik_view = '';
				$row->fabrik_edit = '';
				if ($canView || $canEdit) {
					if ($canEdit == 1) {
						if ($params->get('editlink')) {
							$row->fabrik_edit = "<a class=\"fabrik__rowlink\" href=\"$edit_link\">" . $params->get('editlabel', JText::_('EDIT')) . "</a>";
						}
						$row->fabrik_edit_url = $edit_link;
						if ($params->get('editlink')) {
							$row->fabrik_view = "<a class=\"fabrik___rowlink\" href=\"$link\">" . $params->get('editlabel', JText::_('EDIT')). "</a>";
						}
					} else {
						if ($this->canViewDetails()) {
							if (empty($this->_aLinkElements)) {
								$viewLinkAdded = true;
								$row->fabrik_view = "<a class=\"fabrik___rowlink\" href=\"$link\">" . $params->get('detaillabel', JText::_('VIEW')) . "</a>";
							}
						} else {
							$row->fabrik_edit = '';
						}
					}
				}

				if ($this->canViewDetails() && $params->get('detaillink') == '1' && !$viewLinkAdded) {
					$link = $this->viewDetailsLink($row, 'details');
					$row->fabrik_view_url = $link;
					$row->fabrik_view = "<a class=\"fabrik___rowlink\" href=\"$link\">" . $params->get('detaillabel', JText::_('VIEW')) . "</a>";

				}

				if ($params->get('merge_edit_details')) {
					if(isset($row->fabrik_view) && isset($row->fabrik_edit)) {
						$row->fabrik_edit .= ' ' . $row->fabrik_view;
						unset($row->fabrik_view);
					}
				}
				// create columns containing links which point to tables associated with this table
				$joinsToThisKey = $this->getJoinsToThisKey();
				$f = 0;
				for ($ii=0; $ii <count($joinsToThisKey); $ii++) {
					$element = $joinsToThisKey[$ii];
					$linkedTable 	= JArrayHelper::getValue($aExisitngLinkedTables, $f, false);
					$popupLink = JArrayHelper::getValue($linkedtable_linktype, $f, false);
					if ($linkedTable != '0') {
						// $$$ hugh - see http://fabrikar.com/forums/showthread.php?p=111375#post111375
						/*
						if ($element->tablelabel == $table->label) { //if the link points to the same table
						$key 		= $element->name;
						$val 		= $row->$thiskey;
						} else {
						*/
						$recordKey = $element->element_id."___".$linkedTable;
						$key 		= $recordKey . "_table_heading";
						$val 		= $pKeyVal;
						/* } */

						$recordCounts =& $this->getRecordCounts($element, $pks);
						$count = 0;
						$linkKey = '';
						if (is_array($recordCounts)) {
							if (array_key_exists($val, $recordCounts)) {
								$count = $recordCounts[$val]->total;
								$linkKey = $recordCounts[$val]->linkKey;
							} else {
								if (array_key_exists((int)$val, $recordCounts) && (int)$val !== 0) {
									$count = $recordCounts[(int)$val]->total;
									$linkKey = $recordCounts[$val]->linkKey;
								}
							}
						}
						$element->table_id = array_key_exists($element->tablelabel, $aTableNames) ?  $aTableNames[$element->tablelabel]->id : '';
						$linkLabel = $this->parseMessageForRowHolder(JArrayHelper::getValue($linkedTableText, $f), JArrayHelper::fromObject($row));
						$linkKey .= '_raw';
						$group[$i]->$key = $this->viewDataLink($popupLink, $element->table_id, $row, $linkKey, $val, $count, $linkLabel);
					}
					$f ++;
				}

				$f = 0;
				//create columns containing links which point to forms assosciated with this table
				foreach ($linksToForms as $element) {
					$linkedForm = JArrayHelper::getValue($aExisitngLinkedForms, $f, false);
					$popupLink = JArrayHelper::getValue($linkedform_linktype, $f, false);

					// $$$ hugh @TODO - rob, can you check this, I added this line,
					// but the logic applied for $val in the linked table code above seems to be needed?
					// http://fabrikar.com/forums/showthread.php?t=9535
					$val = $pKeyVal;
					if ($linkedForm !== '0') {
						if (is_object($element)) {
							//$$$rob moved these two lines here as there were giving warnings since Hugh commented out the if ($element != '') {
							$linkKey	= @$element->db_table_name . "___" . @$element->name;
							$key = $linkKey . "_form_heading";
							$linkLabel = $this->parseMessageForRowHolder(JArrayHelper::getValue($linkedFormText, $f), JArrayHelper::fromObject($row));
							$group[$i]->$key = $this->viewFormLink($popupLink, $element->table_id, $element->form_id, $row, $linkKey, $val, false, $linkLabel);
						}
					}
					$f ++;
				}
			}
		}
	}

	/**
	 *
	 * get a list of possible menus
	 * USED TO BUILD RELTED TABLE LNKS WITH CORRECT iTEMD AND TEMPLATE
	 * @return array linked table menu items
	 * @since 2.0.4
	 */
	protected function getTableLinks()
	{
		if (isset($this->tableLinks)){
			return $this->tableLinks;
		}
		$db =& JFactory::getDBO();
		$joinsToThisKey = $this->getJoinsToThisKey();
		if (empty($joinsToThisKey)) {
			$this->tableLinks = array();
		} else {
			$query = "SELECT * FROM #__menu WHERE type = 'component' AND ";
			foreach ($joinsToThisKey as $element) {
				$linkWhere[] = "link LIKE 'index.php?option=com_fabrik&view=table&tableid=".(int)$element->table_id."%'";
			}
			$query .= '('.implode(' OR ', $linkWhere).')';
			$db->setQuery($query);
			$this->tableLinks = $db->loadObjectList();
		}
		return $this->tableLinks;
	}

	/**
	 * for releated table links get the record count for each of the table's rows
	 * @param $element
	 * @param array primary keys to count on
	 * @return array counts key'd on element primary key
	 */

	public function getRecordCounts(&$element, $pks =array())
	{
		if (!isset($this->recordCounts)) {
			$this->recordCounts = array();
		}
		$k = $element->element_id;
		if (array_key_exists($k, $this->recordCounts)) {
			return $this->recordCounts[$k];
		}
		$tableModel =& JModel::getInstance('table', 'FabrikModel');
		$tableModel->setId($element->table_id);
		$db =& $tableModel->getDb();
		$elementModel =& $tableModel->getForm()->getElement($element->element_id, true);
		$key = $elementModel->getFullName(false, false, false);
		$linkKey = FabrikString::safeColName($key);
		$fparams =& $tableModel->getParams();
		//ensure that the facte table's require filters option is set to false
		$fparams->set('require-filter', false);

		//ignore facted tables session filters
		$origIncSesssionFilters = JRequest::getVar('fabrik_incsessionfilters', true);
		JRequest::setVar('fabrik_incsessionfilters', false);
		$where = $tableModel->_buildQueryWhere(JRequest::getVar('incfilters', 0));
		if (!empty($pks)) {
			//only load the current record sets record counts
			$where .= trim($where) == '' ? ' WHERE ' : ' AND ';
			$where .= "$linkKey IN (" . implode(',', $pks) . ")";
		}
		$tableModel->set('_joinsSQL', null); //force reload of join sql
		$tableModel->set('includeCddInJoin', false); //trigger load of joins without cdd elements - seems to mess up count otherwise
		//see http://fabrikar.com/forums/showthread.php?t=12860
		//$totalSql  = "SELECT $linkKey AS id, COUNT($linkKey) AS total FROM " . $element->db_table_name . " " . $tableModel->_buildQueryJoin();

		$k2 = $db->Quote(FabrikString::safeColNameToArrayKey($key));
		$totalSql  = "SELECT $k2 AS linkKey, $linkKey AS id, COUNT($linkKey) AS total FROM " . $tableModel->getTable()->db_table_name . " " . $tableModel->_buildQueryJoin();

		$totalSql 	.= " $where GROUP BY $linkKey";
		$tableModel->set('includeCddInJoin', true);
		$db->setQuery($totalSql);
		$this->recordCounts[$k] =& $db->loadObjectList('id');
		FabrikHelperHTML::debug($db->getQuery(), 'getRecordCounts query: '.$linkKey);
		FabrikHelperHTML::debug($this->recordCounts[$k], 'getRecordCounts data: '.$linkKey);
		JRequest::setVar('fabrik_incsessionfilters', $origIncSesssionFilters);
		return $this->recordCounts[$k];
	}

	/**
	 * creates the html <a> link allowing you to edit other forms from the table
	 * E.g. Faceted browsing: those specified in the table's "Form's whose primary keys link to this table"
	 *
	 * @param bol $popUp
	 * @param int $tableid
	 * @param int $formid
	 * @param object $row
	 * @param string $key
	 * @param string $val
	 * @param string link text leave null to use default
	 * @return string <a> html part
	 */

	function viewFormLink($popUp = false, $tableid = null, $formid = null, $row = null, $key = '', $val = '', $usekey = false, $label = null)
	{
		global $Itemid;
		$app =& JFactory::getApplication();
		if (is_null($tableid)) {
			$table = $this->getTable();
			$tableid = $table->id;
		}

		if (is_null($formid)) {
			$form = $this->getForm();
			$formid = $form->id;
		}
		$facetTable = $this->_facetedTable($tableid);
		if (!$facetTable->canAdd()) {
			return '<div style="text-align:center"><a title="'.JText::_('Insufficient access rights. Please login').'"><img src="media/com_fabrik/images/login.png" alt="'.JText::_('iInsufficient access rights. Please login').'" /></a></div>';
		}
		if ($app->isAdmin()) {
			$bits[] = "c=form";
			$bits[] = "task=form";
			$bits[] = "cid=$tableid";
		} else {
			$bits[] = "view=form";
			$bits[] = "tableid=$tableid";
			$bits[] = "Itemid=$Itemid";
		}
		$bits[] = "fabrik=$formid";
		$bits[] = "referring_table=". $this->getTable()->id;
		// $$$ hugh - change in fabrikdatabasejoin getValue() means we have to append _raw to key name
		if ($key != '') {
			$bits[] = "{$key}_raw=$val";
		}

		if (!is_null($row)) {
			$bits[] = "fabrik_cursor={$row->_cursor}";
			$bits[] = "fabrik_total={$row->_total}";
		}

		if ($popUp) {
			$bits[] = "tmpl=component";
		}

		if ($usekey and $key != '' and !is_null($row)) {
			$bits[] = "usekey=" . FabrikString::shortColName($key);
			$bits[] = "rowid=" . $row->slug;
		} else {
			$bits[] = "rowid=0";
		}

		$url = "index.php?option=com_fabrik&" . implode("&", $bits);
		$url = JRoute::_($url);

		if (is_null($label) || $label == '') {
			$label = JText::_('LINKEDFORMADD');
		}
		if ($popUp) {
			FabrikHelperHTML::mocha( 'a.popupwin');
			$opts = new stdClass();
			$opts->maximizable = 1;
			$opts->title = JText::_('ADD');
			$opts->evalScripts = 1;
			$opts = str_replace('"', "'", FastJSON::encode($opts));
			$link = "<a rel=\"$opts\" href=\"$url\" class=\"popupwin\" title=\"$label\">" . $label . "</a>";
		} else {
			$link =  "<a href=\"$url\" title=\"$label\">" . $label . "</a>";
		}
		$url = "<span class=\"addbutton\">$link</span></a>";
		return $url;
	}

	/**
	 * get one of the current tables facet tables
	 *(used in tables that link to this tables links)
	 * @param int table id
	 * @return object table
	 */

	function _facetedTable($id)
	{
		if (!isset($this->facettables)) {
			$this->facettables = array();
		}
		if (!array_key_exists($id, $this->facettables)) {
			$this->facettables[$id] =& JModel::getInstance('table', 'FabrikModel');
			$this->facettables[$id]->setId($id);
		}
		return $this->facettables[$id];
	}
	/**
	 * build the link (<a href..>) for viewing table data
	 *
	 * @param bol $popUp
	 * @param int $tableid
	 * @param object $row
	 * @param string $key
	 * @param string $val
	 * @return strng
	 */

	function viewDataLink($popUp = false, $tableid = null, $row = null, $key = '', $val = '', $count = 0, $label = '')
	{
		global $Itemid;
		$app =& JFactory::getApplication();
		/*if ($this->ajaxEditViewLink()) {
			return '#';
			}*/

		$action = $app->isAdmin() ? "task" : "view";
		$url = "index.php?option=com_fabrik&";

		if (is_null($tableid)) {
			$table = $this->getTable();
			$tableid = $table->id;
		}
		$facetTable = $this->_facetedTable($tableid);
		if (!$facetTable->canView()) {
			return '<div style="text-align:center"><a title="'.JText::_('Insufficient access rights. Please login').'"><img src="media/com_fabrik/images/login.png" alt="'.JText::_('iInsufficient access rights. Please login').'" /></a></div>';
		}
		$tlabel = ($label === '') ? JText::_('NO_RECORDS') : '(0) '.$label;

		if ($count === 0) {
			return '<div style="text-align:center" class="related_data_norecords">'.$tlabel.'</div>';
		}
		if ($label === '') {
			$label =  JText::_('VIEW');
		}
		if ($app->isAdmin()) {
			$bits[] = "c=table";
			$bits[] = "task=viewTable";
			$bits[] = "cid=$tableid";
		} else {
			$bits[] = "view=table";
			$bits[] = "tableid=$tableid";
			$tableLinks = $this->getTableLinks();
			// $$$ rob 01/03/2011 find at matching itemid in another menu item for the related data link
			foreach ($tableLinks as $tlink) {
				if (strstr($tlink->link, 'index.php?option=com_fabrik&view=table&tableid='.$tableid)) {
					$bits[] = "Itemid=".$tlink->id;
					$Itemid = $tlink->id;
					break;
				}
			}
			$bits[] = "Itemid=$Itemid";
		}

		if ($key != '') {
			$bits[] = "{$key}=$val";
		}
		if (!is_null($row)) {
			$bits[] = "fabrik_cursor={$row->_cursor}";
			$bits[] = "fabrik_total={$row->_total}";
		}
		$bits[] = 'limitstart=0';

		if ($popUp) {
			$bits[] = "tmpl=component";
		}
		$bits[] = "&resetfilters=1";
		//$bits[] = "clearfilters=1"; //nope stops url filter form workin on related data :(
		$url .= implode("&", $bits);
		$url = JRoute::_($url);

		if ($popUp) {
			FabrikHelperHTML::mocha('a.popupwin');
			$opts = new stdClass();
			$opts->maximizable = 1;
			$opts->title = JText::_('VIEW');
			$opts->evalScripts = 1;
			$opts = str_replace('"', "'", FastJSON::encode($opts));
			$url = '<a rel="'.$opts.'" href="'. $url.'" class="popupwin">('. $count . ') '. $label .'</a>';
		} else {
			$url =  "<a class=\"related_data\" href=\"$url\">($count) " . $label . "</a>";
		}
		return $url;
	}

	/**
	 * add a custom link to the element data
	 *
	 * @param string element $data
	 * @param object element
	 * @param object of all row data
	 * @return string element data with link added if specified
	 */

	public function _addLink($data, &$elementModel, $row, $repeatCounter = 0)
	{
		$element 	=& $elementModel->getElement();
		if ($this->_outPutFormat == 'csv' || $element->link_to_detail == 0) {
			return $data;
		}
		$params 	=& $elementModel->getParams();
		$customLink = $params->get('custom_link');

		// $$$ rob if its a custom link then we aren't linking to the details view so we should
		// ignore the view details access settings
		if (!$this->canViewDetails($row) && trim($customLink) == '') {
			return $data;
		}

		$table =& $this->getTable();

		$primaryKeyVal = $this->getKeyIndetifier($row);

		/*if ($customLink == '') {
			// $$$ rob only test canEdit and canView on stardard edit links - if custom we should always use them,
			if ($this->canEdit($row) || $this->canViewDetails($row)) {
			$this->_aLinkElements[] = $element->name;
			$link = $this->viewDetailsLink($row);
			} else {
			return $data;
			}
			} else {
			$array = JArrayHelper::fromObject($row);
			foreach ($array as $k => &$v) {
			if (strstr($v, GROUPSPLITTER)) {
			$v = explode(GROUPSPLITTER, $v);
			$v = $v[$repeatCounter];
			}
			}
			$array['rowid'] = $this->getSlug($row);
			$array['tableid'] = $table->id;
			$link = JRoute::_($this->parseMessageForRowHolder($customLink, $array));
			}*/
		$link = $this->linkHref($elementModel, $row);
		if ($link == '') {
			return $data;
		}
		//try to remove any previously entered links
		$data = preg_replace('/<a(.*?)>|<\/a>/', '', $data);
		$data = "<a class=\"fabrik___rowlink\" href=\"$link\">$data</a>";
		return $data;
	}

	/**
	 * since 2.0.4 get the href for the edit link
	 * @param object $elementModel
	 * @param array $row data
	 * @return string link href
	 */
	public function linkHref($elementModel, $row)
	{
		$element 	=& $elementModel->getElement();
		$table =& $this->getTable();
		$params =& $elementModel->getParams();
		$customLink = $params->get('custom_link');
		if ($customLink == '') {
			// $$$ rob only test canEdit and canView on stardard edit links - if custom we should always use them,
			if ($this->canEdit($row) || $this->canViewDetails($row)) {
				$this->_aLinkElements[] = $element->name;
				$link = $this->viewDetailsLink($row);
			} else {
				$link = '';
			}
		} else {
			$array = JArrayHelper::fromObject($row);
			foreach ($array as $k => &$v) {
				if (strstr($v, GROUPSPLITTER)) {
					$v = explode(GROUPSPLITTER, $v);
					$v = $v[$repeatCounter];
				}
			}
			$array['rowid'] = $this->getSlug($row);
			$array['tableid'] = $table->id;
			$link = JRoute::_($this->parseMessageForRowHolder($customLink, $array));
		}
		return $link;
	}

	/**
	 * get query to make records
	 * @return string sql
	 */

	function _buildQuery()
	{
		global $_PROFILER;
		if ($this->mergeJoinedData()) {
			// $$$ rob - get a list of the main table's ids limited on the navigation
			// this will then be used to filter the main query,
			// by modifying the where part of the query
			$db = $this->getDb();
			$table = $this->getTable();
			// $$$ rob build order first so that we know of any elemenets we need to include in the select statement
			$order = $this->_buildQueryOrder();
			$this->selectedOrderFields = (array)$this->selectedOrderFields;
			array_unshift($this->selectedOrderFields , "DISTINCT ".$table->db_primary_key . " AS __pk_val");
			$query = "SELECT  ". implode(', ', $this->selectedOrderFields) . " FROM ".$db->nameQuote($table->db_table_name);
			$query .= $this->_buildQueryJoin();
			$query .= $this->_buildQueryWhere(JRequest::getVar('incfilters', 1));
			$query .= $this->_buildQueryGroupBy();
			$query .= $order;

			//check that the order by fields are in the select statement

			$db->setQuery($query, $this->limitStart, $this->limitLength);
			$ids = JArrayHelper::getColumn($db->loadObjectList(), '__pk_val');

		}
		$conf =& JFactory::getConfig();
		if ($conf->getValue('config.caching')) {
			$query = $this->getCacheQuery();
			JDEBUG ? $_PROFILER->mark('queryselect: cache got') : null;
		} else {
			$query = $this->_buildQuerySelect();
			JDEBUG ? $_PROFILER->mark('queryselect: got') : null;
			$query .= $this->_buildQueryJoin();
			JDEBUG ? $_PROFILER->mark('queryjoin: got') : null;
		}

		if ($this->mergeJoinedData()) {
			// $$$ rob We've already used _buildQueryWhere to get our list of main pk ids.
			// so lets use that list of ids to create the where statement. This will return 5/10/20 etc
			// records from our main table, as per our page nav, even if a main record has 3 rows of joined
			// data. If no ids found then do where 1 = -1 to return no records

			if (!empty($ids)) {
				$query .= " WHERE $table->db_primary_key IN (".implode($ids, ',').')';
			} else {
				$query .= " WHERE 1 = -1";
			}
		} else {
			// $$$ rob we aren't merging joined records so lets just add the standard where query

			//incfilters set when exporting as CSV
			$query .= $this->_buildQueryWhere(JRequest::getVar('incfilters', 1));
		}
		$query .= $this->_buildQueryGroupBy();
		$query .= $this->_buildQueryOrder();

		$query = $this->pluginQuery($query);
		return $query;
	}

	/**
	 * pass an sql query through the table plug-ins
	 * @param string $query
	 * @return string altered query.
	 */

	public function pluginQuery($query)
	{
		//pass the query as an object property so it can be updated via reference
		$args  = new stdClass();
		$args->query = $query;
		$this->getPluginManager()->runPlugins('onQueryBuilt', $this, 'table', $args);
		$query = $args->query;
		return $query;
	}

	function getCacheQuery()
	{
		$conf =& JFactory::getConfig();
		$cache = &JFactory::getCache('com_fabrik');
		$callback = array('FabrikCacheHelper', 'tableSelect');
		$contents =  $cache->get($callback, array($this), 'table'.$this->_id);
		return $contents;
	}

	/**
	 * get the select part of the query
	 *
	 * @return string
	 */

	function _buildQuerySelect()
	{
		global $_PROFILER;
		$db =& $this->getDb();
		$form =& $this->getForm();
		$table =& $this->getTable();
		$form->getGroupsHiarachy(true);
		JDEBUG ? $_PROFILER->mark('queryselect: fields load start') : null;
		$fields =& $this->getAsFields();
		$pk = FabrikString::safeColName($table->db_primary_key);
		//SEFSLUG TEST
		$params =& $this->getParams();
		if (in_array($this->_outPutFormat, array('raw', 'html', 'feed', 'pdf', 'phocapdf'))) {
			$slug = $params->get('sef-slug');
			if ($slug != '') {
				$slug = FabrikString::safeColName($slug);
				$fields[] = "CONCAT_WS(':', $pk, $slug) AS slug";
			} else {
				if ($pk !== '``') {
					$fields[] = "$pk AS slug";
				}
			}
		}
		//END

		JDEBUG ? $_PROFILER->mark('queryselect: fields loaded') : null;
		$sfields = (empty($fields)) ? '' : implode(", \n ", $fields ) . "\n ";
		//$$$rob added raw as an option to fix issue in saving calendener data
		if (trim($table->db_primary_key) != '' && (in_array($this->_outPutFormat, array('raw','html','feed','pdf','phocapdf','csv')))) {
			$sfields .= ", ";
			$strPKey = $pk . " AS " . $db->nameQuote('__pk_val') . "\n";
			$query = 'SELECT SQL_CALC_FOUND_ROWS DISTINCT ' . $sfields . $strPKey;
		} else {
			$query = 'SELECT SQL_CALC_FOUND_ROWS DISTINCT ' . trim($sfields, ", \n")  . "\n";
		}
		$query .= " FROM ".$db->nameQuote($table->db_table_name)." \n";
		return $query;
	}

	/**
	 * get the part of the sql statement that orders the table data
	 * @return string ordering part of sql statement
	 */

	function _buildQueryOrder()
	{
		$params = $this->getParams();
		$table =& $this->getTable();
		$db =& JFactory::getDBO();
		$this->selectedOrderFields = array();
		if ($this->_outPutFormat == 'feed')
		{
			$dateColId = (int)$params->get('feed_date', 0);
			$db->setQuery('SELECT name FROM #__fabrik_elements WHERE id = '.$dateColId);
			$dateCol = $db->nameQuote($table->db_table_name).'.'.$db->nameQuote($db->loadResult());
			if ($dateColId !== 0) {
				$this->order_dir = 'DESC';
				$this->order_by 	= $dateCol;
				return "\n ORDER BY $dateCol DESC";
			}
		}
		$session =& JFactory::getSession();

		$aFields = array();
		//$$$rob - when table reordered the controller runs order() and
		// stores the order settings in the session by calling setOrderByAndDir()
		// it then redirects to the table view and here all we need to do it get
		// those order settings from the session

		$elements =& $this->getElements();
		//build the order by statement from the session
		$strOrder = '';
		$clearOrdering = (bool)JRequest::getInt('clearordering', false) && JRequest::getCmd('task') !== 'order';
		//$$$tom Added single-ordering option
		if ($params->get('enable_single_sorting', 'default') == 'default') { // Use global
			$fbConfig =& JComponentHelper::getParams('com_fabrik');
			$singleOrdering = $fbConfig->get('enable_single_sorting', false);
		}
		else {
			$singleOrdering = $params->get('enable_single_sorting', false);
		}
		foreach ($elements as $element) {
			$context			= 'com_fabrik.table'. $this->_id.'.order.'.$element->getElement()->id;
			if ($clearOrdering) {
				$session->set($context, '');
			} else {
				//$$$tom Added single-ordering option
				if (!$singleOrdering || ($singleOrdering && $element->getElement()->id == JRequest::getInt('orderby', ''))) {
					$dir = $session->get($context);
					if ($dir != '' && $dir != '-' && trim($dir) != 'Array') {
						$strOrder == '' ? $strOrder = "\n ORDER BY " : $strOrder .= ',';
						$strOrder .= $element->getOrderByName()." $dir";
						$element->getAsField_html($this->selectedOrderFields, $aAsFields);
					}
				}
				else {
					$session->set($context, '');
				}
			}
		}
		//if nothing found in session use default ordering
		if ($strOrder == '') {
			$orderbys = explode(GROUPSPLITTER2, $table->order_by);
			$orderdirs = explode(GROUPSPLITTER2, $table->order_dir);
			if (!empty($orderbys)) {
				$bits = array();
				for ($o = 0; $o < count($orderbys); $o++) {
					$dir = JArrayHelper::getValue($orderdirs, $o, 'desc');
					if ($orderbys[$o] !== '') {
						$orderby = FabrikString::safeColName($orderbys[$o]);
						$els = $this->getElements('filtername');
						if (array_key_exists($orderby, $els)) {
							// $$$ hugh - getOrderByName can return a CONCAT, ie join element ...
							$field = $els[$orderby]->getOrderByName();
							if (!stristr($field, 'CONCAT(')) {
								$field = FabrikString::safeColName($field);
							}
							$bits[] = " $field $dir";
						} else {
							if (strstr($orderby, '_raw`')) {
								$orderby = FabrikString::safeColNameToArrayKey($orderby);
							}
							$bits[] = " $orderby $dir";
						}
					}
				}
				if (!empty($bits)) {
					$strOrder = "\n ORDER BY" . implode(',', $bits);
				}
			}
		}
		// apply group ordering
		// @TODO - explain something to hugh!  Why is this "group ordering"?  AFAICT, it's just a secondary
		// order by, isn't specific to the Group By feature in any way?  So why not just put this option in
		//
		$groupOrderBy = $params->get('group_by_order');
		if ($groupOrderBy != '') {
			$groupOrderDir = $params->get('group_by_order_dir');
			$strOrder == '' ? $strOrder = "\n ORDER BY " : $strOrder .= ',';
			if (strstr($groupOrderBy, '_raw`')) {
				$strOrder .= FabrikString::safeColNameToArrayKey($groupOrderBy) . " $groupOrderDir ";
			}
			else {
				$strOrder .= FabrikString::safeColName($groupOrderBy) ." $groupOrderDir ";
			}
		}
		return $strOrder;
	}

	/**
	 * called when the table column order by is clicked
	 * store order options in session
	 * @return null
	 */

	function setOrderByAndDir()
	{
		$session =& JFactory::getSession();
		$postOrderBy =  JRequest::getInt('orderby', '');

		$postOrderDir = JRequest::getVar('orderdir', '');
		$arOrderVals = array('asc', 'desc', '-');
		//$orderByFields =& $this->getOrderByFields();
		if (in_array($postOrderDir, $arOrderVals)) {
			// Clean the cache.
			$cache = JFactory::getCache(JRequest::getCmd('option'));
			$cache->clean();
			$context			= 'com_fabrik.table'. $this->_id.'.order.'.$postOrderBy;
			$session->set($context, $postOrderDir);
		}
	}

	/**
	 * get the part of the sql query that creates the joins
	 * used when building the table's data
	 *
	 * @return string join sql
	 */

	function _buildQueryJoin()
	{
		if (isset($this->_joinsSQL)) {
			return $this->_joinsSQL;
		}
		$statements = array();
		$table =& $this->getTable();

		$selectedTables[] = $table->db_table_name;
		$return = array();
		$joins 	= ($this->get('includeCddInJoin', true) === false) ? $this->getJoinsNoCdd() : $this->getJoins();
		$tableGroups = array();
		foreach ($joins as $join) {
			//used to bypass user joins if the table connect isnt the Joomla
			//connection
			if (!$join->canUse) {
				continue;
			}
			if ($join->join_type == '') {
				$join->join_type = 'LEFT';
			}
			$sql = strtoupper($join->join_type) ." JOIN `$join->table_join`";
			$k = FabrikString::safeColName("`$join->keytable`.`$join->table_key`");

			if ($join->table_join_alias == '') {
				$on = "`$join->table_join`.`$join->table_join_key`";
				$on = FabrikString::safeColName($on);
				$sql .=	" ON $on = $k \n";
			} else {
				$on = FabrikString::safeColName("`$join->table_join_alias`.`$join->table_join_key`");
				$sql .= " AS `$join->table_join_alias` ON $on = $k \n";
			}
			// try to order join statements to ensure that you are selecting from tables that have
			//already been included (either via a previous join statement or the table select statement)
			if (in_array($join->keytable, $selectedTables)) {
				$return[] = $sql;
				$selectedTables[] = $join->table_join;
			} else {
				//didnt find anything so defer it till later

				//$statements[$join->keytable] = $sql;
				//$$$rob - sometimes the keytable is the same for 2 deferred joins
				//in this case the first join is incorrectly overwritten in the $statements array
				//keying on join->id should solve this
				$statements[$join->id] = array($join->keytable, $sql);
			}

			//go through the deferred join statements and see if their table has now been selected
			foreach ($statements as $joinid => $ar) {
				$t = $ar[0];
				$s = $ar[1];
				if (in_array($t, $selectedTables)) {
					if (!in_array($s, $return)) { //$$$rob test to avoid duplicate join queries
						//ahhh now its there so we can add the statement
						$return[] = $s;
						unset($statements[$t]);
					}
				}
			}
		}
		// $$$rob test for bug #376
		foreach ($statements as $joinid => $ar) {
			$s = $ar[1];
			if (!in_array($s, $return)) {
				$return[] = $s;
			}
		}
		$return = implode(" ", $return);
		$this->_joinsSQL = $return;
		return $return;
	}

	function _buildQueryPrefilterWhere(&$element)
	{
		$elementName =& FabrikString::safeColName($element->getFullName(false, false, false));
		$filters = $this->getFilterArray();
		$keys = array_keys($filters);
		$vkeys = array_keys(JArrayHelper::getValue($filters, 'value', array()));
		foreach ($vkeys as $i) {
			if ($filters['search_type'][$i] != 'prefilter' || $filters['key'][$i] != $elementName) {
				foreach ($keys as $key) {
					unset($filters[$key][$i]);
				}
			}
		}
		list($sqlNoFilter, $sql) = $this->_filtersToSQL($filters);
		$where = str_replace('WHERE', '', $sql);
		if ($where != '') {
			$where = " AND $where";
		}
		return $where;
	}

	/**
	 * get the part of the main query that provides a group by statement
	 * only added by 'count' element plug-in at the moment
	 * @return string
	 */

	function _buildQueryGroupBy()
	{
		$groups =& $this->getForm()->getGroupsHiarachy();
		foreach ($groups as $groupModel) {
			$elementModels =& $groupModel->getPublishedElements();
			foreach ($elementModels as $elementModel) {
				$res = $elementModel->getGroupByQuery();
				if ($res != '') {
					$this->_pluginQueryGroupBy[] = $res;
				}
			}
		}

		if (!empty($this->_pluginQueryGroupBy)) {
			return ' GROUP BY ' . implode(', ', $this->_pluginQueryGroupBy);
		}
		return '';
	}

	/**
	 * does a filter have to be appled before we show any table data
	 * @retrun bool
	 */

	protected function filtersRequired()
	{
		$app =& JFactory::getApplication();
		$params =& $this->getParams();
		switch ($params->get('require-filter', 0)) {
			case 0:
			default:
				return false;
				break;
			case 1:
				return true;
				break;
			case 2:
				return $app->isAdmin() ? false : true;
				break;
		}
	}

	/**
	 * get the part of the sql query that relates to the where statement
	 *
	 * @param bol $incFilters if true the SQL contains
	 * any filters if false only contains prefilter sql
	 * @return string where query
	 */

	function _buildQueryWhere($incFilters = true)
	{
		$db =& JFactory::getDBO();
		if (isset($this->_whereSQL)) {
			return $this->_whereSQL[$incFilters];
		}
		$filters	=& $this->getFilterArray();
		$params =& $this->getParams();

		# $$$ hugh - added option to 'require filtering', so if no filters specified
		# we return an empty table.  Only do this where $inFilters is set, so we're only doing this
		# on the main row count and data fetch, and things like
		# filter dropdowns still get built.

		// $$$ rob prefilters shouldnt be included in 'require filtering'
		$ftypes = JArrayHelper::getValue($filters, 'search_type', array());

		foreach ($ftypes as $i => $ftype){
			if ($ftype == 'prefilter') {
				unset($ftypes[$i]);
			}
		}

		if ($incFilters && $this->filtersRequired() && empty($ftypes)) {
			$this->emptyMsg = JText::_('COM_FABRIK_SELECT_AT_LEAST_ONE_FILTER');
			return " WHERE 1 = -1 ";
		}

		$groups =& $this->getForm()->getGroupsHiarachy();
		foreach ($groups as $groupModel) {
			$elementModels =& $groupModel->getPublishedElements();
			foreach ($elementModels as $elementModel) {
				$elementModel->appendTableWhere($this->_pluginQueryWhere);
			}
		}

		if (empty($filters)) {
			// $$$ hugh - testing hack for plugins to add WHERE clauses
			if (!empty($this->_pluginQueryWhere)) {
				return 'WHERE ' . implode(' AND ', $this->_pluginQueryWhere);
			}
			else {
				return '';
			}
		}
		list($sqlNoFilter, $sql) = $this->_filtersToSQL($filters);

		$this->_whereSQL = array('0' => $sqlNoFilter, '1' => $sql);
		return $this->_whereSQL[$incFilters];
	}

	/**
	 * used by _buildWhereQuery and _buildQueryPrefilterWhere
	 * takes a filter array and returns the SQL
	 * @param array filters
	 * @return array nofilter, filter sql
	 */

	private function _filtersToSQL(&$filters)
	{
		$prefilters = $this->_groupFilterSQL($filters, 'prefilter');
		$postfilers 		= $this->_groupFilterSQL($filters);
		if (!empty($prefilters) && !empty($postfilers)) {
			array_unshift($postfilers, 'AND');
		}
		$sql = array_merge($prefilters, $postfilers);

		$pluginQueryWhere = trim(implode(' AND ', $this->_pluginQueryWhere));
		if ($pluginQueryWhere !== '') {
			$pluginQueryWhere = "(" . $pluginQueryWhere . ")";

			if (!empty($sql)) {
				$sql[] = ' AND ';
			}
			if (!empty($prefilters)) {
				$prefilters[] = ' AND ';
			}
			$sql[] = $pluginQueryWhere;
			$prefilters[] = $pluginQueryWhere;
		}
		//add in the where to the query
		if (!empty($sql)) {
			array_unshift($sql, 'WHERE');
		}
		if (!empty($prefilters)) {
			array_unshift($prefilters, 'WHERE');
		}
		$sql = implode($sql, ' ');
		$prefilters = implode($prefilters, ' ');
		return array($prefilters, $sql);
	}

	/**
	 * parse the filter array and return an array of words that will make up part of the filter query
	 * @param array $filters
	 * @param string $type * = filters, 'prefilter' = get prefilter only
	 * @return array words making up sql query.
	 */

	private function _groupFilterSQL(&$filters, $type = '*')
	{
		$groupedCount = 0;
		$ingroup = false;
		$sql= array();
		// $$$ rob keys may no longer be in asc order as we may have filtered out some in _buildQueryPrefilterWhere()
		$vkeys = array_keys(JArrayHelper::getValue($filters, 'key', array()));

		foreach ($vkeys as $i) {
			//for ($i = 0; $i < count(JArrayHelper::getValue($filters, 'key', array())); $i ++) {
			// $$$rob - prefilter with element that is not published so ignore
			$condition = strtoupper(JArrayHelper::getValue($filters['condition'], $i, ''));
			if (JArrayHelper::getValue($filters['sqlCond'], $i, '') == '' && ($condition != 'IS NULL' && $condition != 'IS NOT NULL')) {
				continue;
			}

			if ($filters['search_type'][$i] == 'prefilter' && $type == '*') {
				continue;
			}
			if ($filters['search_type'][$i] != 'prefilter' && $type == 'prefilter') {
				continue;
			}
			$n = $i +1;
			$gstart = "";
			$gend = "";
			if ($condition == 'IS NULL' || $condition == 'IS NOT NULL') {
				$filters['origvalue'][$i] = 'this is ignoerd i hope';
			}
			// $$$ rob added $filters['sqlCond'][$i] test so that you can test for an empty string
			if ($filters['origvalue'][$i] != '' || $filters['sqlCond'][$i] != '') {

				if (array_key_exists($n, $filters['grouped_to_previous'])) {
					if ($filters['grouped_to_previous'][$n] == 1) {
						if (!$ingroup) {
							// search all filter after a prefilter - alter 'join' value to 'AND'
							if ($i > 1 && JArrayHelper::getValue($filters['search_type'], $i-1) == 'prefilter' && JArrayHelper::getValue($filters['search_type'], $i) !== 'prefilter') {
								$filters['join'][$i] = 'AND';
								// $$$ hugh - if using a search form, with a multiselect object (like checkbox) and
								// prefilters, the gstart is never getting set, so have unbalanced )
								if ($filters['search_type'][$i] == 'search') {
									$gstart = "(";
									$groupedCount++;
								}
							} else {
								$gstart = "(";
								$groupedCount ++;
							}
						}

						$ingroup = true;
					} else {
						if ($ingroup) {
							$gend = ')';
							$groupedCount --;//test
							$ingroup = false;
						}
					}
				} else {
					if ($ingroup) {
						$gend = ')';
						$groupedCount --;
						$ingroup = false;
					}
				}
				$sql[] = empty($sql) ? $gstart : $filters['join'][$i].' '.$gstart;
				$sql[] = $filters['sqlCond'][$i].$gend;

				/*if ($filters['search_type'][$i] == 'prefilter') {
					// $$$rob messed up with prefilters as defined in this thread
					// http://fabrikar.com/forums/showthread.php?t=12251
					//$sqlNoFilter[] = empty($sqlNoFilter ) ? $gstart : $gstart.$filters['join'][$i];
					$sqlNoFilter[] = empty($sqlNoFilter ) ? $gstart : $filters['join'][$i].' '.$gstart;
					$sqlNoFilter[] = $filters['sqlCond'][$i].$gend;
					}*/
			}
		}
		// $$$rob ensure opening and closing parathethis for prefilters are equal
		//seems to occur if you have 3 prefilters with 2nd = grouped/AND and 3rd grouped/OR

		if ($groupedCount > 0) {
			$sql[] = str_pad('', (int)$groupedCount, ")");
		}
		//wrap in brackets
		if (!empty($sql)) {
			array_unshift($sql, '(');
			$sql[] = ')';
		}
		return $sql;
	}

	/**
	 * get a list of the tables columns' order by field names
	 * @return array order by names
	 */

	function getOrderByFields()
	{
		if (is_null($this->orderByFields)) {
			$this->orderByFields = array();
		}
		$form 			=& $this->getForm();
		$groups =& $form->getGroupsHiarachy();
		foreach ($groups as $groupModel) {
			$elementModels =& $groupModel->getPublishedElements();
			foreach ($elementModels as $elementModel) {
				$this->orderByFields[] = $elementModel->getOrderByName();
			}
		}
		return $this->orderByFields;
	}

	function getSearchAllFields()
	{
		global $_PROFILER;
		if (isset($this->searchAllAsFields)) {
			return $this->searchAllAsFields;
		}
		$searchAllFields = array();
		$this->searchAllAsFields = array();

		$form 			=& $this->getForm();
		$table 			=& $this->getTable();
		$aJoinObjs 	=& $this->getJoins();
		$groups =& $form->getGroupsHiarachy();
		$gkeys = array_keys($groups);
		$opts = array('inc_raw'=>false);
		foreach ($gkeys as $x) {
			$groupModel = $groups[$x];
			// $$$ rob moved into elementModel::getAsField_html()
			/*$table_name = $table->db_table_name;
			$group =& $groupModel->getGroup();
			if ($groupModel->isJoin()) {
			foreach ($aJoinObjs as $join) {
			//also ignore any joins that are elements
			if (array_key_exists('group_id', $join) && $join->group_id == $group->id && $join->element_id == 0) {
			$table_name =  $join->table_join;
			}
			}
			}*/

			$elementModels =& $groupModel->getPublishedElements();
			for ($ek = 0; $ek < count($elementModels); $ek ++) {
				$elementModel = $elementModels[$ek];
				if ($elementModel->getParams()->get('inc_in_search_all', true)) {
					// boolean search doesnt seem possible on encrypted fields.
					$p =& $elementModel->getParams();
					$o = $p->get('encrypt');
					$p->set('encrypt', false);
					$elementModel->getAsField_html($this->searchAllAsFields, $searchAllFields, '', $opts);
					$p->set('encrypt', $o);
				}
			}
		}

		$db =& JFactory::getDBO();
		//if the group by element isnt in the fields (IE its not published) add it (otherwise group by wont work)

		$longGroupBy = $db->NameQuote(FabrikString::safeColNameToArrayKey($table->group_by));
		if (!in_array($longGroupBy, $searchAllFields) && trim($table->group_by) != '') {
			$this->searchAllAsFields[] = FabrikString::safeColName($table->group_by) . " AS " . $longGroupBy;
			$searchAllFields[] = $longGroupBy;
		}

		for ($x=0; $x < count($this->searchAllAsFields); $x ++) {
			$match = ' AS '.$searchAllFields[$x];
			if (array_key_exists($x, $this->searchAllAsFields)) {
				$this->searchAllAsFields[$x] = trim(str_replace($match, '', $this->searchAllAsFields[$x]));
			}
		}
		$this->searchAllAsFields = array_unique($this->searchAllAsFields);
		return $this->searchAllAsFields;
	}

	/**
	 * get the part of the table sql statement that selects which fields to load
	 *
	 * @return array field names to select in getelement data sql query
	 */

	function &getAsFields()
	{
		global $_PROFILER;
		if (isset($this->asfields)) {
			return $this->asfields;
		}
		$this->fields = array();
		$this->asfields = array();

		$form 			=& $this->getForm();
		$table 			=& $this->getTable();
		$aJoinObjs 	=& $this->getJoins();
		$this->_temp_db_key_addded = false;
		$groups =& $form->getGroupsHiarachy();
		$gkeys = array_keys($groups);
		foreach ($gkeys as $x) {
			$groupModel = $groups[$x];
			// $$$ rob depreciated
			/*$table_name = $table->db_table_name;
			$group =& $groupModel->getGroup();
			if ($groupModel->isJoin()) {
			foreach ($aJoinObjs as $join) {
			//also ignore any joins that are elements
			if (array_key_exists('group_id', $join) && $join->group_id == $group->id && $join->element_id == 0) {
			$table_name =  $join->table_join;
			}
			}
			}*/
			$elementModels =& $groupModel->getPublishedElements();
			foreach ($elementModels as $elementModel) {
				$method = "getAsField_" . $this->_outPutFormat;
				if (!method_exists($elementModel, $method)) {
					$method = "getAsField_html";
				}
				$elementModel->$method($this->asfields, $this->fields);
			}
		}
		//temporaraily add in the db key so that the edit links work, must remove it before final return
		//	of getData();
		JDEBUG ? $_PROFILER->mark('getAsFields: starting to test if a view') : null;
		if (!$this->isView()) {
			if (!$this->_temp_db_key_addded && $table->db_primary_key != '') {
				$str = str_replace('___', '.', $table->db_primary_key) . " AS " . str_replace('.', '___', $table->db_primary_key);
				//if we are quoting the primary key in the db then we need to remove these quotes
				$str = str_replace('`___`', '___', $str);

				$this->fields[] = str_replace('`.`', '___', $table->db_primary_key);

				// $$$ rob - on http://devplay.simongames.co.uk/index.php?option=com_fabble&view=person&Itemid=2
				//this was giving an sql error - db_primary_key was in ___ format???
				//$this->asfields[] = $table->db_primary_key;
			}
		}
		JDEBUG ? $_PROFILER->mark('getAsFields: end of view test') : null;
		//for raw data in packages

		if ($this->_outPutFormat == 'raw') {
			$str = str_replace('___', '.', $table->db_primary_key) . " AS __pk_val";
			$str = str_replace('`___`', '___', $str);
			$this->fields[] = $str;
		}

		$db =& JFactory::getDBO();
		$this->_group_by_added = false;
		//if the group by element isnt in the fields (IE its not published) add it (otherwise group by wont work)

		$longGroupBy = $db->NameQuote(FabrikString::safeColNameToArrayKey($table->group_by));
		if (!in_array($longGroupBy, $this->fields) && trim($table->group_by) != '') {
			$this->asfields[] = FabrikString::safeColName($table->group_by) . " AS " . $longGroupBy;
			$this->fields = $longGroupBy;
			$this->_group_by_added = true;
		}
		return $this->asfields;
	}

	/**
	 * checks if the params object has been created and if not creates and returns it
	 * @return object params
	 */

	function &getParams()
	{
		$table =& $this->getTable();
		if (!isset($this->_params)) {
			$this->_params = new fabrikParams($table->attribs, JPATH_SITE . '/administrator/components/com_fabrik/models/table.xml', 'component');
		}
		return $this->_params;
	}


	/**
	 * Method to set the table id
	 *
	 * @access	public
	 * @param	int	table ID number
	 */

	function setId($id)
	{
		$this->_id = $id;
	}

	function getId()
	{
		return $this->_id;
	}

	/**
	 * get the table object for the models _id
	 *
	 * @param bool force load the table
	 * @param bool if no table found raise an error
	 * @return object table
	 *
	 */

	function &getTable($force = false, $mustfind = true)
	{
		if ($force || is_null($this->_table) || !is_object($this->_table)) {
			JTable::addIncludePath(JPATH_ADMINISTRATOR.DS.'components'.DS.'com_fabrik'.DS.'tables');
			$this->_table =& JTable::getInstance('table', 'Table');
			if ((int)$this->_id === 0 && $mustfind) {
				//JError::raiseError(500, 'Did not find a tableid to load - check that you have tableid=X in your URL');
			}
			$this->_table->load($this->_id);
		}
		return $this->_table;
	}

	/**
	 * load  the database associated with the table
	 *@return object database
	 */

	function &getDb()
	{
		if (!isset($this->_oConnDB)) {
			$cnn =& $this->getConnection();
			if (!is_object($cnn)) {
				JError::raiseError(E_ERROR, JText::_('FABRIK WAS UNABLE TO LOAD THE DATABASE OBJECT FOR THIS TABLE'));
			}
			$this->_oConnDB =& $cnn->getDb();

			if (JError::isError($this->_oConnDB) || $this->_oConnDB === false) {
				JError::raiseError(E_ERROR, JText::_('FABRIK COULD NOT LOAD DATABASE CONNECTION'));
			}
		}
		return $this->_oConnDB;
	}

	/**
	 * function get the tables connection object
	 * sets $this->_oConn to the tables connection
	 * @return object connection
	 */

	function &getConnection()
	{
		$config =& JFactory::getConfig();
		if (!isset($this->_oConn)) {
			$table =& $this->getTable();
			$connectionModel =& JModel::getInstance('connection', 'FabrikModel');
			$connId = is_null($table->connection_id ) ? JRequest::getVar('connection_id', null ) : $table->connection_id;
			$connectionModel->setId($connId);
			if ($connId == '' || is_null($connId) ||  $connId == '-1') { //-1 for creating new table
				$connectionModel->loadDefaultConnection();
				$connectionModel->setId($connectionModel->_connection->id);
			}
			$connection =& $connectionModel->getConnection();
			$this->_oConn =& $connectionModel;
		}
		return $this->_oConn;
	}

	/**
	 * is the table published
	 * Dates are stored as UTC so we can compare them against a date with no offset applied
	 * @return bol published state
	 */

	function canPublish()
	{
		$table =& $this->getTable();
		$db =& JFactory::getDBO();
		$nullDate = (method_exists($db, 'getNullDate')) ? $db->getNullDate() : $this->getNullDate();
		$publishup =& JFactory::getDate($table->publish_up);
		$publishup = $publishup->toUnix();

		$publishdown =& JFactory::getDate($table->publish_down);
		$publishdown = $publishdown->toUnix();

		$jnow		=& JFactory::getDate();
		$now		= $jnow->toUnix();
		if ($table->state == '1') {
			if ($now >= $publishup || $table->publish_up == '' || $table->publish_up == $nullDate) {
				if ($now <= $publishdown || $table->publish_down == '' || $table->publish_down == $nullDate) {
					return true;
				}
			}
		}
		return false;
		return $table->state;
	}

	/**
	 * if you have koowa installed their db obj doesnt have a getNullDate function
	 * @return unknown_type
	 */

	function getNullDate()
	{
		return '0000-00-00 00:00:00';
	}

	/**
	 * access control to determine if the current user has rights to drop data
	 * from the table
	 * @return bol yes/no
	 */

	function canEmpty()
	{
		$params =& $this->getParams();
		if (!is_object($this->_access) || !array_key_exists('allow_drop', $this->_access)) {
			$this->_access->allow_drop = FabrikWorker::getACL($params->get('allow_drop'), 'allow_drop');
		}
		return $this->_access->allow_drop;
	}

	/**
	 * check if the user can view the detailed records
	 *
	 * @return bol
	 */

	function canViewDetails()
	{
		$params =& $this->getParams();
		if (!is_object($this->_access) || !array_key_exists('viewdetails', $this->_access)) {
			$this->_access->viewdetails = FabrikWorker::getACL($params->get('allow_view_details'), 'viewdetails');
		}
		return $this->_access->viewdetails;
	}

	/**
	 * checks user access for editing records
	 * @param object row of data currently active
	 * @return bol access allowed
	 */

	function canEdit($row = null)
	{
		$params =& $this->getParams();
		$canUserDo = $this->canUserDo($row, 'allow_edit_details2');
		// $$$ hugh - AAAAAAGHHHH!!!  This one took a while ...
		// canUserDo() returns true, false, or -1 ... when "loose" testing with !=
		// then true is the same as -1.  But we want strict testing, with !==
		// so it isn't the same!
		// Hmmm - I just noticed canDelete() already has a !== here.
		//if ($canUserDo != -1) {
		if ($canUserDo !== -1) {
			return $canUserDo;
		}

		// $$$ hugh - FIXME - we really need to split out a onCanEditRow method, rather than overloading
		// onCanEdit for both table and per-row contexts.  At the moment, we calling per-row plugins with
		// null $row when canEdit() is called in a table context.
		$canEdit = $this->getPluginManager()->runPlugins('onCanEdit', $this, 'table', $row);
		if (in_array(false, $canEdit)) {
			return false;
		}


		if (!is_object($this->_access) || !array_key_exists('edit', $this->_access)) {
			$this->_access->edit = FabrikWorker::getACL($params->get('allow_edit_details', 25 ), 'edit');
		}
		return $this->_access->edit;
	}

	/**
	 * checks if any one row is editalbe = used to get the correct headings
	 */

	protected function canEditARow()
	{
		$data = $this->getData();
		foreach ($data as $rows) {
			foreach ($rows as $row) {
				if ($this->canEdit($row)) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * access control function for determining if the user can perform
	 * a designated function on a specific row
	 * @param object $row data
	 * @param string $col access control setting to compare against
	 * @return mixed - if ACL setting defined here return blo, otherwise return -1 to contiune with default acl setting
	 */

	function canUserDo($row, $col)
	{
		if (!is_null($row)) {
			$params =& $this->getParams();
			$user = &JFactory::getUser();
			$usercol =$params->get($col, '');
			if ($usercol !=  '') {
				$usercol = FabrikString::safeColNameToArrayKey($usercol);

				if (!array_key_exists($usercol, $row)) {
					return false;
				} else {
					if (array_key_exists($usercol . "_raw", $row)) {
						$usercol .= "_raw";
					}
					$myid = $user->get('id');
					//-1 for menu items that link to their own reocrds
					// $$$ hugh - TODO - test - something doesn't look right about this logic!!
					// $$$ hugh - was $row->$usercol, but $row is an array not an object!
					// $$$ hugh - oops, it's an array when coming here from form, object when coming from table!
					if (is_array($row)) {
						$usercol_val = $row[$usercol];
					}
					else {
						$usercol_val = $row->$usercol;
					}
					if (empty($usercol_val) && empty($myid)) {
						return false;
					}
					if (intVal($usercol_val) === intVal($myid) || JRequest::getVar('rowid') == -1) {
						return true;
					}
					# $$$ hugh - testing making the "or use field" truly an OR, so they can edit
					# if they either have usercol privs or the regular ACL.  i.e. if this test fails,
					# don't reurn false, rather drop through and test regular ACL.
					/*
					else {
					return false;
					}
					*/
				}
			}
		}
		return -1;
	}

	/**
	 * checks user access for deleting records
	 * @param object row of data currently active
	 * @return bol access allowed
	 */

	function canDelete($row = null)
	{
		$canUserDo = $this->canUserDo($row, 'allow_delete2');
		if ($canUserDo !== -1) {
			if ($canUserDo === true) {
				$this->_access->deletePossible = true;
			}
			return $canUserDo;
		}
		$params =& $this->getParams();
		if (!is_object($this->_access) || !array_key_exists('delete', $this->_access)) {
			// @TODO - rob, sanity check - shouldn't this be 'delete', not 'edit'??
			$this->_access->delete = FabrikWorker::getACL($params->get('allow_delete', 25), 'delete');
		}
		return $this->_access->delete;
	}

	/**
	 * determin if any record can be deleted - used to see if we include the
	 * delete button in the table view
	 * @return bol
	 */

	function deletePossible()
	{
		if (is_object($this->_access)) {
			if (array_key_exists('deletePossible', $this->_access)) {
				return $this->_access->deletePossible;
			}
		}
		return $this->canDelete();
	}

	/**
	 * checks user access for importing csv
	 *
	 * @return bol access allowed
	 */

	function canCSVImport()
	{
		$params =& $this->getParams();
		if (!is_object($this->_access) || !array_key_exists('csvimport', $this->_access)) {
			if ($params->get('csv_import_frontend', 0) == 1) {//compat with old yes/no (cant do anything about no option as its the same as 'everybody'
				$this->_access->csvimport = true;
			} else {
				$this->_access->csvimport = FabrikWorker::getACL($params->get('csv_import_frontend', 25), 'csvimport');
			}
		}
		return $this->_access->csvimport;
	}

	/**
	 * checks user access for exporting csv
	 *
	 * @return bol access allowed
	 */

	function canCSVExport()
	{
		$params =& $this->getParams();
		if (!is_object($this->_access) || !array_key_exists('csvexport', $this->_access)) {
			if ($params->get('csv_export_frontend', 0) == 1) {//compat with old yes/no (cant do anything about no option as its the same as 'everybody'
				$this->_access->csvexport = true;
			} else {
				$this->_access->csvexport = FabrikWorker::getACL($params->get('csv_export_frontend', 25), 'csvexport');
			}
		}
		return $this->_access->csvexport;
	}

	/**
	 * checks user access for adding records
	 *
	 * @return bol access allowed
	 */

	function canAdd()
	{
		$params =& $this->getParams();
		if (!is_object($this->_access) || !array_key_exists('add', $this->_access)) {
			$this->_access->add = FabrikWorker::getACL($params->get('allow_add', 25), 'add');
		}
		return $this->_access->add;
	}

	/**
	 * check use can view the table
	 * @return bol can view or not
	 */

	function canView()
	{
		$params =& $this->getParams();
		if (!is_object($this->_access) || !array_key_exists('view', $this->_access)) {
			$this->_access->view = FabrikWorker::getACL($params->get('access'), 'view');
		}
		return $this->_access->view;
	}

	/**
	 * load the table from the form_id value
	 * @param int $formId
	 * @return object table row
	 */

	function loadFromFormId($formId)
	{
		JTable::addIncludePath(JPATH_ADMINISTRATOR.DS.'components'.DS.'com_fabrik'.DS.'table');
		$row = JTable::getInstance('table', 'Table');
		$origKey = $row->getKeyName();
		//$row->_tbl_key = "form_id";
		$row->set('_tbl_key', "form_id");
		$row->load($formId);
		$this->_table = $row;
		//$row->_tbl_key = $origKey;
		$row->set('_tbl_key', $origKey);
		$this->setId($row->id);
		return $row;
	}

	/**
	 * like getJoins() but exclude cascading dropdown joins
	 * seems to be needed when calculating related table's record counts.
	 * This is called frm within _buildQueryJoin()
	 * and fired if this is done:
	 * $tableModel->set('includeCddInJoin', false);
	 * as in tableModel::getRecordCounts()
	 * @return array join objects (table rows - not table objects or models)
	 */

	function getJoinsNoCdd()
	{
		if (!isset($this->_joinsNoCdd)) {
			$form =& $this->getForm();
			$form->getGroupsHiarachy();
			$ignore = array('FabrikModelFabrikCascadingdropdown');
			$ids = $form->getElementIds( $ignore);
			$db =& JFactory::getDBO();
			$sql = "SELECT * FROM #__fabrik_joins WHERE table_id = ".(int)$this->_id;
			if (!empty($ids)) {
				$sql .= " OR element_id IN ( ".implode(", ", $ids).")";
			}
			//maybe we will have to order by element_id asc to ensure that table joins are loaded
			//before element joins (if an element join is in a table join then its 'join_from_table' key needs to be updated
			$sql .= " ORDER BY id";
			$db->setQuery($sql);
			$this->_joinsNoCdd = $db->loadObjectList();

			if ($db->getErrorNum()) {
				JError::raiseError(500, $db->stderr());
			}
			$this->_makeJoinAliases($this->_joinsNoCdd);
		}
		return $this->_joinsNoCdd;
	}
	/**
	 * @return array join objects (table rows - not table objects or models)
	 */

	function &getJoins()
	{
		if (!isset($this->_aJoins)) {

			$form =& $this->getForm();
			$form->getGroupsHiarachy();
			$ids = $form->getElementIds();
			$db =& JFactory::getDBO();
			$sql = "SELECT * FROM #__fabrik_joins WHERE table_id = ".(int)$this->_id;
			if (!empty($ids)) {
				$sql .= " OR element_id IN ( ".implode(", ", $ids).")";
			}
			//maybe we will have to order by element_id asc to ensure that table joins are loaded
			//before element joins (if an element join is in a table join then its 'join_from_table' key needs to be updated
			$sql .= " ORDER BY id";
			$db->setQuery($sql);
			$this->_aJoins = $db->loadObjectList();

			if ($db->getErrorNum()) {
				JError::raiseError(500, $db->stderr());
			}
			$this->_makeJoinAliases($this->_aJoins);
		}
		return $this->_aJoins;
	}

	function _makeJoinAliases(&$joins)
	{
		$app =& JFactory::getApplication();
		$prefix = $app->getCfg('dbprefix');
		$table =& $this->getTable();
		$db =& JFactory::getDBO();
		$aliases = array($table->db_table_name);
		$tableGroups = array();

		//build up the alias and $tableGroups array first
		foreach ($joins as &$join) {
			$join->canUse = true;
			if ($join->table_join == '#__users' || $join->table_join == $prefix . 'users') {
				if ($db != $this->getDb()) {
					$join->canUse = false;
				}
			}
			$tablejoin = str_replace('#__', $prefix, $join->table_join);
			if (in_array($tablejoin, $aliases)) {
				$base = $tablejoin;
				$a = $base;
				$c = 0;
				while (in_array($a, $aliases)) {
					$a = "{$base}_{$c}";
					$c ++;
				}
				$join->table_join_alias = $a;
			} else {
				$join->table_join_alias = $tablejoin;
			}

			$aliases[] = str_replace('#__', $prefix, $join->table_join_alias);

			if (!array_key_exists($join->group_id, $tableGroups)) {
				if ($join->element_id == 0) {
					$tableGroups[$join->group_id] = $join->table_join_alias;
				}
			}
		}
		foreach ($joins as &$join) {
			//if they are element joins add in this tables name as the calling joining table.
			if ($join->join_from_table == '') {
				$join->join_from_table = $table->db_table_name;
			}

			// test case:
			/*
			* you have a talbe that joins to a 2nd table
			* in that 2nd table there is a database join element
			* that 2nd elements key needs to point to the 2nd tables name and not the first
			*
			* e.g. when you want to create a n-n relationship
			*
			* events -> (table join) events_artists -> (element join) artist
			*/

			$join->keytable = $join->join_from_table;
			if (!array_key_exists($join->group_id, $tableGroups)) {

			} else {
				if ($join->element_id != 0) {
					$join->keytable = $tableGroups[$join->group_id];
					//test
					$join->join_from_table = $join->keytable;
				}
			}
		}
		FabrikHelperHTML::debug($joins, 'joins');
	}


	/**
	 * gets the field names for the given table
	 * @param string table name
	 * @return array table fields
	 */

	function getDBFields($tbl = null, $key = null)
	{
		if (is_null($tbl)) {
			$table =& $this->getTable();
			$tbl = $table->db_table_name;
		}
		$sig = $tbl.$key;
		$tbl = FabrikString::safeColName($tbl);
		if (!isset($this->_dbFields[$sig])) {
			$db =& $this->getDb();
			if (!strstr($tbl, '`')) {
				$tbl = $db->nameQuote($tbl);
			}
			$db->setQuery("DESCRIBE ".$tbl);
			$this->_dbFields[$sig] = $db->loadObjectList($key);
			if (!$this->_dbFields[$sig]) {
				JError::raiseWarning(500, $db->getErrorMsg());
				$this->_dbFields[$sig] = array();
			}
		}
		return $this->_dbFields[$sig];
	}

	/**
	 * called at the end of saving an element
	 * @param object $elementModel
	 * @param string $origColName
	 * @return array($update, $q, $oldName, $newdesc, $origDesc)
	 */

	public function shouldUpdateElement(&$elementModel, $origColName = null)
	{
		$db =& JFactory::getDBO();
		$return 		= array(false, '', '', '', '');
		$element 		=& $elementModel->getElement();
		$pluginManager =& $this->getPluginManager();
		$basePlugIn =& $pluginManager->getPlugIn($element->plugin, 'element');
		$fbConfig 	=& JComponentHelper::getParams('com_fabrik');
		$fabrikDb 	=& $this->getDb();
		$group 			=& $elementModel->getGroup();

		//$$$ rob - replaced this with getting the table from the group as if we moved the element
		//from one group to another $this->getTable gives you the old group's table, where as we want
		// the new group's table
		//$table 			=& $this->getTable();
		$table =& $group->getTableModel()->getTable();

		// $$$ rob - if we are saving an element that wasn't attached to a group then we should
		// get the table id from the group's table
		if ($table->id == '') {
			$table =& $group->getTableModel()->getTable();
		}
		// $$$ hugh - if this is a table-less form ... not much point going any
		// further 'cos things will go BANG
		if (empty($table->id)) {
			return $return;
		}
		if ($this->isView()) {
			return $return;
		}
		if ($group->isJoin()) {
			$tableName = $group->getJoinModel()->getJoin()->table_join;
			$keydata = $this->getPrimaryKeyAndExtra($tableName);
			$keydata = $keydata[0];
			$primaryKey = $keydata['colname'];
		} else {
			$tableName 	= $table->db_table_name;
			$primaryKey = $table->db_primary_key;
		}
		// $$$ rob base plugin needs to know group info for date fields in non-join repeat groups
		$basePlugIn->_group =& $elementModel->_group;
		$objtype = $elementModel->getFieldDescription(); //the element type AFTER saving
		$dbdescriptions = $this->getDBFields($tableName, 'Field');
		if (!$this->_canAlterFields()) {
			$objtype = $dbdescriptions[$origColName]->Type;
		}
		$thisFieldDesc = $dbdescriptions[$origColName];

		// $$$ rob the Default property for timestamps when they are set to CURRENT_TIMESTAMP
		// doesn't show up from getDBFields()  - so presuming a timestamp field will always default
		// to the current timestamp (update of the field's data controller in the Extra property (on update CURRENT_TIMESTAMP)
		// $$$ hugh - FIXME $thisFieldDesc might be null, i.e. setting group on a previously non-grouped or tableless element
		$existingDef = $thisFieldDesc->Type;
		if ($thisFieldDesc->Type == 'timestamp') {
			$existingDef .= $thisFieldDesc->Null = 'YES' ? ' NULL' : ' NOT NULL';
			$existingDef .= ' DEFAULT CURRENT_TIMESTAMP';
			$existingDef .= ' ' . $thisFieldDesc->Extra;
		}

		if (!is_null($objtype)) {
			if ($element->name == $origColName && strtolower(trim($existingDef)) == strtolower(trim($objtype))) {
				//no chanages to the element name or field type
				return $return;
			}
			$return[4] = $existingDef;
			$exitingfields = array_keys($dbdescriptions);

			$lastfield = $exitingfields[count($exitingfields)-1];
			$element->name = FabrikString::safeColName($element->name);
			$tableName = FabrikString::safeColName($tableName);
			$lastfield = FabrikString::safeColName($lastfield);

			// $$$ rob this causes issues when renaming an element with the same name but different upper/lower case
			//if (empty($origColName) || !in_array(strtolower($origColName ), $exitingfields)) {
			if (empty($origColName) || !in_array(( $origColName ), $exitingfields)) {
				$fabrikDb->setQuery("ALTER TABLE $tableName ADD COLUMN $element->name $objtype AFTER $lastfield");
				if (!$fabrikDb->query()) {
					return JError::raiseError(500, 'alter structure: ' . $fabrikDb->getErrorMsg());
				}

			} else {
				// $$$ rob don't alter it yet - lets defer this and give the user the choice if they
				// really want to do this
				if ($this->_canAlterFields()) {
					if ($origColName == null) {
						$origColName = $element->name;
					} else {
						$origColName = $db->nameQuote($origColName);
					}
					$q = "ALTER TABLE $tableName CHANGE $origColName $element->name $objtype ";
					if ($primaryKey == $db->NameQuote($tableName) . "." . $element->name && $table->auto_inc) {
						if (!strstr($q, 'NOT NULL AUTO_INCREMENT')) {
							$q .= " NOT NULL AUTO_INCREMENT ";
						}
					}
					$origColName = FabrikString::safeColName($origColName);
					$return[0] = true;
					$return[1] = $q;
					$return[2] = $origColName;
					$return[3] = $objtype;
					return $return;
				}
			}

		}
		return $return;
	}

	/**
	 * add or update a database column via sql
	 * @param object element plugin
	 * @param string origional field name
	 */

	function alterStructure(&$elementModel, $origColName = null)
	{
		$db =& JFactory::getDBO();
		$element =& $elementModel->getElement();
		$pluginManager =& $this->getPluginManager();
		$basePlugIn =& $pluginManager->getPlugIn($element->plugin, 'element');
		$fbConfig 	=& JComponentHelper::getParams('com_fabrik');
		$fabrikDb 	=& $this->getDb();
		$table 			=& $this->getTable();
		$tableName 	= $table->db_table_name;
		// $$$ rob base plugin needs to know group info for date fields in non-join repeat groups
		$basePlugIn->_group =& $elementModel->_group;
		$objtype = $elementModel->getFieldDescription();
		$dbdescriptions = $this->getDBFields($tableName);
		if (!$this->_canAlterFields()) {
			foreach ($dbdescriptions as $f) {
				if ($f->Field == $origColName) {
					$objtype = $f->Type;
				}
			}
		}
		if (!is_null($objtype)) {
			foreach ($dbdescriptions as $dbdescription) {
				$fieldname = strtolower($dbdescription->Field);
				if (strtolower($element->name) == $fieldname && strtolower($dbdescription->Type) == strtolower($objtype)) {
					return;
				}
				$exitingfields[] = $fieldname;
			}
			$lastfield = $fieldname;
			$element->name = FabrikString::safeColName($element->name);
			$tableName = FabrikString::safeColName($tableName);
			$lastfield = FabrikString::safeColName($lastfield);
			// @TODO - Rob, please sanity check this!
			// $$$ hugh - erm, this if statement MUST be wrong!  Otherwise we can never change an element name,
			// it'll always create a new colum.
			// if (!in_array(strtolower($name ), $exitingfields) || !in_array(strtolower($origColName ), $exitingfields)) {
			if (empty($origColName) || !in_array(strtolower($origColName ), $exitingfields)) {

				$fabrikDb->setQuery("ALTER TABLE $tableName ADD COLUMN $element->name $objtype AFTER $lastfield");
				if (!$fabrikDb->query()) {
					return JError::raiseError(500, 'alter structure: ' . $fabrikDb->getErrorMsg());
				}

			} else {
				if ($this->_canAlterFields()) {
					if ($origColName == null) {
						$origColName = $element->name;
					}
					$origColName = FabrikString::safeColName($origColName);
					$fabrikDb->setQuery("ALTER TABLE $tableName CHANGE $origColName $element->name $objtype");
					if (!$fabrikDb->query()) {
						return JError::raiseError(500, 'alter structure: ' . $fabrikDb->getErrorMsg());
					}
				}
			}

			$this->createCacheQuery();
		}
		return true;
	}

	/**
	 * can we alter this tables fields structure?
	 * @return bol
	 */

	function _canAlterFields()
	{
		$fbConfig =& JComponentHelper::getParams('com_fabrik');
		$params =& $this->getParams();
		$alter = $params->get('alter_existing_db_cols', 'notset');
		if ($alter == 'notset') {
			//fall back to old global settting
			$alter = $fbConfig->get('fbConf_alter_existing_db_cols', true);
		}
		return $alter;
	}

	/**
	 * if not loaded this loads in the table's form model
	 * also binds a reference of the table to the form.
	 * @return object form model with form table loaded
	 */

	function &getForm()
	{
		if (is_null($this->_oForm)) {
			$this->_oForm =& JModel::getInstance('Form', 'FabrikModel');
			$table =& $this->getTable();
			$this->_oForm->setId($table->form_id);
			$this->_oForm->getForm();
			$this->_oForm->setTableModel($this);
			//ensure joins are loaded
		}
		return $this->_oForm;
	}

	/**
	 * tests if the table is in fact a view
	 * @returns true if table is a view
	 */

	function isView()
	{
		$params =& $this->getParams();
		$isview = $params->get('isview', null);

		// $$$ hugh - because querying INFORMATION_SCHEMA can be very slow (like minutes!) on
		// a shared host, I made a small change.  The edit table view now adds a hidden 'isview'
		// param, defaulting to -1 on new tables.  So the following code should only ever execute
		// one time, when a new table is saved.  Before this change, because 'isview' wasn't
		// included on the edit view (because it's not a "real" user settable param), so didn't
		// exist when we picked up the params from the submitted data, this code was running (twice!)
		// every time a table was saved.
		// http://fabrikar.com/forums/showthread.php?t=16622&page=6
		if (is_null($isview) || (int)$isview < 0) {
			if (isset($this->_isview)) {
				return $this->_isview;
			}
			$db =& JFactory::getDBO();
			$table =& $this->getTable();
			$cn =& $this->getConnection();
			$c = $cn->getConnection();
			$dbname = $c->database;
			if ($table->db_table_name == '') {
				return;
			}
			$sql = " SELECT table_name, table_type, engine FROM INFORMATION_SCHEMA.tables ".
			"WHERE table_name = " . $db->Quote($table->db_table_name) ." AND table_type = 'view' AND table_schema = " . $db->Quote($dbname);
			$db->setQuery($sql);
			$row = $db->loadObjectList();
			if (empty($row)) {
				$isview = 0;
				$this->_isview = 0;
			} else {
				// $$$ hugh - not quite sure why we are testing empty($row) again??
				$isview = empty($row) ? false : true;
				$this->_isview = $isview ? 1 : 0;
			}
			//store and save param for following tests
			$params->set('isview', $isview);
			$table->attribs .= "\nisview=" . $this->_isview . "\n";
			$table->store();

			// $$$ hugh - testing an alternative to querying INFORMATION_SCHEMA directly
			/*
			$fabrikDb =& $this->getDb();
			$fabrikDb->setQuery("show create table " . $fabrikDb->nameQuote($table->db_table_name));
			$row = $db->loadObjectList();
			*/
		}
		return $isview;
	}

	/**
	 * store filters in the registry
	 * @param array filters to store
	 * @return null
	 */

	function storeRequestData($request)
	{
		$app =& JFactory::getApplication();
		$session =& JFactory::getSession();
		$registry	=& $session->get('registry');
		$tid = 'table'.$this->_id;
		//make sure that we only store data thats been entered from this page first test we aren't in a plugin
		if (JRequest::getCmd('option') == 'com_fabrik' && is_object($registry)) {
			// dont do this when you are viewing a form or details page as it wipes out the table filters
			$reg =& $registry->get('_registry');
			if (isset($reg['com_fabrik']) && !in_array(JRequest::getCmd('view'), array('form', 'details'))) {
				unset($reg['com_fabrik']['data']->$tid->filter);
			}
		}
		//@TODO test for _clear_ in values and if so delete session data
		foreach ($request as $key => $val) {
			if (is_array($val)) {
				$key = 'com_fabrik.'. $tid .'.filter.'.$key;
				$app->setUserState($key, array_values($val));
			}
		}
		if (isset($reg['com_fabrik']) && array_key_exists($tid, $reg['com_fabrik']['data'])) {
			FabrikHelperHTML::debug($reg['com_fabrik']['data']->$tid, 'session filters saved as:');
		}
		else {
			FabrikHelperHTML::debug('', 'session filters saved as: no filters to save!');
		}
	}

	/**
	 * creates filter array (return existing if exists)
	 *@return array filters
	 */

	function &getFilterArray()
	{
		if (isset($this->filters)) {
			return $this->filters;
		}

		$filterModel =& $this->getFilterModel();

		$db =& JFactory::getDBO();

		$this->filters = array();
		$user  					= &JFactory::getUser();
		$request 				=& $this->getRequestData();
		$this->storeRequestData($request);
		FabrikHelperHTML::debug($request, 'filter:request');

		$params =& $this->getParams();
		$elements =& $this->getElements('id');

		// $$$ rob prefilters loaded before anything to avoid issues where you filter on something and
		// you have 2 prefilters with joined by an OR - this was incorrectly giving SQL of
		// WHERE normal filter = x OR ( prefilter1 = y OR prefilter2 = x)
		// this change changes the SQL to
		// WHERE ( prefilter1 = y OR prefilter2 = x) AND normal filter = x
		$this->getPrefilterArray($this->filters);

		// these are filters created from a search form or normal search
		$keys = array_keys($request);
		$indexStep = count(JArrayHelper::getValue($this->filters, 'key', array()));
		FabrikHelperHTML::debug($keys, 'filter:request keys');
		foreach ($keys as $key) {
			if (is_array($request[$key])) {
				foreach ($request[$key] as $kk => $v) {
					if (!array_key_exists($key, $this->filters) || !is_array($this->filters[$key])) {
						$this->filters[$key] = array();
					}
					$this->filters[$key][$kk + $indexStep] = $v;
				}
			}
		}

		FabrikHelperHTML::debug($this->filters, 'tablemodel::getFilterArray middle');

		$readOnlyValues = array();

		$w = new FabrikWorker();

		$noFiltersSetup = JArrayHelper::getValue($this->filters, 'no-filter-setup', array());

		if (count($this->filters) == 0) {
			$this->getPluginManager()->runPlugins('onFiltersGot', $this, 'table');
			return $this->filters;
		}

		//get a list of plugins
		$pluginKeys = $filterModel->getPluginFilterKeys();
		$elementids = JArrayHelper::getValue($this->filters, 'elementid', array());
		$sqlCond = JArrayHelper::getValue($this->filters, 'sqlCond', array());
		$raws = JArrayHelper::getValue($this->filters, 'raw', array());
		//for ($i=0; $i < count($this->filters['key']); $i++) {
		foreach ($this->filters['key'] as $i => $keyval) {
			$value 					= $this->filters['value'][$i];
			$condition 			= strtolower($this->filters['condition'][$i]);
			$key 						= $this->filters['key'][$i];
			$filterEval	= $this->filters['eval'][$i];
			$elid 					= JArrayHelper::getValue($elementids, $i);
			$key2						= array_key_exists('key2', $this->filters) ? JArrayHelper::getValue($this->filters['key2'], $i, '') : '';

			// $$$ rob see if the key is a raw filter
			// 20/12/2010 - think $key is never with _raw now as it is unset in tablefilter::getQuerystringFilters() although may  be set elsewhere
			// - if it is make a note and remove the _raw from the name
			$raw = JArrayHelper::getValue($raws, $i, false);
			if (substr($key, -5, 5) == '_raw`') {
				$key = substr($key, 0, strlen($key) -5) . '`';
				$raw = true;
			}
			if ($elid == -1) {
				//bool match
				$this->filters['origvalue'][$i] = $value;
				$this->filters['sqlCond'][$i] = $key.' '.$condition.' ('.$db->Quote($value) . ' IN BOOLEAN MODE)';
				continue;
			}

			//table plug-in filter found - it should have set its own sql in onGetPostFilter();
			if (in_array($elid, $pluginKeys)) {
				$this->filters['origvalue'][$i] = $value;
				$this->filters['sqlCond'][$i] = $this->filters['sqlCond'][$i];
				continue;

			}
			$elementModel = JArrayHelper::getValue($elements, $elid);
			// $$$ rob key2 if set is in format  `countries_0`.`label` rather than  `countries`.`label`
			// used for search all filter on 2nd db join element pointing to the same table
			if (strval($key2) !== '') {
				$key = $key2;
			}
			$eval						= $this->filters['eval'][$i];
			$fullWordsOnly 	= $this->filters['full_words_only'][$i];
			$exactMatch = $this->filters['match'][$i];

			if (!is_a($elementModel, 'FabrikModelElement')) {
				continue;
			}
			$elementModel->_rawFilter = $raw;
			// $$ hugh - testing allowing {QS} replacements in pre-filter values
			$w->replaceRequest($value);
			$value = $this->_prefilterParse($value);
			$value = $w->parseMessageForPlaceHolder($value);
			if ($filterEval == '1') {
				$value 	= stripslashes(htmlspecialchars_decode($value, ENT_QUOTES));
				$value 	  = @eval($value);
			}

			if ($condition == 'regexp') {
				$condition = 'REGEXP';
				$value = $db->quote($value);
			}
			else if ($condition == 'like') {
				$condition = 'LIKE';
				$value = $db->Quote($value);
			}else if ($condition == 'laterthisyear' || $condition == 'earlierthisyear') {
				$value = $db->Quote($value);
			}
			if ($fullWordsOnly == '1') {
				$condition = 'REGEXP';
			}

			$originalValue = $this->filters['value'][$i];
			list($value, $condition) = $elementModel->getFilterValue($value, $condition, $eval);

			if ($fullWordsOnly == '1') {
				if (is_array($value)) {
					foreach ($value as &$v) {
						$v = "\"[[:<:]]" . $v . "[[:>:]]\"";
					}
				} else {
					$value = "\"[[:<:]]" . $value . "[[:>:]]\"";
				}
			}
			if (!array_key_exists($i, $sqlCond) || $sqlCond[$i] == '') {
				$query = $elementModel->getFilterQuery($key, $condition, $value, $originalValue, $this->filters['search_type'][$i]);
				$this->filters['sqlCond'][$i] = $query;
			}
			$this->filters['condition'][$i] = $condition;
			$this->filters['origvalue'][$i] = $originalValue; //used when getting the selected dropdown filter value
			$this->filters['value'][$i] = $value;
			if (!array_key_exists($i, $noFiltersSetup)) {
				$this->filters['no-filter-setup'][$i] = 0;
			}

			if ($this->filters['no-filter-setup'][$i] == 1) {
				$tmpName = $elementModel->getFullName(false, true, false);
				$tmpData = array($tmpName => $originalValue, $tmpName . "_raw" => $originalValue);

				//set defaults to null to ensure we get correct value for 2nd dropdown search value (mutli dropdown from search form)
				$elementModel->defaults = null;
				if (array_key_exists($key, $readOnlyValues)) {
					$readOnlyValues[$key][] = $elementModel->_getROElement($tmpData);
				} else {
					$readOnlyValues[$key] = array($elementModel->_getROElement($tmpData));
				}
				//set it back to null again so that in form view we dont return this value.
				$elementModel->defaults = null;
				// filter value assinged in readOnlyValues foreach loop towards end of this function
				$this->filters['filter'][$i] = '';

			} else {
				//$$$rob not sure $value is the right var to put in here - or if its acutally used
				// but without this line you get warnings about missing variable in the filter array
				$this->filters['filter'][$i] = $value;
			}
		}
		FabrikHelperHTML::debug($this->filters, 'end filters');
		foreach ($readOnlyValues as $key => $val) {
			foreach ($this->filters['key'] as $i => $fkey) {
				if ($fkey === $key) {
					$this->filters['filter'][$i] = implode("<br>", $val);
				}
			}
		}
		$this->getPluginManager()->runPlugins('onFiltersGot', $this, 'table');
		FabrikHelperHTML::debug($this->filters, 'after plugins:onFiltersGot');
		return $this->filters;
	}

	/**
	 * creates array of prefilters
	 * @param array filters
	 * @return array prefilters combinde with filters
	 */

	function getPrefilterArray(&$filters)
	{

		if (!isset($this->prefilters)) {
			$params =& $this->getParams();
			$elements =& $this->getElements('filtername');
			$afilterJoins 			= $params->get('filter-join', '', '_default', 'array');
			$afilterFields 			= $params->get('filter-fields', '', '_default', 'array');
			$afilterConditions 	= $params->get('filter-conditions', '', '_default', 'array');
			$afilterValues 			= $params->get('filter-value', '', '_default', 'array');
			$afilterAccess 			= $params->get('filter-access', '', '_default', 'array');
			$afilterEval	 			= $params->get('filter-eval', '', '_default', 'array');
			$afilterGrouped	 		= $params->get('filter-grouped', '', '_default', 'array');
			$join = 'WHERE';
			$w = new FabrikWorker();
			for ($i=0; $i < count($afilterFields); $i++) {
				if (!array_key_exists(0, $afilterJoins) || $afilterJoins[0] == '') {
					$afilterJoins[0] = 'AND';
				}
				$join 	  = $afilterJoins[$i];

				if (trim(strtolower($join)) == 'where') {
					$join = "AND";
				}
				$filter 	  	= $afilterFields[$i];
				$condition 		= $afilterConditions[$i];
				$selValue 	  = JArrayHelper::getValue($afilterValues, $i, '');
				$filterEval 	= JArrayHelper::getValue($afilterEval, $i, false);
				$filterGrouped = $afilterGrouped[$i];

				/* $$$ hugh - moved eval to _prefilterParse, after plceholder and prefilter query processing
				 if ($filterEval == '1') {
					$selValue 	= stripslashes(htmlspecialchars_decode($selValue, ENT_QUOTES));
					$selValue 	  = @eval($selValue);
					}
					*/

				$selAccess 	  = $afilterAccess[$i];
				if (!$this->mustApplyFilter($selAccess, $i)) {
					continue;
				}
				$tmpfilter = (strstr($filter, '_raw`')) ? FabrikString::rtrimword( $filter, '_raw`').'`' : $filter;
				$elementModel =& JArrayHelper::getValue($elements, FabrikString::safeColName($tmpfilter), false);
				if ($elementModel === false) {
					JError::raiseNotice(500, 'A prefilter has been set up on an unpublished element, and will not be applied:' . FabrikString::safeColName($tmpfilter));
					continue;
				}
				$filters['join'][] = $join;
				$filters['search_type'][] = 'prefilter';
				$filters['key'][] = $filter;
				$filters['value'][] = $selValue;
				$filters['origvalue'][] = $selValue;
				$filters['sqlCond'][] = '';
				$filters['no-filter-setup'][] = null;
				$filters['condition'][] = $condition;
				$filters['grouped_to_previous'][] = $filterGrouped;
				$filters['eval'][] = $filterEval;
				$filters['match'][] = ($condition == 'equals') ? 1 : 0;
				$filters['full_words_only'][] = 0;

				$filters['label'][] = '';
				$filters['access'][] = '';
				$filters['key2'][] = '';
				$filters['required'][] = 0;
				$filters['hidden'][] = false;
				$filters['elementid'][] = $elementModel !== false ? $elementModel->getElement()->id : 0;
				$this->prefilters = true;
			}
		}
		FabrikHelperHTML::debug($filters, 'prefilters');
	}

	/**
	 * get the total number of records in the table
	 * @return int total number of records
	 */

	function getTotalRecords()
	{
		// $$$ rob ensure that the limits are set - otherwise can create monster query
		$this->setLimits();
		$session =& JFactory::getSession();
		$context = 'com_fabrik.table'. $this->_id.'.total';
		if (isset($this->totalRecords)) {
			$session->set($context, $this->totalRecords);
			return $this->totalRecords;
		}
		// $$$ rob getData() should always be run first
		if (is_null($this->_data)) {
			$this->getData();
			return $this->totalRecords;
		}
		if ($this->mergeJoinedData()) {
			$this->totalRecords = $this->getJoinMergeTotalRecords();
			$session->set($context, $this->totalRecords);
			return $this->totalRecords;
		}
	}

	/**
	 * modified version of getTotalRecords() for use when the table join data
	 * is to be merged on the main table's primary key
	 * @return int total records
	 */

	protected function getJoinMergeTotalRecords()
	{
		$db =& $this->getDb();
		$table = $this->getTable();
		$count = "DISTINCT " .$table->db_primary_key;
		$totalSql  	= "SELECT COUNT(" . $count . ") AS t FROM " . $db->nameQuote($table->db_table_name) . " " . $this->_buildQueryJoin();
		$totalSql 	.= " " . $this->_buildQueryWhere(JRequest::getVar('incfilters', 1));
		$totalSql 	.= " " . $this->_buildQueryGroupBy();
		$totalSql 	= $this->pluginQuery($totalSql);
		$db->setQuery($totalSql);
		FabrikHelperHTML::debug($db->getQuery(), 'table getJoinMergeTotalRecords');
		$total  	= $db->loadResult();
		return $total;
	}

	/**
	 * load in the elements for the table's form
	 * If no form loaded for the table object then one is loaded
	 * @return array element objects
	 */

	function &getFormGroupElementData()
	{
		return $this->getForm()->getGroupsHiarachy();
	}

	/**
	 * require the correct pagenav class based on template
	 *
	 * @param int total
	 * @param int start
	 * @param int length
	 * @return object pageNav
	 */

	function &getPagination($total = 0, $limitstart = 0, $limit = 0)
	{
		if (!isset($this->nav)) {
			if ($this->_randomRecords) {
				$limitstart = $this->getRandomLimitStart();
			}
			$params =& $this->getParams();
			$this->nav = new FPagination($total, $limitstart, $limit);
			// $$$ rob set the nav link urls to the table action to avoid messed up url links when
			// doing ranged filters via the querystring
			$this->nav->url = $this->getTableAction();
			$this->nav->showAllOption = $params->get('showall-records', false);
			$this->nav->setId($this->_id);
			$this->nav->_postMethod = $this->getPostMethod();
			$this->nav->showTotal = $params->get('show-total', false);
		}
		return $this->nav;
	}

	/**
	 * get the random lmit start val
	 * @return int limit start
	 */

	protected function getRandomLimitStart()
	{
		if (isset($this->randomLimitStart)) {
			return $this->randomLimitStart;
		}
		$db =& $this->getDb();
		$table =& $this->getTable();
		// $$$ rob @todo - do we need to add the join in here as well?
		// added + 1 as with 4 records to show 3 4th was not shown
		$db->setQuery("SELECT FLOOR(RAND() * COUNT(*) + 1) AS ".$db->nameQuote('offset')." FROM ". $db->nameQuote($table->db_table_name) . " " . $this->_buildQueryWhere());
		$limitstart = $db->loadResult();
		//$$$ rob 11/01/2011 cant do this as we dont know what the total is yet
		//$$$ rob ensure that the limitstart + limit isn't greater than the total
		/*if ($limitstart + $limit > $total) {
		$limitstart = $total - $limit;
		}*/
		// $$$ rob 25/02/2011 if you only have say 3 reocrds then above random will show 1 2 or 3 records
		// so decrease the random start num by the table row dispaly num
		// going to favour records at the beginning of the table though
		$limitstart -= $table->rows_per_page;
		if ($limitstart < 0) { $limitstart = 0;}
		$this->randomLimitStart = $limitstart;
		return $limitstart;
	}

	/**
	 * used to determine which filter action to use
	 *if a filter is a range then override tables setting with onsubmit
	 */

	function getFilterAction()
	{
		if (!isset($this->_real_filter_action)) {
			$form =& $this->getForm();
			$table =& $this->getTable();
			$this->_real_filter_action = $table->filter_action;
			$groups =& $form->getGroupsHiarachy();
			foreach ($groups as $groupModel) {
				$elementModels =& $groupModel->getPublishedElements();
				foreach ($elementModels as $elementModel) {
					$element =& $elementModel->getElement();
					if (isset($element->filter_type) && $element->filter_type <> '') {
						if ($elementModel->canView() && $elementModel->canUseFilter() && $element->show_in_table_summary == '1') {
							if ($element->filter_type == 'range' || $element->filter_type == 'auto-complete') {
								$this->_real_filter_action = 'submitform';
								return $this->_real_filter_action;
							}
						}
					}
				}
			}
		}
		return $this->_real_filter_action;
	}

	/**
	 * gets the part of a url to describe the key that the link links to
	 * if a table this is rowid=x
	 * if a view this is view_primary_key={where statement}
	 *
	 * @param object row $data
	 * @return string
	 */

	function getKeyIndetifier($data)
	{
		return "&rowid=". $this->getSlug($data);
	}

	/**
	 * format the row id slug
	 * @param object $row data
	 * @return string formatted slug
	 */

	protected function getSlug($row)
	{
		return empty($row->slug) ? '' : str_replace(' ', '-', $row->slug);
	}

	/**
	 * *get detailed info on each of the tables fields
	 *
	 * @return unknown
	 */

	function _fetchFields()
	{
		$table =& $this->getTable();
		$db =& $this->getDb();
		$db->setQuery("SELECT * FROM ".$db->nameQuote($table->db_table_name)." LIMIT 1");
		if (!($result = $db->query())) {
			return null;
		}
		$fields       = array();
		$num_fields   = mysql_num_fields($result);
		for ($i = 0; $i < $num_fields; $i++) {
			$fields[] = mysql_fetch_field($result, $i);
		}
		return $fields;
	}

	/**
	 * @return array of element objects that are database joins and that
	 * use this table's key as their foregin key
	 */

	function getJoinsToThisKey()
	{
		if (is_null($this->_joinsToThisKey)) {
			$this->_joinsToThisKey = array();
			$db =& JFactory::getDBO();
			$table =& $this->getTable();
			if ($table->id == 0) {
				$this->_joinsToThisKey = array();
			} else {
				$usersConfig = &JComponentHelper::getParams('com_fabrik');
				$sql = "SELECT db_table_name,
				name, plugin,
				t.label AS tablelabel, t.id as table_id, \n" .
				"el.id AS element_id, el.label AS element_label, el.state AS element_state, f.id AS form_id FROM #__fabrik_elements AS el \n" .
	  		"LEFT JOIN #__fabrik_formgroup AS fg \n" .
				"ON fg.group_id = el.group_id \n" .
				"LEFT JOIN #__fabrik_forms AS f \n" .
				"ON f.id = fg.form_id  \n" .
				"LEFT JOIN #__fabrik_tables AS t \n" .
				"ON t.form_id = f.id \n" .
	  		"WHERE \n".
				" el.state = 1 AND (".
	  		" (plugin = 'fabrikDatabasejoin' AND \n" .
	  		" el.attribs like '%join_db_name=".$table->db_table_name."%' \n" .
	  		"AND el.attribs like  '%join_conn_id=".$table->connection_id."%') ";

				//load in cdd elements
				$sql .= " OR (plugin = 'fabrikcascadingdropdown' AND \n" .
	  		" el.attribs like '%cascadingdropdown_table=".$table->id."%' \n" .
	  		"AND el.attribs like  '%cascadingdropdown_connection=".$table->connection_id."%') ";

				// load in user element links as well
				//$$$rob - not convinced this is a good idea
				if ($usersConfig->get('user_elements_as_related_data', false) == true) {
					$sql .= " OR (plugin = 'fabrikuser' AND
				 	el.attribs like  '%join_conn_id=".$table->connection_id."%')";
				}
				$sql .= ")";
				$db->setQuery($sql);
				$this->_joinsToThisKey = $db->loadObjectList();
			}
		}
		return $this->_joinsToThisKey;
	}

	/**
	 * get an array of elements that point to a form where their data will be filtered
	 * @return array
	 */

	function getLinksToThisKey()
	{
		if (!is_null($this->aJoinsToThisKey)) {
			return $this->aJoinsToThisKey;
		}
		$params =& $this->getParams();
		$aExisitngLinked = $params->get('linkedform', '', '_default', 'array');
		$aAllJoinsToThisKey = $this->getJoinsToThisKey();
		$this->aJoinsToThisKey= array();
		foreach ($aAllJoinsToThisKey as $join) {
			$key = "{$join->table_id}-{$join->form_id}-{$join->element_id}";
			if (in_array($key, $aExisitngLinked)) {
				$this->aJoinsToThisKey[] = $join;
			}else{
				// $$$ rob required for releated form links. otherwise links for forms not listed first in the admin options wherent being rendered
				$this->aJoinsToThisKey[] = false;
			}
		}
		return $this->aJoinsToThisKey;
	}

	public function getEmptyDataMsg()
	{
		if (isset($this->emptyMsg)){
			return $this->emptyMsg;
		}
		$params =& $this->getParams();
		return $params->get('empty_data_msg');
	}

	/**
	 * have all the required filters been met?
	 *
	 * @return bol true if they have if false we shouldnt show the table data
	 */

	function getRequiredFiltersFound()
	{
		if (isset($this->requiredFilterFound)) {
			return $this->requiredFilterFound;
		}
		$filters 	=& $this->getFilterArray();
		$elements =& $this->getElements();

		$required = array();

		foreach ($elements as $kk => $val2) {
			$elementModel = $elements[$kk]; //dont do with =& as this foobars up the last elementModel
			$element =& $elementModel->getElement();
			if ($element->filter_type <> '' && $element->filter_type != 'null') {
				if ($elementModel->canView() && $elementModel->canUseFilter()) {
					//force the correct group model into the element model to ensure no wierdness in getting the element name
					if ($elementModel->getParams()->get('filter_required') == 1) {
						$name = FabrikString::safeColName($elementModel->getFullName(false, false, false));
						if (array_key_exists('key', $filters) && is_array($filters['key'])) {
							reset($filters['key']);
							$found = false;
							while (list($key, $val) = each($filters['key'])) {
								if ($val == $name) {
									$found = true;
									break;
								}
							}
							if (!$found || $filters['origvalue'][$key] == '') {
								$this->emptyMsg = JText::_('PLEASE_SELECT_ALL_REQUIRED_FILTERS');
								return false;
							}
						}
						else {
							// $$$ hugh ... if $filters doesn't exist, then by definition
							// the required filter isn't there?
							if (empty($filters)) {
								$this->emptyMsg = JText::_('PLEASE_SELECT_ALL_REQUIRED_FILTERS');
								return false;
							}
						}
					}
				}
			}
		}
		return true;
	}

	/**
	 *
	 * @param string $container
	 * @param string $type
	 * @return array filters
	 */

	function getFilters($container = 'tableform_1', $type = 'table', $id = '')
	{
		if (!isset($this->viewfilters)) {
			global $_PROFILER;
			$params 	=& $this->getParams();
			$this->viewfilters = array();

			JDEBUG ? $_PROFILER->mark('fabrik makeFilters start') : null;
			$modelFilters =& $this->makeFilters($container, $type, $id);
			JDEBUG ? $_PROFILER->mark('fabrik makeFilters end') : null;
			foreach ($modelFilters as $name => $filter) {
				$f 					= new stdClass();
				$f->label 	= $filter->label;
				$f->element = $filter->filter;
				$f->required = array_key_exists('required', $filter) ? $filter->required : '';
				$this->viewfilters[$filter->name] 	= $f;
			}
			$this->getPluginManager()->runPlugins('onMakeFilters', $this, 'table');
			// moved advanced filters to table settings
			if ($params->get('advanced-filter', '0')) {
				$f = new stdClass();
				$f->element = $this->getAdvancedSearchLink();
				$f->label = '';
				$f->required = '';
				$this->viewfilters['fabrik_advanced_search'] 	= $f;
			}
		}

		return $this->viewfilters;
	}

	/**
	 * creates an array of html code for each filter
	 * Also adds in JS code to manage filters
	 * @param string $container
	 * @param string $type
	 * @return array of html code for each filter
	 */

	function &makeFilters($container = 'tableform_1', $type = 'table', $id = '')
	{
		$aFilters = array();
		$document =& JFactory::getDocument();
		$table =& $this->getTable();
		// $$$ rob js should be loaded in view if required
		//FabrikHelperHTML::script('table.js', 'media/com_fabrik/js/');
		$opts = new stdClass();
		$opts->container = $container;
		$opts->type = $type;
		$opts->id = $type === 'table' ? $this->_id : $id; //only used in tables
		$opts = FastJSON::encode($opts);
		$fscript = "window.addEvent('domready', function() {\nfilter_{$container} = new TableFilter($opts);\n";

		$app =& JFactory::getApplication();
		$filters =& $this->getFilterArray();

		$params =& $this->getParams();
		if ($params->get('search-mode', 'AND') == 'OR') {
			//test new option to have one field to search them all
			$key = 'com_fabrik.table'. $table->id.'.searchall';

			$v = $app->getUserStateFromRequest($key, 'fabrik_table_filter_all');
			if (trim($v) == '') {
				$fromFormId = $app->getUserState('com_fabrik.searchform.fromForm');
				if ($fromFormId != $this->getForm()->_id) {
					$v = $app->getUserState('com_fabrik.searchform.form'.$fromFormId.'.searchall');
				}
			}
			$v = htmlspecialchars($v, ENT_QUOTES);
			$o = new stdClass();
			$o->filter = "<input size=\"20\" value=\"$v\" class=\"fabrik_filter\" name=\"fabrik_table_filter_all\" />";
			if ($params->get('search-mode-advanced') == 1) {
				$opts = array();
				$opts[] = JHTML::_('select.option', 'all', JText::_('ALL_OF_THESE_TERMS'));
				$opts[] = JHTML::_('select.option', 'any', JText::_('ANY_OF_THESE_TERMS'));
				$opts[] = JHTML::_('select.option', 'exact', JText::_('EXACT_TERMS'));
				$opts[] = JHTML::_('select.option', 'none', JText::_('NONE_OF_THESE_TERMS'));
				$mode = $app->getUserStateFromRequest( 'com_fabrik.table'. $table->id.'.searchallmode', 'search-mode-advanced');
				$o->filter .= '&nbsp;'.JHTML::_('select.genericList', $opts, 'search-mode-advanced', "class='fabrik_filter'", 'value', 'text', $mode);
			}
			$o->name = 'all';
			$o->label = $params->get('search-all-label', JText::_('ALL'));
			$aFilters[] = $o;
		}
		$counter = 0;
		// $$$ hugh - another one of those weird ones where if we use =& the foreach loop
		// will sometimes skip a group
		//$groups =& $this->getFormGroupElementData();
		$groups = $this->getFormGroupElementData();
		foreach ($groups as &$groupModel) {
			$g =& $groupModel->getGroup();
			$elementModels = null;
			$elementModels =& $groupModel->getPublishedElements();

			/*foreach ($elementModels as $kk => $val2) {
			 $elementModel = $elementModels[$kk]; //dont do with =& as this foobars up the last elementModel*/
			// $$$ rob changed from above 2 lines to 2 below as I think the form_id test inside here fixes things???
			foreach ($elementModels as &$elementModel) {
				$element =& $elementModel->getElement();

				//$$ rob added as some filter_types were null, have to double check that this doesnt
				// mess with showing the readonly values from search forms
				if (isset($element->filter_type) && $element->filter_type <> '' && $element->filter_type != 'null') {
					if ($elementModel->canView() && $elementModel->canUseFilter()) {

						// $$$ rob in facted browsing somehow (not sure how!) some elements from the facted table get inserted into elementModels
						// with their form id set - so test if its been set and if its not the same as the current form id
						// if so then ignore
						if (isset($element->form_id) && (int)$element->form_id !== 0 && $element->form_id !== $this->getForm()->_id) {
							continue;
						}
						//force the correct group model into the element model to ensure no wierdness in getting the element name
						$elementModel->_group =& $groupModel;
						$o = new stdClass();
						$o->name = $elementModel->getFullName(false, true, false);
						//global $_PROFILER;
						//JDEBUG ? $_PROFILER->mark('About to getFilter for:' . $o->name) : null;
						$o->filter = $elementModel->getFilter($counter, true);
						$fscript .= $elementModel->_filterJS( true, $container);
						$o->required = $elementModel->getParams()->get('filter_required');
						$o->label = $elementModel->getParams()->get('element_alt_table_heading') == '' ? $element->label : $elementModel->getParams()->get('element_alt_table_heading');
						$aFilters[] = $o;
						$counter ++;
					}
				}
			}
		}
		$fscript .= "filter_{$container}.update();\n";
		$fscript .= "});";
		FabrikHelperHTML::addScriptDeclaration($fscript);
		//check for search form filters - if they exists create hidden elements for them
		$keys = JArrayHelper::getValue($filters, 'key', array());

		foreach ($keys as $i => $key) {
			if ($filters['no-filter-setup'][$i] == '1' && !in_array($filters['search_type'][$i], array('searchall', 'advanced'))) {
				$o = new stdClass();
				// $$$ rob - we are now setting read only filters 'filter' var to the elements read only
				// label for the passed in filter value
				//$o->filter = $value;
				$o->filter = $filters['filter'][$i];
				$o->name = $filters['key'][$i];
				$o->label = $filters['label'][$i];
				$aFilters[] = $o;
			}
		}
		return $aFilters;
	}

	/**
	 * build the advanced search link
	 * @return string <a href...> link
	 */

	function getAdvancedSearchLink()
	{
		FabrikHelperHTML::mocha( 'a.popupwin');
		$table =& $this->getTable();
		$url = COM_FABRIK_LIVESITE . "index.php?option=com_fabrik&amp;view=table&amp;layout=_advancedsearch&amp;tmpl=component&amp;tableid=".$table->id."&amp;nextview=".JRequest::getVar('view')."&amp;nextcontroller=".JRequest::getVar('controller', 'table');
		$opts = new stdClass();
		$opts->id = 'advanced-search-win';
		$opts->width = 690;
		$opts->loadMethod = 'xhr';
		$opts->title = JText::_('ADVANCED SEARCH');
		$otps->maximizable = 1;
		$opts = FastJSON::encode($opts);
		// $$$ rob dont use double quotes around $opts
		return "<a rel='$opts' href=\"$url\" class=\"popupwin\">". JText::_('ADVANCED SEARCH') ."</a>";
	}

	/**
	 * called from index.php?option=com_fabrik&view=table&layout=_advancedsearch&tmpl=component&tableid=4
	 * advanced serach popup view
	 * @return unknown_type
	 */

	function getAdvancedSearchOpts()
	{
		$table =& $this->getTable();
		$document =& JFactory::getDocument();
		$opts = new stdClass();
		$opts->conditionList = FabrikHelperHTML::conditonList($this->_id, '');
		list($fieldNames, $firstFilter) = $this->getAdvancedSearchElementList();
		$statements = $this->getStatementsOpts();
		$opts->elementList = JHTML::_('select.genericlist', $fieldNames, 'fabrik___filter[table_'.$this->_id.'][key][]', "class=\"inputbox key\" size=\"1\" ",'value', 'text');
		$opts->statementList = JHTML::_('select.genericlist', $statements, 'fabrik___filter[table_'.$this->_id.'][condition][]', "class=\"inputbox\" size=\"1\" ",'value', 'text');
		$opts->tableid = $table->id;
		$opts->liveSite = COM_FABRIK_LIVESITE;
		$opts->counter = count($this->getadvancedSearchRows()) - 1;
		$elements =& $this->getElements();
		$arr = array();
		foreach ($elements as $e) {
			$key = FabrikString::safeColName($e->getFullName(false, false, false));
			$arr[$key] = array('id'=>$e->_id, 'plugin'=>$e->getElement()->plugin);
		}
		$opts->elementMap = $arr;
		$opts = FastJSON::encode($opts);
		// $$$rob window.domready no good for Google chrome
		//- need to add an additional delay before creating search object
		$script = "
		window.addEvent('domready', function(){
     document.mochaSearch = new MochaSearch($opts);
		(function(){
      document.mochaSearch.ini();
			}).delay(750);
	  });";

		FabrikHelperHTML::addScriptDeclaration($script);
		return $script;
	}

	private function getAdvancedSearchElementList()
	{
		$first = false;
		$fieldNames[] = JHTML::_('select.option', '', JText::_('PLEASE SELECT'));
		$elementModels =& $this->getElements();
		foreach ($elementModels as $elementModel) {
			$element =& $elementModel->getElement();
			$elParams =& $elementModel->getParams();
			if ($elParams->get('inc_in_adv_search', 1)) {
				$elName = FabrikString::safeColName($elementModel->getFullName(false, false, false));
				if (!$first) {
					$first = true;
					$firstFilter =& $elementModel->getFilter(0, false);
				}
				$fieldNames[] = JHTML::_('select.option', $elName, $element->label);
			}
		}
		return array($fieldNames, $firstFilter);
	}

	/**
	 * get a list of advanced search options
	 * @return array of JHTML options
	 */

	private function getStatementsOpts()
	{
		$statements = array();
		$statements[] = JHTML::_('select.option', '=', JText::_('EQUALS'));
		$statements[] = JHTML::_('select.option', '<>', JText::_('NOT_EQUALS'));
		$statements[] = JHTML::_('select.option', 'BEGINS WITH', JText::_('BEGINS_WITH'));
		$statements[] = JHTML::_('select.option', 'CONTAINS', JText::_('CONTAINS'));
		$statements[] = JHTML::_('select.option', 'ENDS WITH', JText::_('ENDS_WITH'));
		$statements[] = JHTML::_('select.option', '>', JText::_('GREATER_THAN'));
		$statements[] = JHTML::_('select.option', '<', JText::_('LESS_THAN'));
		return $statements;
	}

	/**
	 * get a list of submitted advanced filters
	 * @return array advanced filter values
	 */

	private function getAdvancedFilterValues()
	{
		$filters =& $this->getFilterArray();
		$advanced = array();
		for ($i = 0; $i < count(JArrayHelper::getValue($filters, 'key', array())); $i++) {
			if ($filters['search_type'][$i] == 'advanced') {
				$tmp = array();
				foreach (array_keys($filters ) as $k) {
					if (array_key_exists($k, $advanced)) {
						$advanced[$k][] = ($filters[$k][$i]);
					} else {
						$advanced[$k] =  array_key_exists($i, $filters[$k]) ? array(($filters[$k][$i])) : '';
					}
				}
			}
		}
		return $advanced;
	}
	/**
	 * build an array of html data that gets inserted into the advanced search popup view
	 * @return array html lists/fields
	 */

	function getAdvancedSearchRows()
	{
		if (isset($this->advancedSearchRows)) {
			return $this->advancedSearchRows;
		}
		$statements = $this->getStatementsOpts();
		$rows = array();

		$first = false;
		$elementModels =& $this->getElements();

		list($fieldNames, $firstFilter )  = $this->getAdvancedSearchElementList();
		$type 		= "<input type=\"hidden\" name=\"fabrik___filter[table_{$this->_id}][search_type][]\" value=\"advanced\" />";
		$grouped 	= "<input type=\"hidden\" name=\"fabrik___filter[table_{$this->_id}][grouped_to_previous][]\" value=\"0\" />";

		$filters =& $this->getAdvancedFilterValues();
		$counter = 0;
		if (array_key_exists('key', $filters)) {
			foreach ($filters['key'] as $key) {

				foreach ($elementModels as $elementModel) {
					$testkey = FabrikString::safeColName($elementModel->getFullName(false, false, false));
					if ($testkey == $key) {
						break;
					}
				}
				$join  = $filters['join'][$counter];

				$condition = $filters['condition'][$counter];
				$value 		= $filters['origvalue'][$counter];
				$v2 = $filters['value'][$counter];
				switch( $condition )
				{
					case "<>":
						$jsSel = '<>';
						break;
					case "=":
						$jsSel = 'EQUALS';
						break;
					case "<":
						$jsSel = '<';
						break;
					case ">":
						$jsSel = '>';
						break;
					default:
						$firstChar = substr($v2, 1, 1);
						$lastChar = substr($v2, -2, 1);
						switch( $firstChar )
						{
							case "%":
								$jsSel =( $lastChar == "%")? 'CONTAINS' : $jsSel = 'ENDS WITH';
								break;
							default:
								if ($lastChar == "%") {
									$jsSel = 'BEGINS WITH';
								}
								break;
						}
						break;
				}

				$value = trim(trim($value, '"'), "%");
				if ($counter == 0) {
					$join = JText::_('WHERE') . "<input type=\"hidden\" value=\"WHERE\" name=\"fabrik___filter[table_{$this->_id}][join][]\" />";
				} else {
					$join = FabrikHelperHTML::conditonList($this->_id, $join);
				}

				$lineElname = FabrikString::safeColName($elementModel->getFullName(false, true, false));
				$orig = JRequest::getVar($lineElname);
				JRequest::setVar($lineElname, array('value' => $value));
				$filter = & $elementModel->getFilter($counter, false);
				JRequest::setVar($lineElname, $orig);
				$key = JHTML::_('select.genericlist', $fieldNames, 'fabrik___filter[table_'.$this->_id.'][key][]', "class=\"inputbox key\" size=\"1\" ",'value', 'text', $key);
				$jsSel = JHTML::_('select.genericlist', $statements, 'fabrik___filter[table_'.$this->_id.'][condition][]', "class=\"inputbox\" size=\"1\" ",'value', 'text', $jsSel);
				$rows[] = array('join' => $join, 'element' => $key, 'condition' => $jsSel, 'filter' => $filter, 'type' => $type, 'grouped' => $grouped);

				$counter ++;
			}
		}

		if ($counter == 0) {
			$join = JText::_('WHERE') . "<input type=\"hidden\" name=\"fabrik___filter[table_{$this->_id}][join][]\" value=\"WHERE\" />";
			$key = JHTML::_('select.genericlist', $fieldNames, 'fabrik___filter[table_'.$this->_id.'][key][]', "class=\"inputbox key\" size=\"1\" ",'value', 'text', '');
			$jsSel = JHTML::_('select.genericlist', $statements, 'fabrik___filter[table_'.$this->_id.'][condition][]', "class=\"inputbox\" size=\"1\" ",'value', 'text', '');
			$rows[] = array('join' => $join, 'element' => $key, 'condition' => $jsSel, 'filter' => $firstFilter, 'type' => $type, 'grouped' => $grouped);
		}
		$this->advancedSearchRows =& $rows;
		return $rows;
	}

	/**
	 * set the headings that should be shown in the csv export file
	 * @param unknown_type $headings
	 */

	function setHeadingsForCSV($headings)
	{
		$asfields =& $this->getAsFields();
		$newfields = array();
		$this->_temp_db_key_addded = false;
		// $$$ rob if no fields specified presume we are requesting CSV file from URL and return
		// all fields otherwise set the fields to be those selected in mocha window.
		if (!empty($headings)) {
			foreach ($headings as $name => $val) {
				foreach ($asfields as $f) {
					if ((strstr($f, "`$name`") || strstr($f, "`{$name}_raw`")) && $val == 1) {
						$newfields[] = $f;
					}
				}
			}
			$this->asfields =& $newfields;
		}
	}

	/**
	 * returns the table headings, seperated from writetable function as
	 * when group_by is selected mutliple tables are written
	 * @return array (table headings, array columns, $aLinkElements)
	 */

	function getHeadings()
	{
		$table =& $this->getTable();
		$table->order_dir = strtolower($table->order_dir);
		$aTableHeadings 		= array();
		$headingClass				= array();
		$cellClass					= array();
		$params =& $this->getParams();

		$w = new FabrikWorker();
		$session =& JFactory::getSession();
		$formModel =& $this->getForm();
		$linksToForms =  $this->getLinksToThisKey();
		$groups =& $formModel->getGroupsHiarachy();
		$groupHeadings = array();

		$orderbys = explode(GROUPSPLITTER2, $table->order_by);

		foreach ($groups as $groupModel) {
			$groupHeadingKey = $w->parseMessageForPlaceHolder($groupModel->getGroup()->label, array(), false);
			$groupHeadings[$groupHeadingKey] = 0;
			$elementModels =& $groupModel->getPublishedTableElements();
			foreach ($elementModels as $key => $elementModel) {
				$viewLinkAdded = false;
				$element =& $elementModel->getElement();

				$groupHeadings[$groupHeadingKey] ++;
				$key 		= $elementModel->getFullName(false, true, false);
				$orderKey 	= $elementModel->getOrderbyFullName(false, false);
				$elementParams =& $elementModel->getParams();
				$label = $elementParams->get('element_alt_table_heading');
				if ($label == '') {
					$label = $element->label;
				}
				$label = $w->parseMessageForPlaceHolder($label, array());

				if ($element->can_order == '1' && $this->_outPutFormat != 'csv') {
					//@TDO this shouldnt be using the str_replace i think? /// $$$ rob
					//$context			= 'com_fabrik.table' . $this->_id . '.order.' . str_replace('.', '___', $elementModel->getOrderByName());
					$context			= 'com_fabrik.table' . $this->_id . '.order.' . $element->id;
					$orderDir			= $session->get($context);
					$class 		= "";
					$currentOrderDir = $orderDir;
					switch ($orderDir) {
						case "desc":
							$orderDir = "-";
							$class = "class=\"fabrikorder-desc\"";
							break;
						case "asc":
							$orderDir = "desc";
							$class = "class=\"fabrikorder-asc\"";
							break;
						case "":
						case "-":
							$orderDir = "asc";
							$class = "class=\"fabrikorder\"";
							break;
					}

					if ($class === '') {
						if (in_array($key, $orderbys)) {
							if ($table->order_dir === 'desc') {
								$class = "class=\"fabrikorder-desc\"";
							}
						}
					}

					$heading = "<a $class href=\"#\">$label</a>";
				} else {
					$heading = $label;
				}
				$aTableHeadings[$key] = $heading;

				/// $$$rob apply custom heading css
				//$headingClass[$key] = "class='{$key}_heading " . $elementParams->get('tablecss_header_class')."'";
				//key heading depreciated use {element->id}_order instead
				$headingClass[$key] = "class=\"fabrik_ordercell {$key}_heading {$element->id}_order" . $elementParams->get('tablecss_header_class')."\"";

				$css = $elementParams->get('tablecss_header');
				if ($css != '') {
					$headingClass[$key] .= " style=\"$css\"";
				}

				$cellClass[$key] = "class=\"fabrik_row___{$key} fabrik_element " . $elementParams->get('tablecss_cell_class')."\"";
				$css = $elementParams->get('tablecss_cell');
				if ($css != '') {
					$cellClass[$key] .= " style=\"$css\"";
				}

			}
			if ($groupHeadings[$groupHeadingKey] == 0) {
				unset ($groupHeadings[$groupHeadingKey]);
			}
		}
		if (!in_array($this->_outPutFormat, array('pdf','csv'))) {
			//@TODO check if any plugins need to use the selector as well!

			if ($this->canSelectRows()) {
				$select = '<input type="checkbox" name="checkAll" class="table_' . $this->_id . '_checkAll" />';
				$aTableHeadings['fabrik_delete'] = $select;
				$headingClass['fabrik_delete'] = 'class="fabrik_delete"';
				$cellClass['fabrik_delete'] = 'class="fabrik_row___fabrik_delete"'; //needed for ajax filter/nav
			}
			$viewLinkAdded = false;
			//if no elements linking to the edit form add in a edit column (only if we have the right to edit/view of course!)

			$viewLabel = $params->get('detaillabel', JText::_('VIEW'));
			if ($this->canView() || $this->canEdit()) {
				if ($this->canEdit() || $this->canEditARow()) {
					if ($params->get('editlink')) {
						$aTableHeadings['fabrik_edit'] = $params->get('editlabel', JText::_('EDIT'));
						$headingClass['fabrik_edit'] = 'class="fabrik_edit"';
						$cellClass['fabrik_edit'] = 'class="fabrik_row___fabrik_edit"'; //needed for ajax filter/nav
						if ($params->get('detaillink') && $this->canViewDetails()) {
							$aTableHeadings['fabrik_view'] = $viewLabel;
							$headingClass['fabrik_view'] = 'class="fabrik_view"';
							$cellClass['fabrik_view'] = 'class="fabrik_row___fabrik_view"'; //needed for ajax filter/nav
							$viewLinkAdded = true;
						}
					}
				} else {
					if ($params->get('detaillink') && $this->canViewDetails()) {
						$aTableHeadings['fabrik_view'] = $viewLabel;
						$headingClass['fabrik_view'] = 'class="fabrik_view"';
						$cellClass['fabrik_view'] = 'class="fabrik_row___fabrik_view"'; //needed for ajax filter/nav
						$viewLinkAdded = true;
					}
				}
			}

			if ($this->canViewDetails() && $params->get('detaillink') == '1' && !$viewLinkAdded) {
				$aTableHeadings['fabrik_view'] = $viewLabel;
				$headingClass['fabrik_view'] = 'class="fabrik_view"';
				$cellClass['fabrik_view'] = 'class="fabrik_row___fabrik_view"'; //needed for ajax filter/nav
			}

			if ($params->get('merge_edit_details')) {
				if (array_key_exists('fabrik_view', $aTableHeadings) && array_key_exists('fabrik_edit', $aTableHeadings)) {
					//$aTableHeadings['fabrik_edit'] .= $aTableHeadings['fabrik_view'];
					unset($aTableHeadings['fabrik_view']);
					unset($headingClass['fabrik_view']);
					unset($cellClass['fabrik_view']);
				}
			}

			// create columns containing links which point to tables associated with this table
			$aExisitngLinkedTables = $params->get('linkedtable', '', '_default', 'array');
			$aExistingTableHeaders = $params->get('linkedtableheader', '', '_default', 'array');
			$joinsToThisKey = $this->getJoinsToThisKey();
			$f = 0;
			foreach ($joinsToThisKey as $element) {
				if (is_object($element)) {
					$linkedTable 	= JArrayHelper::getValue($aExisitngLinkedTables, $f, false);
					$heading 			= JArrayHelper::getValue($aExistingTableHeaders, $f, false);
					if ($linkedTable != '0') {
						$prefix = $element->element_id."___".$linkedTable;
						$aTableHeadings[$prefix . "_table_heading"] = empty($heading) ? $element->tablelabel . " " . JText::_('TABLE') : $heading;
						$headingClass[$prefix . "_table_heading"] = "class=\"{$prefix}_table_heading related\"";
						$cellClass[$prefix . "_table_heading"] = "class=\"fabrik_row___{$prefix}_table_heading related\"";
					}
				}
				$f ++;
			}

			$aExisitngLinkedForms = $params->get('linkedform', '', '_default', 'array');
			$aExistingFormHeaders = $params->get('linkedformheader', '_default', '', 'array');

			$f = 0;
			foreach ($linksToForms as $element) {
				$linkedForm 	= JArrayHelper::getValue($aExisitngLinkedForms, $f, false);
				if ($linkedForm != '0') {
					$heading = JArrayHelper::getValue($aExistingFormHeaders, $f, false);
					$prefix	= $element->db_table_name . "___" . $element->name;
					$aTableHeadings[$prefix . "_form_heading"] = empty($heading) ? $element->tablelabel . " " . JText::_('FORM') : $heading;
					$headingClass[$prefix . "_form_heading"] = "class=\"{$prefix}_form_heading related\"";
					$cellClass[$prefix . "_form_heading"] = "class=\"fabrik_row___{$prefix}_form_heading related\"";
				}
				$f ++;
			}
		}
		if ($this->canSelectRows()) {
			$groupHeadings[''] = '';
		}
		return array($aTableHeadings, $groupHeadings, $headingClass, $cellClass);
	}

	/**
	 * can the user select the specified row
	 * @param object row
	 * @return bool
	 */

	function canSelectRow($row)
	{
		$canSelect = $this->getPluginManager()->runPlugins('onCanSelectRow', $this, 'table', $row);
		if (in_array(false, $canSelect)) {
			return false;
		}
		if ($this->canDelete($row)) {
			$this->canSelectRows = true;
			return true;
		}
		$params =& $this->getParams();
		$usedPlugins 	= $params->get('plugin', "", "_default", "array");
		if (empty($usedPlugins)) {
			return false;
		}
		$pluginManager =& $this->getPluginManager();
		$tableplugis =& $pluginManager->getPlugInGroup('table');
		$v = in_array(true, $pluginManager->runPlugins('canSelectRows', $this, 'table'));
		if ($v) {
			$this->canSelectRows = true;
		}
		return $v;
	}

	/**
	 * can the user select ANY row?
	 * If you can delete then true returned, if not then check
	 * available table plugins to see if they allow for row selection
	 * if so a checkbox column appears in the table
	 * @return bool
	 */

	function canSelectRows()
	{
		if (!is_null($this->canSelectRows)) {
			return $this->canSelectRows;
		}
		if ($this->canDelete()) {
			$this->canSelectRows = true;
			return $this->canSelectRows;
		}
		$params =& $this->getParams();
		$usedPlugins 	= $params->get('plugin', "", "_default", "array");
		if (empty($usedPlugins)) {
			$this->canSelectRows = false;
			return $this->canSelectRows;
		}
		$pluginManager =& $this->getPluginManager();
		$tableplugis =& $pluginManager->getPlugInGroup('table');
		$this->canSelectRows = in_array(true, $pluginManager->runPlugins('canSelectRows', $this, 'table'));
		return $this->canSelectRows;
	}

	/**
	 * return mathematical column calculations (run at doCalculations() on for submission)
	 */

	function getCalculations()
	{
		if (!empty($this->_aRunCalculations)) {
			return $this->_aRunCalculations;
		}
		$user  = &JFactory::getUser();
		$aCalculations = array();
		$formModel =& $this->getForm();
		$aAvgs = array();
		$aSums = array();
		$aMedians = array();
		$aCounts = array();
		$groups =& $formModel->getGroupsHiarachy();
		foreach ($groups as $groupModel) {
			$elementModels =& $groupModel->getPublishedElements();
			foreach ($elementModels as $elementModel) {
				$params = $elementModel->getParams();
				$elName = $elementModel->getFullName(false, true, false);
				$sum 			= $params->get('sum_on', '0');
				$avg 			= $params->get('avg_on', '0');
				$median 		= $params->get('median_on', '0');
				$countOn 		= $params->get('count_on', '0');

				$sumAccess 		= $params->get('sum_access', 0);
				$avgAccess 		= $params->get('avg_access', 0);
				$medianAccess 	= $params->get('median_access', 0);
				$countAccess 	= $params->get('count_access', 0);

				if ($sumAccess <= $user->get('gid') && $params->get('sum_value', '') != '') {
					$aSums[ $elName ] = $params->get('sum_value', '');
					$ser = $params->get('sum_value_serialized');
					if (is_string($ser)) { //if group gone from repeat to none repeat could be array
						$aSums[ $elName . "_obj" ] = unserialize($ser);
					}
				}

				if ($avgAccess <= $user->get('gid') && $params->get('avg_value', '') != '') {
					$aAvgs[ $elName ] = $params->get('avg_value', '');
					$ser = $params->get('avg_value_serialized');
					if (is_string($ser)) {
						$aAvgs[ $elName . "_obj" ] = unserialize($ser);
					}
				}

				if ($medianAccess <= $user->get('gid') && $params->get('median_value', '') != '') {
					$aMedians[ $elName ] = $params->get('median_value', '');
					$ser = $params->get('median_value_serialized', '');
					if (is_string($ser)) {
						$aMedians[ $elName . "_obj" ] = unserialize($ser);
					}
				}

				if ($countAccess <= $user->get('gid') && $params->get('count_value', '') != '') {
					$aCounts[ $elName ] = $params->get('count_value', '');
					$ser = $params->get('count_value_serialized');
					if (is_string($ser)) {
						$aCounts[ $elName . "_obj" ] = unserialize($ser);
					}
				}
			}
		}
		$aCalculations['sums'] 			= $aSums;
		$aCalculations['avgs'] 			= $aAvgs;
		$aCalculations['medians'] 	= $aMedians;
		$aCalculations['count'] 		= $aCounts;
		$this->_aRunCalculations =& $aCalculations;
		return $aCalculations;
	}

	/**
	 * get table headings to pass into table js oject
	 *
	 * @return string headings tablename___name
	 */

	function _jsonHeadings()
	{
		$aHeadings = array();
		$table =& $this->getTable();
		$formModel =& $this->getForm();
		$groups =& $formModel->getGroupsHiarachy();
		foreach ($groups as $groupModel) {
			$elementModels =& $groupModel->getPublishedElements();
			foreach ($elementModels as $elementModel) {
				$element =& $elementModel->getElement();
				if (isset($element->show_in_table_summary) && $element->show_in_table_summary) {
					$aHeadings[] = $table->db_table_name . '___' . $element->name;
				}
			}
		}
		return "['" . implode("','", $aHeadings) . "']";
	}

	/**
	 * when form saved (and set to record in database)
	 * this is run to see if there is any table join data,
	 * if there is it stores it in $this->_joinsToProcess
	 *
	 * @return array [joinid] = array(join, group array);
	 */

	function preProcessJoin()
	{
		if (!isset($this->_joinsToProcess)) {
			$this->_joinsToProcess = array();
			$formModel = $this->getForm();
			$groups =& $formModel->getGroupsHiarachy();
			foreach ($groups as $groupModel) {
				$group =& $groupModel->getGroup();
				if ($groupModel->isJoin()) {
					$joinModel =& $groupModel->getJoinModel();
					$join =& $joinModel->getJoin();
					if (!array_key_exists($join->id, $this->_joinsToProcess)) {
						$this->_joinsToProcess[$join->id] = array("join" => $join, "groups" => array($groupModel));
					} else {
						$this->_joinsToProcess[$join->id]["groups"][] = $groupModel;
					}
				}
			}
		}
		return $this->_joinsToProcess;
	}

	/**
	 * check to see if a table exists
	 * @param string name of table (ovewrites form_id val to test)
	 * @param object database that contains the table if null then default $db object used
	 * @return boolean false if no table fodund true if table found
	 */

	function databaseTableExists($tableName = null, $fabrikDatabase = null)
	{
		$db = JFactory::getDBO();
		if ($tableName === '') {
			return false;
		}
		$table =& $this->getTable();
		if (is_null($tableName)) {
			$tableName = $table->db_table_name;
		}
		$sql = "SHOW TABLES LIKE " . $db->Quote($tableName);
		/* use the default Joomla database if no table database specified */
		if (is_null($fabrikDatabase) || !is_object($fabrikDatabase)) {
			$fabrikDatabase =& $this->getDb();
		}
		$fabrikDatabase->setQuery($sql);

		$total = $fabrikDatabase->loadResult();
		echo $fabrikDatabase->getError();
		return ($total == "") ? false : true;
	}

	/**
	 * strip the table names from the front of the key
	 * @param array data to strip
	 * @return array stripped data
	 */

	function removeTableNameFromSaveData($data, $split='___')
	{
		foreach ($data as $key=>$val) {
			$akey = explode($split, $key);
			if (count($akey) > 1) {
				$newKey = $akey[1];
				unset($data[$key]);
			} else {
				$newKey = $akey[0];
			}
			$data[$newKey] = $val;
		}
		return $data;
	}

	/**
	 * saves posted form data into a table
	 * data should be keyed on short name
	 * @param array data to save
	 * @param int row id to edit/updated
	 * @param bol is the data being saved into a join table
	 * @param object joined group table
	 * @return bol true if saved ok
	 */

	function storeRow($data, $rowId, $isJoin = false, $joinGroupTable = null)
	{
		$origRowId = $rowId;
		//dont save a record if no data collected
		//if ($isJoin && implode($data) == '') { //raises notice on save of joined data from csv import
		if ($isJoin && empty($data)) {
			return;
		}
		$fabrikDb 	=& $this->getDb();
		$table =& $this->getTable();

		$formModel =& $this->getForm();
		if ($isJoin) {
			$this->getFormGroupElementData();
		}
		$oRecord = new stdClass();
		$aBindData = array();
		$noRepeatFields = array();
		$c = 0;
		$groups =& $formModel->getGroupsHiarachy();
		foreach ($groups as $groupModel) {
			$group =& $groupModel->getGroup();
			// $$$rob this following if statement avoids this scenario from happening:
			/*
			* you have a form with joins to two other tables
			* each joined group has a field called 'password'
			* first group's password is set to password plugin, second to field
			* on update if no password entered for first field data should not be updated as recordInDatabase() return false
			* however, as we were iterating over all groups, the 2nd password field's data is used instead!
			* this if statement ensures we only look at the correct group
			*/
			if ($isJoin == false || $group->id == $joinGroupTable->id) {
				if (($isJoin && $groupModel->isJoin()) || (!$isJoin && !$groupModel->isJoin())) {
					$elementModels =& $groupModel->getPublishedElements();
					foreach ($elementModels as $elementModel) {
						$element = $elementModel->getElement();
						$key = $element->name;
						//for radio buttons and dropdowns otherwise nothing is stored for them??
						$postkey = (array_key_exists($key ."_raw", $data)) ? $key . "_raw" : $key;

						//if the user cant use or view dont update this element's value
						//read only data should be added in _addDefaultDataFromRO
						if (!$elementModel->canUse() && !$elementModel->canView()) {
							continue;
						}
						//@TODO similar check (but not quiet the same performed in formModel _removeIgnoredData() - should merge into one place
						if ($elementModel->recordInDatabase($data)) {
							if (array_key_exists($key, $data) && !in_array($key, $noRepeatFields)) {
								$noRepeatFields[] = $key;
								$lastKey = $key;

								$val = $elementModel->storeDatabaseFormat($data[$postkey], $data, $key);
								$elementModel->updateRowId($rowId);
								if (array_key_exists('fabrik_copy_from_table', $data)) {
									$val = $elementModel->onCopyRow($val);
								}

								if (array_key_exists('Copy', $data)) {
									$val = $elementModel->onSaveAsCopy($val);
								}

								//test for backslashed quotes
								if (get_magic_quotes_gpc()) {
									if (!$elementModel->_is_upload) {
										$val = stripslashes($val);
									}
								}
								$oRecord->$key = $val;
								$aBindData[$key] = $val;
								$c++;
							}
						}
					}
				}
			}
		}
		$this->_addDefaultDataFromRO($aBindData, $oRecord, $isJoin);
		$primaryKey = $this->_shortKey();
		if ($rowId != '' && $c == 1 && $lastKey == $primaryKey) {
			return;
		}

		/*
		 * $$$ rob - correct rowid is now inserted into the form's rowid hidden field
		 * even when useing usekey and -1, we just need to check if we are adding a new record and if so set rowid to 0
		 */
		if (JRequest::getVar('usekey_newrecord', false)) {
			$rowId = 0;
			$origRowId = 0;
		}

		$primaryKey = str_replace("`", "", $primaryKey);
		// $$$ hugh - if we do this, CSV importing can't maintain existing keys
		if (!$this->_importingCSV) {
			$oRecord->$primaryKey = $rowId;
		}

		if ($origRowId == '' || $origRowId == 0) {

			// $$$ rob added test for auto_inc as sugarid key is set from storeDatabaseFormat() and needs to be maintained
			// $$$ rob don't do this when importing via CSV as we want to maintain existing keys (hence check on task var
			if (($primaryKey !== '' && $this->getTable()->auto_inc == true) && JRequest::getCmd('task') !== 'doImport') {
				unset($oRecord->$primaryKey);
			}
			$ok = $this->insertObject($table->db_table_name, $oRecord, $primaryKey, false);
		} else {
			$ok = $this->updateObject($table->db_table_name, $oRecord, $primaryKey, false);
		}
		$this->_tmpSQL = $fabrikDb->getQuery();
		if (!$ok) {
			$q = JDEBUG ? $fabrikDb->getQuery() : '';
			return JError::raiseWarning(500, 'Store row failed: ' . $q  . "<br>" . $fabrikDb->getErrorMsg());
		} else {
			// $$$ rob new as if you update a record the insertid() returns 0
			$this->_lastInsertId = ($rowId == '' || $rowId == 0) ?$fabrikDb->insertid() : $rowId;
			return true;
		}
	}

	/**
	 * hack! copied from mysqli db driver to enable AES_ENCRYPT calls
	 *
	 * @access public
	 * @param [type] $updateNulls
	 */

	function updateObject($table, &$object, $keyName, $updateNulls=true)
	{
		$db =& $this->getDb();
		$secret = JFactory::getConfig()->getValue('secret');
		$fmtsql = 'UPDATE '.$db->nameQuote($table).' SET %s WHERE %s';
		$tmp = array();
		foreach (get_object_vars($object) as $k => $v) {
			if( is_array($v) or is_object($v) or $k[0] == '_') { // internal or NA field
				continue;
			}
			if( $k == $keyName) { // PK not to be updated
				$where = $keyName . '=' . $db->Quote($v);
				continue;
			}
			if ($v === null)
			{
				if ($updateNulls) {
					$val = 'NULL';
				} else {
					continue;
				}
			} else {
				$val = $db->isQuoted($k) ? $db->Quote($v) : (int) $v;
			}
			if (in_array($k, $this->encrypt)) {
				$val = "AES_ENCRYPT($val, '$secret')";
			}
			$tmp[] = $db->nameQuote($k) . '=' . $val;
		}
		$db->setQuery(sprintf($fmtsql, implode(",", $tmp) , $where));
		return $db->query();
	}

	/**
	 * hack! copied from mysqli db driver to enable AES_ENCRYPT calls
	 * Inserts a row into a table based on an objects properties
	 *
	 * @access public
	 * @param	string	The name of the table
	 * @param	object	An object whose properties match table fields
	 * @param	string	The name of the primary key. If provided the object property is updated.
	 */

	function insertObject($table, &$object, $keyName = NULL)
	{
		$db =& $this->getDb();
		$secret = JFactory::getConfig()->getValue('secret');
		$fmtsql = 'INSERT INTO '.$db->nameQuote($table).' ( %s ) VALUES ( %s ) ';
		$fields = array();
		foreach (get_object_vars($object) as $k => $v) {
			if (is_array($v) or is_object($v) or $v === NULL) {
				continue;
			}
			if ($k[0] == '_') { // internal field
				continue;
			}
			$fields[] = $db->nameQuote($k);
			$val = $db->isQuoted($k) ? $db->Quote($v) : (int) $v;
			if (in_array($k, $this->encrypt)) {
				$val = "AES_ENCRYPT($val, '$secret')";
			}
			$values[] = $val;
		}
		$db->setQuery(sprintf($fmtsql, implode(",", $fields) ,  implode(",", $values)));
		if (!$db->query()) {
			return false;
		}
		$id = $db->insertid();
		if ($keyName && $id) {
			$object->$keyName = $id;
		}
		return true;
	}

	/**
	 * If an element is set to readonly, and has a default value selected then insert this
	 * data into the array that is to be bound to the table record
	 * @since 1.0.6
	 * @param array data
	 * @param object to bind to table row
	 * @param int is record join record
	 */

	function _addDefaultDataFromRO(&$data, &$oRecord, $isJoin)
	{
		jimport('joomla.utilities.simplecrypt');
		// $$$ rob since 1.0.6 : 10 June 08
		// get the current record - not that which was posted
		$formModel =& $this->getForm();
		$table =& $this->getTable();
		//test dont think this is used?
		//$pkv = $data[$this->_shortKey($table->db_primary_key, true )];

		if (is_null($this->_origData)) {
			$sql = $formModel->_buildQuery();
			$db =& $this->getDb();
			$db->setQuery($sql);
			$origdata = $db->loadObject();
			$origdata = JArrayHelper::fromObject($origdata);
			$origdata = is_array($origdata) ? $origdata : array();
			$this->_origData =& $origdata;
		} else {
			$origdata =& $this->_origData;
		}
		$form =& $formModel->getForm();
		$groups =& $formModel->getGroupsHiarachy();
		$gcounter = 0;
		$repeatGroupCounts = JRequest::getVar('fabrik_repeat_group', array());
		foreach  ($groups as $groupModel) {
			if (($isJoin && $groupModel->isJoin()) || (!$isJoin && !$groupModel->isJoin())) {
				$elementModels =& $groupModel->getPublishedElements();
				foreach ($elementModels as $elementModel) {
					// $$$ rob 25/02/2011 unviewable elements are now also being encrypted
					//if (!$elementModel->canUse() && $elementModel->canView()) {
					if (!$elementModel->canUse()) {
						$element =& $elementModel->getElement();
						$fullkey = $elementModel->getFullName(false, true, false);
						$key = $element->name;
						//force a reload of the default value with $origdata
						unset($elementModel->defaults);
						$default = array();
						foreach ($repeatGroupCounts as $groupId => $repeatCount) {
							$def = $elementModel->getValue($origdata, $repeatCount);
							//if its a dropdown radio etc
							if (is_array($def)) {
								$def = implode(GROUPSPLITTER2, $def);
							}
							$default[] = $def;
						}
						$default = implode(GROUPSPLITTER, $default);
						$data[$key] = $default;
						$oRecord->$key = $default;
					}
				}
			}
			$gcounter ++;
		}

		$copy = JRequest::getBool('Copy');

		//check crypted querystring vars (encrypted in form/view.html.php ) _cryptQueryString
		if (array_key_exists('fabrik_vars', $_REQUEST) && array_key_exists('querystring', $_REQUEST['fabrik_vars'])) {
			$crypt = new JSimpleCrypt();
			foreach ($_REQUEST['fabrik_vars']['querystring'] as $key => $encrypted) {

				$key = FabrikString::shortColName($key);
				// $$$ hugh - trying to fix issue where encrypted elements from a main group end up being added to
				// a joined group's field list for the update/insert on the joined row(s).
				if (!array_key_exists($key,$data)) {
					continue;
				}
				foreach ($groups as $groupModel) {
					$elementModels =& $groupModel->getPublishedElements();
					foreach ($elementModels as $elementModel) {
						$element =& $elementModel->getElement();

						if ($element->name == $key) {
							//dont overwrite if something has been entered
							// $$$ rob 25/02/2011 unviewable elements are now also being encrypted
							//if (!$elementModel->canUse() && $elementModel->canView()) {
							if (!$elementModel->canUse()) {
								//repeat groups no join:
								if (is_array($encrypted)) {
									$v = array();
									foreach ($encrypted as $e) {
										$v[] = empty($e)? '' : $crypt->decrypt($e);
									}
									$v = implode(GROUPSPLITTER, $v);
								} else {
									$v = !empty($encrypted) ? $crypt->decrypt($encrypted) : '';
								}

								if ($copy) {
									$v = $elementModel->onSaveAsCopy( $v);
								}
								$data[$key] = $v;
								$oRecord->$key = $v;
							}
						}
					}
				}
			}
		}
	}

	/**
	 * get the short version of the primary key, e.g if key = table.key return "key"
	 * @param string key name - leave null to use table key
	 * @param bol remove quotes (`) from key name
	 * @return string short version of the primary key
	 */

	function _shortKey($key = null, $removeQuotes = false)
	{
		if (is_null($key)) {
			$table =& $this->getTable();
			$key = $table->db_primary_key;

		}
		if (strstr($key, ".")) {
			$bits = explode(".", $key);
			$key = array_pop($bits);
		} else {
			if (strstr($key, '___')) {
				$bits = explode("___", $key);
				$key = array_pop($bits);
			}
		}
		if ($removeQuotes) {
			$key = str_replace("`", "", $key);
		}
		return $key;
	}

	/**
	 * called when the form is submitted to perform calculations
	 */

	function doCalculations()
	{
		$db =& JFactory::getDBO();
		$formModel =& $this->getForm();
		$groups =& $formModel->getGroupsHiarachy();
		foreach ($groups as $groupModel) {
			$elementModels =& $groupModel->getPublishedElements();
			foreach ($elementModels as $elementModel) {
				$element =& $elementModel->getElement();

				$params =& $elementModel->getParams();
				$update = false;
				if ($params->get('sum_on', 0)) {
					$aSumCals = $elementModel->sum($this);
					$params->set('sum_value_serialized', serialize($aSumCals[1]));
					$params->set('sum_value', $aSumCals[0]);
					$update = true;
				}
				if ($params->get('avg_on', 0)) {
					$aAvgCals = $elementModel->avg($this);
					$params->set('avg_value_serialized', serialize($aAvgCals[1]));
					$params->set('avg_value', $aAvgCals[0]);
					$update = true;
				}
				if ($params->get('median_on', 0)) {
					//$params->set('median_value', $elementModel->median($this));

					$medians = $elementModel->median($this);
					$params->set('median_value_serialized', serialize($medians[1]));
					$params->set('median_value', $medians[0]);

					$update = true;
				}
				if ($params->get('count_on', 0)) {
					$aCountCals = $elementModel->count($this);
					$params->set('count_value_serialized', serialize($aCountCals[1]));
					$params->set('count_value', $aCountCals[0]);
					$update = true;
				}
				if ($update) {
					$element =& $elementModel->getElement();
					$element->attribs = $params->updateAttribsFromParams($params->toArray());
					// $$$ hugh - can't store the whole element, otherwise we overwrite the default val!
					// added storeAttribs() to element model
					//$element->store();
					$elementModel->storeAttribs();
				}
			}
		}
	}

	/**
	 * check to see if prefilter should be applied
	 * Kind of an inverse access lookup
	 * @param int group id to check against
	 * @param string ref for filter
	 * @return bol must apply filter - true, ignore filter (user has enough access rights) false;
	 */

	function mustApplyFilter($gid, $ref)
	{
		// prefilters with JACL are applied to a single group only
		// not a group and groups beneath them (think author, registered)
		// so if JACL on then prefilters work in the inverse in that they are only applied
		// to the group selected

		if (defined('_JACL')) {
			return FabrikWorker::getACL($gid, 'prefilter' . $ref);
		} else {
			return FabrikWorker::getACL($gid, 'prefilter' . $ref, '<=');
		}

	}

	/**
	 * set the connection id - used when creating a new table
	 * @param int connection id
	 */

	function setConnectionId($id)
	{
		$this->_table->connection_id = $id;
	}

	/**
	 * run each time a table is saved (plus when an element is saved )
	 * builds the sql query that the table getData function will use
	 * stores it in the cache/fabrik folder
	 * retrieved by getData when table rendered
	 *
	 * NOT sure we need/use this
	 */

	function createCacheQuery()
	{
		$q = $this->_buildQuery();
		$cache = &JFactory::getCache('com_fabrik');
		$cache->store($q, 'tablequery' . $this->_id);
	}


	/**
	 * return the default set of attributes when creating a new
	 * fabrik table
	 *
	 * @return string attributes
	 */

	function getDefaultAttribs()
	{
		$o = "admin_template=admin
detaillink=0
empty_data_msg=No data found
advanced-filter=0
show-table-nav=1
show-table-filters=1
show-table-add=1
pdf=
rss=0
feed_title=
feed_date=
rsslimit=150
rsslimitmax=2500
csv_import_frontend=0
csv_export_frontend=0
csvfullname=0
access=0
allow_view_details=0
allow_edit_details=0
allow_add=0
allow_delete=0
group_by_order=
group_by_order_dir=ASC
prefilter_query=
require-filter=0";
		return $o;
	}

	protected function getGroupBy()
	{
		$table =& $this->getTable();
		return JRequest::getVar('group_by', $table->group_by);
	}

	/**
	 * test if the main J user can create mySQL tables
	 * @return bol
	 */

	function canCreateDbTable()
	{
		return true;
	}

	/**
	 * Create a table to store the forms' data depending upon what groups are assigned to the form
	 * @param object form model
	 * @param string table name - taken from the table oject linked to the form
	 * @param obj tables database object NOT USED!!!!
	 */

	function createDBTable(&$formModel, $dbTableName = null, $tableDatabase = null)
	{
		$db 		=& JFactory::getDBO();
		$tableDatabase =& $this->getDb();
		$user  	=& JFactory::getUser();
		$config =& JFactory::getConfig();
		if (is_null($dbTableName)) {
			$dbTableName = $this->getTable()->db_table_name;
		}
		$sql = "CREATE TABLE IF NOT EXISTS ".$db->nameQuote($dbTableName)." ( ";

		$db->setQuery("SELECT group_id FROM #__fabrik_formgroup WHERE form_id = ".(int)$formModel->_id);
		$groupIds = $db->loadResultArray();
		/* create elements for the internal id and time_date fields */
		$element =& JTable::getInstance('Element', 'Table');
		$element->name			= "id";
		$element->label			= "id";
		$element->plugin 		= 'fabrikinternalid';
		$element->hidden 		= 1;
		$element->group_id 	= $groupIds[0];
		$element->primary_key		= 1;
		$element->auto_increment	= 1;
		$element->created 			= JHTML::_('date', date( '%Y-%m-%d %H:%M:%S'), '%Y-%m-%d %H:%M:%S', - $config->getValue('offset'));
		$element->created_by 		= $user->get('id');
		$element->created_by_alias = $user->get('username');
		$element->state 			= '1';
		$element->show_in_table_summary = '1';
		$element->link_to_detail = '1';
		$element->width 	= '30';
		$element->ordering 		= 0;
		if (!$element->store()) {
			return JError::raiseWarning(500, $element->getError());
		}
		$element =& JTable::getInstance('Element', 'Table');
		$element->name		= "date_time";
		$element->label		= "date";
		$element->plugin 	= 'fabrikdate';
		$element->hidden 			= 1;
		$element->eval				= 1;
		$element->default			= "return date('Y-m-d h:i:s');";
		$element->group_id 		= $groupIds[0];
		$element->primary_key		= 0;
		$element->auto_increment	= 0;
		$element->created 			= JHTML::_('date', date( '%Y-%m-%d %H:%M:%S'), '%Y-%m-%d %H:%M:%S', - $config->getValue('offset'));
		$element->created_by 		= $user->get('id');
		$element->created_by_alias = $user->get('username');
		$element->state 			= '1';
		$element->show_in_table_summary = '1';
		$element->width 	= '30';
		$element->ordering 		= 1;
		if (!$element->store()) {
			return JError::raiseWarning(500, $element->getError());
		}
		//reset form and plugin manager to load up newly created elementss
		$formModel->groups = null;
		$formModel->_loadGroupIds();
		$pluginManager =& $formModel->getPluginManager();
		unset($pluginManager->formplugins);
		$aGroups = $formModel->getGroupsHiarachy();
		$arAddedObj = array();
		//these two should be real elements not hacked in here
		$pluginManager =& JModel::getInstance('Pluginmanager', 'FabrikModel');
		foreach ($aGroups as $groupModel) {
			$elementModels =& $groupModel->getMyElements();
			foreach ($elementModels as $elementModel) {
				$element = $elementModel->getElement();
				/* replace all non alphanumeric characters with _ */
				$objname = preg_replace("/[^A-Za-z0-9]/", "_", $element->name);
				/* any elements that are names the same (eg radio buttons) can not be entered twice into the database */
				if (!in_array($objname, $arAddedObj)) {
					$arAddedObj[] = $objname;
					$objtype = $elementModel->getFieldDescription();
					if ($objname != "" && !is_null($objtype)) {
						if (JString::stristr($objtype, 'not null')) {
							$sql .= $db->nameQuote($objname) . " $objtype, ";
						} else {
							$sql .= $db->nameQuote($objname) . " $objtype null, ";
						}
					}
				}
			}
		}
		$sql .= " primary key (id))";
		$sql .= " ENGINE = MYISAM ";
		$tableDatabase->setQuery($sql);
		if (!$tableDatabase->query()) {
			return JError::raiseWarning(500, $tableDatabase->getErrorMsg());
		}
	}



	/**
	 * updates the table record to point to the newly created form
	 * @params int form id
	 */

	function _updateFormId($formId)
	{
		$db =& JFactory::getDBO();
		$table =& $this->getTable();
		$table->form_id = $formId;
		if (!$table->store()) {
			return JError::raiseWarning(500, $db->getError());
		}
	}

	/**
	 * get the tables primary key and if the primary key is auto increment
	 * @param string optional table name (used when getting pk to joined tables
	 * @return mixed if ok returns array(key, extra, type, name) otherwise
	 * returns false
	 */

	function getPrimaryKeyAndExtra($table = null)
	{
		$origColNames = $this->getDBFields($table);
		$keys = array();
		if (is_array($origColNames)) {
			foreach ($origColNames as $origColName) {
				$colName 	= $origColName->Field;
				$key 			= $origColName->Key;
				$extra 		= $origColName->Extra;
				$type 		= $origColName->Type;
				if ($key == "PRI") {
					$keys[] = array("key" => $key, "extra" => $extra, "type" => $type, "colname" => $colName);
				}
			}
		}
		return empty($keys) ? false : $keys;
	}

	/**
	 * run the prefilter sql and replace any placeholders in the subsequent prefilter
	 *
	 * @param string/array prefilter value
	 * @return string/array prefilter value
	 */

	function _prefilterParse($selValue)
	{
		$isstring = false;
		if (is_string($selValue)) {
			$isstring = true;
			$selValue = array($selValue);
		}
		$preSQL = htmlspecialchars_decode($this->_params->get('prefilter_query'), ENT_QUOTES);
		if (trim($preSQL) != '') {
			$db =& JFactory::getDBO();
			$w = new FabrikWorker();
			$w->replaceRequest( $preSQL);
			$preSQL = $w->parseMessageForPlaceHolder($preSQL);
			$db->setQuery($preSQL);
			$q = $db->loadObjectList();
			if (!empty($q)) {
				$q = $q[0];
			}
		}
		if (isset($q)) {
			foreach ( $q as $key=>$val) {
				if (substr($key, 0, 1) != '_') {
					$found = false;
					for ($i=0; $i < count($selValue); $i++) {
						if (strstr($selValue[$i], '{$q-&gt;'. $key)) {
							$found = true;
							$pattern = '{$q-&gt;'. $key. "}";
						}
						if (strstr($selValue[$i], '{$q->' . $key)) {
							$found = true;
							$pattern = '{$q->'. $key . "}";
						}

						if ($found) {
							$selValue[$i] = str_replace($pattern, $val, $selValue[$i]);
						}
					}
				}
			}
		} else {
			//parse for default values only
			$pattern = "/({[^}]+}).*}?/s";
			for ($i=0; $i < count($selValue); $i++) {
				$ok = preg_match($pattern, $selValue[$i], $matches);
				foreach ($matches as $match) {
					$matchx = substr($match, 1, strlen($match) - 2);
					//a default option was set so lets use that
					if (strstr($matchx, '|')) {
						$bits = explode('|', $matchx);
						$selValue[$i] = str_replace($match, $bits[1], $selValue[$i]);
					}
				}
			}
		}
		return $isstring ? $selValue[0] : $selValue;
	}

	protected function getIndexes()
	{
		if (!isset($this->_indexes)) {
			$db =& $this->getDb();
			$db->setQuery("SHOW INDEXES FROM " . $this->getTable()->db_table_name);
			$this->_indexes = $db->loadObjectList();
		}
		return $this->_indexes;
	}

	/**
	 * add an index to the table
	 * @param string field name
	 * @param stirng index name prefix (allows you to differentiate between indexes created in
	 * different parts of fabrik)
	 * @param string index type
	 * @param int index length
	 */

	public function addIndex($field, $prefix = '', $type = 'INDEX', $size = '')
	{
		$indexes =& $this->getIndexes();
		// $$$ hugh - @TODO $field is in 'table.element' format but $indexes
		// has Column_name as just 'element' ... so we're always rebuilding indexes!
		// I'm in the middle of fixing something else, must come back and fix this!!
		// OK, moved these two lines from below to here
		$field = str_replace('_raw', '', $field);
		// $$$ rob 29/03/2011 ensure its in tablename___elementname format
		$field = str_replace('.', '___', $field);
		// $$$ rob 28/02/2011 if index in joined table we need to use that the make the key on
		if (!strstr($field, '___')) {
			$table = $this->getTable()->db_table_name;
		} else {
			$table = array_shift(explode('___', $field));
		}
		$field = FabrikString::shortColName($field);
		FArrayHelper::filter($indexes, 'Column_name', $field);
		if (!empty($indexes)) {
			// an index already exists on that column name no need to add
			return;
		}
		$db =& $this->getDb();

		if ($field == '') {
			return;
		}
		if ($size != '') {
			$size = "( $size )";
		}

		$this->dropIndex($field, $prefix, $type, $table);

		$query = " ALTER TABLE ".$db->nameQuote($table)." ADD INDEX ".$db->nameQuote("fb_{$prefix}_{$field}_{$type}")." (".$db->nameQuote($field)." $size)";
		$db->setQuery($query);
		$db->query();
	}

	/**
	 * drop an index
	 * @param string field name
	 * @param stirng index name prefix (allows you to differentiate between indexes created in
	 * different parts of fabrik)
	 * @param string table name @since 29/03/2011
	 * @return string index type
	 */

	public function dropIndex($field, $prefix = '', $type = 'INDEX', $table = '')
	{
		$db =& $this->getDb();
		$table = $table == '' ? $this->getTable()->db_table_name : $table;
		$field = FabrikString::shortColName($field);
		if ($field == '') {
			return;
		}
		$db->setQuery("SHOW INDEX FROM ".$db->nameQuote($table));
		$dbIndexes = $db->loadObjectList();
		if (is_array($dbIndexes)) {
			foreach ($dbIndexes as $index) {
				if ($index->Key_name == "fb_{$prefix}_{$field}_{$type}") {
					$db->setQuery(" ALTER TABLE ".$db->nameQuote($table)." DROP INDEX ".$db->nameQuote("fb_{$prefix}_{$field}_{$type}"));
					$db->query();
					break;
				}
			}
		}
	}

	/**
	 * delete joined records when deleting the main row
	 * @param string quoted primary key values from the main table's rows that are to be deleted
	 */

	protected function deleteJoinedRows($val)
	{
		$db 		=& $this->getDb();
		$params =& $this->getParams();
		if ($params->get('delete-joined-rows', false)) {
			$joins = $this->getJoins();
			for ($i=0; $i<count($joins); $i++) {
				$join = $joins[$i];
				if ((int)$join->table_id !== 0) {
					$sql = "DELETE FROM ".$db->nameQuote($join->table_join)." WHERE ".$db->nameQuote($join->table_join_key)." IN (".$val.")";
					$db->setQuery($sql);
					$db->query();
				}
			}
		}
	}

	/**
	 * deletes records from a table
	 * @param string key value to delete
	 * @param string key to use (leave empty to default to the table's key)
	 * @return string error message
	 */

	function deleteRows(&$ids, $key = '')
	{
		if (!is_array($ids)) {
			$ids = array($ids);
		}
		$val = $ids;
		$app =& JFactory::getApplication();
		$table 	=& $this->getTable();
		$db 		=& $this->getDb();
		$params =& $this->getParams();
		if ($key == '') {
			$key = $table->db_primary_key;
			if ($key == '') {
				return JError::raiseWarning(JText::_("NO KEY FOUND FOR THIS TABLE"));
			}
		}

		$c = count($val);
		foreach ($val as &$v) {
			$v = $db->Quote($v);
		}
		$val = implode(",", $val);

		// $$$ rob - if we are not deleting joined rows then onloy load in the first row
		// otherwise load in all rows so we can apply onDeleteRows() to all the data
		if ($this->getParams()->get('delete-joined-rows', false) == false) {
			$nav =& $this->getPagination($c, 0, $c);
		}
		$this->_whereSQL[true] = " WHERE " . $key . " IN (" . $val . ")";
		// $$$ hugh - need to clear cached data, 'cos we called getTotalRecords from the controller, which now
		// calls getData(), and will have cached all rows on this page, not just the ones being deleted, which means
		// things like form and element onDelete plugins will get handed a whole page of rows, not just the ones
		// selected for delete!  Ooops.
		unset($this->_data);
		$rows 	=& $this->getData();

		// $$$ hugh - we need to check delete perms, see:
		// http://fabrikar.com/forums/showthread.php?p=102670#post102670
		// Short version, if user has access for a table plugin, they get a checkbox on the row, but may not have
		// delete access on that row.
		$removed_id = false;
		foreach ($rows as &$group) {
			foreach ($group as $group_key => $row) {
				if (!$this->canDelete($row)) {
					// Can't delete, so remove row data from $rows, and the id from $ids, and queue a message
					foreach ($ids as $id_key => $id) {
						if ($id == $row->__pk_val) {
							unset($ids[$id_key]);
							continue;
						}
					}
					unset($group[$group_key]);
					$app->enqueueMessage('NO PERMISSION TO DELETE ROW');
					$removed_id = true;
				}
			}
		}

		// see if we have any rows left to delete after checking perms
		if (empty($ids)) {
			return;
		}

		// redo $val list of ids in case we zapped any on canDelete check
		if ($removed_id) {
			$val = $ids;
			$c = count($val);
			foreach ($val as &$v) {
				$v = $db->Quote($v);
			}
			$val = implode(",", $val);
		}

		$this->_rowsToDelete =& $rows;
		$groupModels =& $this->getFormGroupElementData();

		foreach ($groupModels as $groupModel) {
			$elementModels =& $groupModel->getPublishedElements();
			foreach ($elementModels as $elementModel) {
				$elementModel->onDeleteRows($rows);
			}
		}

		// $$$ hugh - added onDeleteRowsForm plugin (needed it so fabrikjuser form plugin can delete users)
		// NOTE - had to call it onDeleteRowsForm rather than onDeleteRows, otherwise runPlugins() automagically
		// runs the element onDeleteRows(), which we already do above.  And with the code as-is, that won't work
		// from runPlugins() 'cos it won't pass it the $rows it needs.  So i have to sidestep the issue by using
		// a different trigger name.  Added a default onDeleteRowsForm() to plugin-form.php, and implemented
		// (and tested) user deletion in fabrikjuser.php using this trigger.  All seems to work.  7/28/2009
		$formModel =& $this->getForm();
		$pluginManager 	=& $formModel->getPluginManager();
		if (in_array(false, $pluginManager->runPlugins('onDeleteRowsForm', $formModel, 'form', $rows))) {
			return;
		}

		$pluginManager =& JModel::getInstance('Pluginmanager', 'FabrikModel');
		$pluginManager->getPlugInGroup('table');
		if (in_array(false, $pluginManager->runPlugins('onDeleteRows', $this, 'table'))) {
			return;
		}

		$sql = "DELETE FROM ".$db->nameQuote($table->db_table_name)." WHERE ".$key." IN (".$val.")";
		$db->setQuery($sql);
		if (!$db->query()) {
			return JError::raiseWarning($db->getErrorMsg());
		}

		$this->deleteJoinedRows($val);

		// Clean the cache.
		$cache = JFactory::getCache(JRequest::getCmd('option'));
		$cache->clean();
		return true;
	}

	/**
	 * remove all records from the table
	 */

	function dropData()
	{
		$db =& $this->getDb();
		$table =& $this->getTable();
		$sql = "DELETE FROM ".$db->nameQuote($table->db_table_name);
		$db->setQuery($sql);
		$msg= '';
		if (!$db->query()) {
			return JError::raiseWarning(JText::_($db->getErrorMsg()));
		}
		return '';
	}

	/**
	 * drop the table containing the fabriktables data
	 */

	function drop()
	{
		$db =& $this->getDb();
		$table =& $this->getTable();
		$sql = "DROP TABLE IF EXISTS ".$db->nameQuote($table->db_table_name);
		$db->setQuery($sql);
		if (!$db->query()) {
			return JError::raiseWarning(JText::_($db->getErrorMsg()));
		}
		return '';
	}

	function truncate()
	{
		$db =& $this->getDb();
		$table =& $this->getTable();
		$db->setQuery("TRUNCATE ".$db->nameQuote($table->db_table_name));
		$db->query();
	}

	/**
	 * test if a field already exists in the database
	 *
	 * @param string $field
	 * @param array id's to ignore.
	 * @return bol
	 */

	function fieldExists($field, $ignore = array())
	{
		$field = strtolower($field);
		$groupModels =& $this->getFormGroupElementData();
		foreach ($groupModels as $groupModel) {
			if (!$groupModel->isJoin()) {//dont check groups that aren't in this table
				$elementModels =& $groupModel->getMyElements();
				foreach ($elementModels as $elementModel) {
					$element =& $elementModel->getElement();
					$n = strtolower($element->name);
					if (strtolower($element->name) == $field && !in_array($element->id, $ignore)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * Alter the forms' data collection table when the forms' groups and/or
	 * elements are altered
	 * @param object form
	 * @param string table name
	 * @param object database connection object
	 */

	function ammendTable(&$oForm, $tableName = null, $tableDatabase = null)
	{
		$db 		=& JFactory::getDBO();
		$user  	=& JFactory::getUser();
		$table =& $this->getTable();
		$pluginManager =& JModel::getInstance('Pluginmanager', 'FabrikModel');
		$ammend 		= false;
		if (is_null($tableName)) {
			$tableName = $table->db_table_name;
		}
		if (is_null($tableDatabase) || !is_object($tableDatabase)) {
			//$tableDatabase = $db;
			$tableDatabase =& $this->getDb();
		}

		$dbdescriptions = $this->getDBFields($tableName);
		//@TODO: test (was strtolower($dbdescription->Field))  if this is going to cause issues, think fields should be case insenitvely compared as some joomla core fields are mixed case
		foreach ($dbdescriptions as $dbdescription) {
			$exitingfields[] = strtolower($dbdescription->Field);
		}
		$lastfield = $exitingfields[count($exitingfields)-1];
		$sql = "ALTER TABLE ".$db->nameQuote($tableName)." ";
		$sql_add = array();
		if (!isset($_POST['current_groups_str'])) {
			/* get a list of groups used by the form */
			$groupsql = "SELECT group_id FROM #__fabrik_formgroup WHERE form_id = ".(int)$oForm->_id;
			$db->setQuery($groupsql);
			$groups = $db->loadObjectList();
			if (!$groups) {
				JError::raiseWarning(500,  'ammendTable: ' . $fabrikDb->getErrorMsg());
			}
			$arGroups = array();
			foreach ($groups as $g) {
				$arGroups[] = $g->group_id;
			}
		} else {
			$current_groups_str = JRequest::getVar('current_groups_str');

			$arGroups = explode(",", $current_groups_str);
		}

		$arAddedObj = array();
		foreach ($arGroups as $group_id) {
			$group = JTable::getInstance('Group', 'Table');
			$group->load($group_id);
			if ($group->is_join == '0') {
				$groupsql = "SELECT * FROM #__fabrik_elements WHERE group_id = ".(int)$group_id;
				$db->setQuery($groupsql);
				$elements = $db->loadObjectList();
				foreach ($elements as $obj) {
					$objname = strtolower(preg_replace("/[^A-Za-z0-9]/", "_", $obj->name));
					/* replace all non alphanumeric characters with _*/
					if (!in_array($objname, $exitingfields)) {
						/* make sure that the object is not already in the table*/
						if (!in_array($objname, $arAddedObj)) {
							/* any elements that are names the same (eg radio buttons) can not be entered twice into the database*/
							$arAddedObj[] 		= $objname;
							$objtypeid 				= $obj->plugin;
							$pluginClassName 	= $obj->plugin;
							$plugin 					= $pluginManager->getPlugIn($pluginClassName, 'element');
							$plugin->setId($obj->id);
							$objtype 					= $plugin->getFieldDescription();
							if ($objname != "" && !is_null($objtype)) {
								$ammend = true;
								$sql_add[] = "ADD COLUMN ".$tableDatabase->nameQuote($objname)." $objtype null AFTER ".$tableDatabase->nameQuote($lastfield);
							}
						}
					}
				}
			}
		}
		if ($ammend) {
			$sql .= implode(', ', $sql_add);
			$tableDatabase->setQuery($sql);
			if (!$tableDatabase->query()) {
				return JError::raiseWarning(500, 'amend table: ' . $tableDatabase->getErrorMsg());
			}
			$this->createCacheQuery();
		}
	}

	/**
	 * @param int connection id to use
	 * @param string table to load fields for
	 * @param string show "please select" top option
	 * @param bol append field name values with table name
	 * @param string name of drop down
	 * @param string selected option
	 * @param string class name
	 * @return string html to be added to DOM
	 */

	function getFieldsDropDown($cnnId, $tbl, $incSelect, $incTableName = false, $selectListName = 'order_by', $selected = null, $className = "inputbox")
	{
		$this->setConnectionId($cnnId);
		$aFields = $this->getDBFields($tbl);
		$fieldNames = array();
		if ($incSelect != '') {
			$fieldNames[] = JHTML::_('select.option', '', $incSelect);
		}
		if (is_array($aFields)) {
			foreach ($aFields as $oField) {
				if ($incTableName) {
					$fieldNames[] = JHTML::_('select.option', $tbl . "___" . $oField->Field, $oField->Field);
				} else {
					$fieldNames[] = JHTML::_('select.option', $oField->Field);
				}
			}
		}
		$fieldDropDown = JHTML::_('select.genericlist',  $fieldNames, $selectListName, "class=\"$className\"  size=\"1\" ", 'value', 'text', $selected);
		return str_replace("\n", "", $fieldDropDown);
	}

	/**
	 * create the RSS href link to go in the table template
	 * @return string RSS link
	 */

	function getRSSFeedLink()
	{
		$app =& JFactory::getApplication();
		$link = '';
		if ($this->_params->get('rss') == '1') {
			//$$$ rob test fabriks own feed renderer
			//$link = 'index.php?option=com_fabrik&view=table&tableid=' . $this->_id . "&format=feed";
			$link = 'index.php?option=com_fabrik&view=table&tableid=' . $this->_id . "&format=fabrikfeed";
			if (!$app->isAdmin()) {
				$link = JRoute::_($link);
			}
		}
		return $link;
	}

	/**
	 * iterates through string to replace every
	 * {placeholder} with row data
	 * (added by hugh, does the same thing as parseMessageForPlaceHolder in parent
	 * class, but for rows instead of forms)
	 * @param string text to parse
	 * @param array of row data
	 * @param bool add slashes to the replaced data (default = false) set to true in fabrikcalc element
	 */

	function parseMessageForRowHolder($msg, &$row, $addslashes = false)
	{
		$this->_aRow = $row;
		if (!strstr($msg, '{')){
			return $msg;
		}
		$this->_parseAddSlases = $addslashes;
		$msg = FabrikWorker::_replaceWithUserData($msg);
		$msg = FabrikWorker::_replaceWithGlobals($msg);
		$msg = preg_replace("/{}/", "", $msg);
		$this->_rowIdentifierAdded = false;
		/* replace {element name} with form data */
		// $$$ hugh - testing changing the regex so we don't blow away PHP structures!  Added the \s so
		// we only match non-space chars in {}'s.  So unless you have some code like "if (blah) {foo;}", PHP
		// block level {}'s should remain unmolested.
		$msg = preg_replace_callback( "/{[^}\s]+}/i", array($this,'_replaceWithRowData'), $msg);
		return $msg;
	}

	/**
	 * PRVIATE:
	 * called from parseMessageForRowHolder to iterate through string to replace
	 * {placeholder} with row data
	 * @param array matches found in parseMessageForRowHolder
	 * @return string posted data that corresponds with placeholder
	 */

	function _replaceWithRowData($matches)
	{
		$match = $matches[0];
		/* strip the {} */
		$match = substr($match, 1, strlen($match) - 2);

		// $$$ hugh - in case any {$my->foo} or {$_SERVER->FOO} paterns are left over, avoid 'undefined index' warnings
		if (preg_match('#^\$#',$match)) {
			return '';
		}
		$match = str_replace('.', '___', $match);
		// $$$ hugh - allow use of {$rowpk} or {rowpk} to mean the rowid of the row within a table
		if ($match == 'rowpk' || $match == '$rowpk' || $match == 'rowid')
		{
			$this->_rowIdentifierAdded = true;
			$match = '__pk_val';
		}
		$match = preg_replace("/ /", "_", $match);
		if ($match == 'fabrik') {
			return $this->getForm()->_id;
		}
		$return = JArrayHelper::getValue($this->_aRow, $match);
		if ($this->_parseAddSlases) {
			$return = htmlspecialchars($return, ENT_QUOTES, 'UTF-8');
		}
		return $return;
	}

	/**
	 * get the link to view the records details
	 * @param object $row active table row
	 * @return url of view details link
	 */

	function viewDetailsLink(&$row, $view = null)
	{
		global $Itemid;
		$app =& JFactory::getApplication();
		$keyIdentifier = $this->getKeyIndetifier($row);
		$params =& $this->getParams();
		$table =& $this->getTable();
		$link = '';

		if (is_null($view)) {
			$view = $this->canEdit($row) ? "form" : "details";
		}

		$customLink = $view == 'form' ? $params->get('editurl') : $params->get('detailurl');
		$action = $app->isAdmin() ? "task" : "view";

		//$$$ rob check done outside of method - as we want to populate $row->fabrik_view_url and $row->fabrik_edit_url regardless
		//$check = ($view == 'details') ? $this->canViewDetails() : $this->canEdit($row);

		//TODO: test this in admin table & front end
		/*if ($this->ajaxEditViewLink()) {
		$link = "#";
		} else {*/
		if (trim($customLink) === '') {
			$link = '';
			// $$$ hugh - if we don't do this on feeds, links with subfolders in root get screwed up because no BASE_HREF is set
			if (JRequest::getvar('format', '') == 'fabrikfeed') {
				$link .= COM_FABRIK_LIVESITE;
			}
			$link .= "index.php?option=com_fabrik&c=form&$action=$view&Itemid=$Itemid&fabrik=" . $table->form_id . "$keyIdentifier&tableid=" . $this->_id;
			// $$$ hugh - no _cursor if doing an RSS feed, so avoid PHP warning ...
			if (isset($row->_cursor)) {
				$link .= "&fabrik_cursor=".$row->_cursor;
			}
			$link = JRoute::_($link);
		} else {
			//custom link
			$link = $this->makeCustomLink($customLink, $row);
		}
		//}
		return $link;
	}

	/**
	 * create a custom edit/view details link
	 * @param string $link
	 * @param object $row
	 */

	protected function makeCustomLink($link, $row)
	{
		$link = htmlspecialchars($link);
		$keyIdentifier = $this->getKeyIndetifier($row);
		$link = $this->parseMessageForRowHolder($link, JArrayHelper::fromObject($row));
		if ($this->_rowIdentifierAdded === false) {
			if (strstr($link,'?')) {
				$link .= $keyIdentifier;
			}
			else {
				$link .= '?' . str_replace('&', '', $keyIdentifier);
			}
		}
		$link = JRoute::_($link);
		return $link;
	}

	/**
	 * get the link to edit the records details
	 * @param object $row active table row
	 * @return url of view details link
	 */

	function editLink(&$row)
	{
		global $Itemid;
		$app =& JFactory::getApplication();
		$keyIdentifier = $this->getKeyIndetifier($row);
		$table =& $this->getTable();
		$link = '';
		$action = $app->isAdmin() ? "task" : "view";
		/*if ($this->ajaxEditViewLink()) {
			$link = "#";
			} else {*/
		$customLink = $this->getParams()->get('editurl');
		if ($customLink == '') {
			$link = JRoute::_("index.php?option=com_fabrik&c=form&$action=form&Itemid=$Itemid&fabrik=" . $table->form_id . "$keyIdentifier&tableid=" .$this->_id);
		} else {
			$link = $this->makeCustomLink($customLink, $row);
		}
		//}
		return $link;
	}

	public function ajaxEditViewLink()
	{
		$params = $this->getParams();
		return ((($this->_outPutFormat == 'json' || $this->_outPutFormat == 'raw') && $params->get('ajax_nav') == 'post') || $this->getAddEditMethod() == 'ajax');
	}

	/**
	 * make the drop slq statement for the table
	 * @return string drop table sql
	 */

	function getDropTableSQL()
	{
		$db = JFavotry::getDBO();
		$genTable = $this->getGenericTableName();
		$sql = "DROP TABLE IF EXISTS ".$db->nameQuote($genTable);
		return $sql;
	}

	function getGenericTableName()
	{
		$app =& JFactory::getApplication();
		$table =& $this->getTable();
		return str_replace($app->getCfg('dbprefix'), '#__', $table->db_table_name);
	}

	/**
	 * make the create sql statement for the table
	 * @return string sql to drop & or create table
	 */

	function getCreateTableSQL()
	{
		$table 	= $this->getGenericTableName();
		$fields 	=  $this->getDBFields();
		$primaryKey = "";
		$sql 		= "";
		$table = FabrikString::safeColName($table);
		if (is_array($fields)) {
			$sql .= "CREATE TABLE " .  $table ." (\n";
			foreach ($fields as $field) {
				$field->Field = FabrikString::safeColName($field->Field);
				if ($field->Key == 'PRI') {
					$primaryKey = "PRIMARY KEY ($field->Field)";
				}

				$sql .=	"$field->Field ";

				if ($field->Key == 'PRI') {
					$sql .= ' INT(6) ';
				} else {
					$sql .= ' ' . $field->Type . ' ';
				}
				if ($field->Null == '') {
					$sql .= " NOT NULL ";
				}
				if ($field->Default != '' && $field->Key != 'PRI') {
					if ($field->Default == 'CURRENT_TIMESTAMP') {
						$sql .= "DEFAULT $field->Default";
					} else {
						$sql .= "DEFAULT '$field->Default'";
					}
				}
				if ($field->Key == 'PRI') {
					$sql .= " AUTO_INCREMENT ";
				}

				$sql .= $field->Extra . ",\n";
			}
			if ($primaryKey == '') {
				$sql = rtrim($sql,",\n");
			}
			$sql .= $primaryKey . ");";
		}
		return $sql;
	}

	/**
	 * make the create sql statement for inserting the table data
	 * used in package export
	 * @param object exporter
	 * @return string sql to drop & or create table
	 */

	function getInsertRowsSQL($oExporter)
	{
		@set_time_limit(300);
		$table =& $this->getTable();
		$memoryLimit = ini_get('memory_limit');
		$db =& $this->getDb();
		//dont load in all the table data as on large tables this gives a memory error
		//in fact this wasnt the problem, but rather the $sql var becomes too large to hold in memory
		//going to try saving to a file on the server and then compressing that and sending it as a header for download

		$db->setQuery("SELECT $table->db_primary_key FROM ".$db->nameQuote($table->db_table_name));
		$keys = $db->loadResultArray();
		$sql = "";
		$dump_buffer_len = 0;
		if (is_array($keys)) {
			foreach ($keys as $id) {
				$db->setQuery("SELECT * FROM ".$db->nameQuote($table->db_table_name)." WHERE $table->db_primary_key = $id");
				$row = $db->loadObject();
				$fmtsql = "\t<query>INSERT INTO ".$db->nameQuote($table->db_table_name)." ( %s ) VALUES ( %s )</query>";
				$values = array();
				$fields = array();
				foreach ($row as $k => $v) {
					$fields[] = $db->NameQuote($k);
					$values[] = $db->Quote($v);
				}
				$sql .= sprintf( $fmtsql, implode(",", $fields ) ,  implode(",", $values));
				$sql .= "\n";

				$dump_buffer_len += strlen($sql);
				if ($dump_buffer_len  > $memoryLimit) {
					$oExporter->writeExportBuffer($sql);
					$sql = "";
					$dump_buffer_len = 0;
				}
				unset($values);
				unset($fmtsql);
			}
		}
		$oExporter->writeExportBuffer($sql);
	}

	/**
	 * get a row of data from the table
	 *
	 * @param int $id
	 * @return object row
	 */

	function getRow($id, $format = false)
	{
		$fabrikDb =& $this->getDb();
		$formModel =& $this->getForm();
		$formModel->_rowId = $id;
		unset($formModel->query);
		$sql = $formModel->_buildQuery();
		$fabrikDb->setQuery($sql);
		if ($format == true) {
			$row = $fabrikDb->loadObject();
			$row->_cursor = 0;
			$row->_total = 0;
			$row = array($row);
			$this->formatData($row);
			return $row[0][0];
		} else {
			return $fabrikDb->loadObject();
		}
	}

	/**
	 * try to find a row in the table that matches " key LIKE '%val' "
	 * @param string $key
	 * @param string $val
	 * @param bool $format
	 * @return object row
	 */

	function findRow($key, $val, $format = false)
	{
		$usekey = JRequest::getVar('usekey');
		$usekey_comparison = JRequest::getVar('usekey_comparison');
		JRequest::setVar('usekey', $key);
		JRequest::setVar('usekey_comparison', 'like');
		$row =  $this->getRow($val, $format);
		JRequest::setVar('usekey', $usekey);
		JRequest::setVar('usekey_comparison', $usekey_comparison);
		return $row;
	}

	/**
	 * ajax get record specified by row id
	 */

	function xRecord($mode = 'table')
	{
		$fabrikDb = $this->getDb();
		$cursor = JRequest::getInt('cursor', 1);
		$this->getConnection();
		$this->_outPutFormat = 'json';
		$nav	=& $this->getPagination(1, $cursor, 1);
		if ($mode == 'table') {
			$query 		= $this->_buildQuery();
			$this->setBigSelects();
			$fabrikDb->setQuery($query, $this->limitStart, $this->limitLength);
			$data =  $fabrikDb->loadObjectList();
		} else {
			//get the row id
			$table =& $this->getTable();

			$order = $this->_buildQueryOrder();
			$join = $this->_buildQueryJoin();
			$query = "SELECT $table->db_primary_key FROM $table->db_table_name $join $order";

			$fabrikDb->setQuery($query, $nav->limitstart, $nav->limit);
			$rowid  = $fabrikDb->loadResult();
			JRequest::setVar('rowid', $rowid);
			$app =& JFactory::getApplication();
			$formid = JRequest::getInt('fabrik');
			$mode = $mode == 'details' ? 'details' : 'form';
			$app->redirect('index.php?option=com_fabrik&c=form&view='.$mode.'&fabrik='.$formid.'&rowid='.$rowid.'&format=raw');
		}
		return FastJSON::encode($data);
	}

	/**
	 * ajax get next record
	 * @return string json object representing record/row
	 */

	function nextRecord()
	{
		$cursor = JRequest::getInt('cursor', 1);
		$this->getConnection();
		$this->_outPutFormat = 'json';
		$nav =& $this->getPagination(1, $cursor, 1);
		$data = $this->getData();
		echo FastJSON::encode($data);
	}

	/**
	 * ajax get previous record
	 * @return string json object representing record/row
	 */

	function previousRecord()
	{
		$cursor = JRequest::getInt('cursor', 1);
		$this->getConnection();
		$this->_outPutFormat = 'json';
		$nav =& $this->getPagination(1, $cursor-2, 1);
		$data = $this->getData();
		return FastJSON::encode($data);
	}

	/**
	 * ajax get first record
	 * @return string json object representing record/row
	 */

	function firstRecord()
	{
		$cursor = JRequest::getInt('cursor', 1);
		$this->getConnection();
		$this->_outPutFormat = 'json';
		$nav =& $this->getPagination(1, 0, 1);
		$data = $this->getData();
		return FastJSON::encode($data);
	}

	/**
	 * ajax get last record
	 * @return string json object representing record/row
	 */

	function lastRecord()
	{
		$total = JRequest::getInt('total', 0);
		$this->getConnection();
		$this->_outPutFormat = 'json';
		$nav =& $this->getPagination(1, $total-1, 1);
		$data = $this->getData();
		return FastJSON::encode($data);
	}

	/**
	 * get a single column of data from the table, test for element filters
	 * @param string column to get
	 * @return array values for the column - empty array if no results found
	 */

	function getColumnData($col)
	{
		$table =& $this->getTable();
		$fbConfig =& JComponentHelper::getParams('com_fabrik');
		$db =& $this->getDb();
		$el = $this->getForm()->getElement($col);
		$col = FabrikString::safeColName($col);
		$el->encryptFieldName($col);
		$tablename =  $table->db_table_name;
		$tablename = FabrikString::safeColName($tablename);
		$query = "SELECT DISTINCT($col) FROM " . $tablename . ' ' . $this->_buildQueryJoin();
		$query .= $this->_buildQueryWhere(false);
		$query .= " LIMIT " . $fbConfig->get('filter_list_max', 100);
		$query = $this->pluginQuery($query);
		$db->setQuery($query);
		$res = $db->loadResultArray();
		if ((int)$fbConfig->get('filter_list_max', 100) == count($res)) {
			JError::raiseNotice(500, JText::sprintf('COM_FABRIK_FILTER_LIST_MAX_REACHED', $col));
		}
		FabrikHelperHTML::debug($query, 'filter:getColumnData query');
		if (is_null($res)) {
			$res = array();
		}
		return $res;
	}

	/**
	 * determine how the model does filtering and navigation
	 * @return string (ajax/post default post)
	 */

	function getPostMethod()
	{
		$params = &$this->getParams();
		if (is_null($this->_postMethod)) {
			$this->_postMethod = $params->get('ajax_nav', JRequest::getVar('_postMethod', 'post'));
		}
		return $this->_postMethod;
	}

	/**
	 * determine how the table should deail with add/edit links
	 * @return string (ajax/post default post)
	 */

	function getAddEditMethod()
	{
		$params = &$this->getParams();
		return $params->get('ajax_addedit', JRequest::getVar('_postMethod', 'post'));
	}

	/**
	 * get an array of the table's elements that match a certain plugin type
	 *
	 * @param string $plugin
	 * @return array matched element models
	 */

	function getElementsOfType($plugin)
	{
		$found = array();
		$groups =& $this->getFormGroupElementData();
		foreach ($groups as $groupModel) {
			$elementModels =& $groupModel->getMyElements();
			foreach ($elementModels as $elementModel) {
				$element =& $elementModel->getElement();
				if ($element->plugin == $plugin) {
					$found[] =  $elementModel;
				}
			}
		}
		return $found;
	}

	/**
	 * get all the elements in the table
	 * @param string key to key returned array on, currently accepts null, '', 'id', or 'filtername'
	 * @param bol show in table default true
	 * @return array table element models
	 */

	function getElements($key = 0, $showInTable = true)
	{
		if (!isset($this->elements)) {
			$this->elements = array();
		}
		$sig = $key.'.'.(int)$showInTable;
		if (!array_key_exists($sig, $this->elements)) {
			$this->elements[$sig] = array();
			$found = array();
			$this->elements[$key] = array();
			$groups =& $this->getFormGroupElementData();
			foreach (array_keys($groups) as $gid) {
				//foreach ($groups as $groupModel) { // $$$ rob dont do this as for some reason only fist groups elements are got when applying filters
				$groupModel = $groups[$gid];
				$elementModels =& $groupModel->getMyElements();
				foreach ($elementModels as $elementModel) {
					$element =& $elementModel->getElement();
					if ($element->state == 0) {
						continue;
					}
					switch ($key) {
						case 'filtername'://depreciated (except for querystring filters)
							// used id instead for filters
							// $$$ rob was incorrect for db joinelements with search type = field
							//$key =  $elementModel->getFullName(false, false, false);

							// $$$ rob hack to ensure that querystring filters dont use the concat string when getting the
							// dbkey for the element, otherwise related data doesn't work
							$origconcat = $elementModel->getParams()->get('join_val_column_concat');
							$elementModel->getParams()->set('join_val_column_concat', '');

							$dbkey = trim($elementModel->getFilterFullName());
							//$$$ rob if prefilter was using _raw field then we need to assign the model twice to both possible keys
							if ($elementModel->getElement()->plugin == 'fabrikdatabasejoin') {
								$dbkey2 =  FabrikString::safeColName($elementModel->getFullName(false, false, false));
								$this->elements[$sig][$dbkey2] = $elementModel;
							}
							$elementModel->getParams()->set('join_val_column_concat', $origconcat);
							//$$$ rob moved this to individual element getFilterFullName() as db join may not use it (when concat option set)
							//$dbkey = FabrikString::safeColName($dbkey);
							$this->elements[$sig][$dbkey] = $elementModel;
							break;
						case 'id':
							$this->elements[$sig][$element->id] = $elementModel;
							break;
						default:
							$this->elements[$sig][] =  $elementModel;
							break;
					}
				}
			}
		}
		return $this->elements[$sig];
	}

	/**
	 * determines if the talbe needs mocha js classes loaded
	 *
	 * @return bol true if required
	 */

	function requiresMocha()
	{
		if ($this->canCSVExport()) {
			return true;
		}
		$params =& $this->getParams();
		if ($params->get('advanced-filter') == 1) {
			return true;
		}
		$pluginManager =& $this->getPluginManager();
		$activePlugins = $params->get('plugin', array(), '_default', 'array');

		$tableplugis =& $pluginManager->getPlugInGroup('table');

		foreach ($tableplugis as $name => $plugin) {
			if (in_array($name, $activePlugins)) {
				if ($plugin->requiresMocha()) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * does the table need to include the slimbox js code
	 *
	 * @return bol
	 */

	function requiresSlimbox()
	{
		$form =& $this->getForm();
		$groups =& $form->getGroupsHiarachy();
		foreach ($groups as $group) {
			$elements =& $group->getPublishedElements();
			foreach ($elements as $elementModel) {
				$element =& $elementModel->getElement();
				if (isset($element->show_in_table_summary) && $element->show_in_table_summary && $elementModel->requiresLightBox()) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * get pluginmanager (get reference to form's plugin manager
	 *
	 * @return object plugin manager model
	 */

	function getPluginManager()
	{
		$form =& $this->getForm();
		return $form->getPluginManager();
	}

	/**
	 * called via advanced search to load in a given element filter
	 * @return string html for filter
	 */

	function getAdvancedElementFilter()
	{
		$element = JRequest::getVar('element');
		$elementid = JRequest::getVar('elid');
		$pluginManager =& $this->getPluginManager();
		$className = JRequest::getVar('plugin');
		$plugin =& $pluginManager->getPlugIn($className, 'element');
		$plugin->setId($elementid);
		$el =& $plugin->getElement();
		return $plugin->getFilter(JRequest::getInt('counter', 0), false);
	}

	/**
	 * build the table's add record link
	 * if a querystring filter has been passed in to the table then apply this to the link
	 * this means that table->faceted table->add will auto select the data you browsed on
	 * @return string url
	 */

	function getAddRecordLink()
	{
		$qs = array();
		global $Itemid;
		$filters =& $this->getRequestData();
		$keys = JArrayHelper::getValue($filters, 'key', array());
		$vals = JArrayHelper::getValue($filters, 'value', array());
		$types = JArrayHelper::getValue($filters, 'search_type', array());
		for ($i = 0; $i < count($keys); $i++) {
			if (JArrayHelper::getValue($types, $i, '') === 'querystring') {
				$qs[] = FabrikString::safeColNameToArrayKey($keys[$i]) . '_raw=' .  $vals[$i];
			}
		}
		// $$$ rob needs the item id for when sef urls are turned on
		if (JRequest::getCmd('option') !== 'com_fabrik') {
			$qs[] ='Itemid='.$Itemid;
		}
		$qs = implode('&', $qs);
		$table =& $this->getTable();
		$app =& JFactory::getApplication();
		if ($app->isAdmin()) {
			return ($this->getAddEditMethod() == 'ajax') ? "#" : JRoute::_("index.php?option=com_fabrik&c=form&task=form&fabrik=" . $table->form_id . "&tableid=" . $this->_id ."&rowid=0&$qs");
		} else {
			return ($this->getAddEditMethod() == 'ajax') ? "#" : JRoute::_("index.php?option=com_fabrik&c=form&view=form&fabrik=" . $table->form_id . "&tableid=" . $this->_id ."&rowid=0&$qs");
		}
	}

	function getElementJs()
	{
		$document =& JFactory::getDocument();
		$form =& $this->getForm();
		$script = '';
		$groups =& $form->getGroupsHiarachy();
		$run = array();
		foreach ($groups as $groupModel) {
			$elementModels =& $groupModel->getPublishedElements();
			foreach ($elementModels as $elementModel) {
				$element = $elementModel->getElement();
				if (!in_array($element->plugin, $run)) {
					$run[] = $element->plugin;
					$elementModel->tableJavascriptClass();
				}
				$script .= $elementModel->elementTableJavascript();
			}
		}
		if ($script !== '') {
			$script = "window.addEvent('domready', function(){\n". $script . "});\n";
			FabrikHelperHTML::addScriptDeclaration($script);
		}
	}

	/**
	 * return the url for the table's form - this url is used when submitting searches, and ordering
	 * @return string action url
	 */

	function getTableAction()
	{
		if (isset($this->tableAction)) {
			return $this->tableAction;
		}
		$option = JRequest::getCmd('option');
		// Get the router
		$app	= &JFactory::getApplication();
		$router = &$app->getRouter();

		$uri = clone(JURI::getInstance());
		// $$$ rob force these to be 0 once the menu item has been loaded for the first time
		//subsequent loads of the link should have this set to 0. When the menu item is re-clicked
		//rest filters is set to 1 again
		//if ($router->getVar('resetfilters') == 1) {
		$router->setVar('resetfilters', 0);
		//}
		if ($option !== 'com_fabrik') {
			// $$$ rob these can't be set by the menu item, but can be set in {fabrik....}
			$router->setVar('clearordering', 0);
			$router->setVar('clearfilters', 0);
		}
		$queryvars = $router->getVars();
		$form =& $this->getForm();
		$page = "index.php?";
		foreach ($queryvars as $k => $v) {
			$el = $form->getElement($k);
			if (is_array($v)) {
				// $$$ rob if you were using URL filters such as
				//&jos_fabble_activity___create_date[value][]=now&jos_fabble_activity___create_date[value][]=%2B2%20week&jos_fabble_activity___create_date[condition]=BETWEEN
				// then we don't want to re-add them to the table action.
				// Instead they are aded to the filter sessions and reapplied that way
				// otherwise we ended up with elementname=Array in the query string
				if ($el === false) {
					$qs[] = "$k=$v";
				}
			} else {
				if ($el === false) {
					$qs[] = "$k=$v";
				} else {
					//check if its a tag element if it is we want to clear that when we clear the form
					// (if the filter was set via the url we generally want to keep it though
					if($el->getElement()->plugin !== 'fabriktextarea' && $el->getParams()->get('textarea-tagify') !== true) {
						$qs[] = "$k=$v";
					}
				}
			}
		}
		$action = $page . implode("&amp;", $qs);
		//limitstart gets added in the pageination model
		$action = preg_replace("/limitstart{$this->_id}=(.*)?(&|)/", "", $action);
		$action = FabrikString::rtrimword($action, "&");
		$this->tableAction 	= JRoute::_($action);
		return $this->tableAction;
	}

	/** allow plugins to add arbitrary WHERE clauses.  Gets checked in _buildQueryWhere().
	 *  always gets added with AND to the end of any other where clauses
	 * @param string plugin name
	 * @param string where clause (WITHOUT prepended where/and etc)
	 * @return bol
	 */

	function setPluginQueryWhere($pluginName, $whereClause)
	{
		// strip any prepended conditions off
		$whereClause = preg_replace('#(^where |^and |^or )#','',$whereClause);
		// only do anything if it's a different clause ...
		// if it's the same, no need to clear the table data, can use cached
		if (!array_key_exists($pluginName,$this->_pluginQueryWhere) || $whereClause != $this->_pluginQueryWhere[$pluginName]) {
			// set the internal data, which will get used in _buildQueryWhere
			$this->_pluginQueryWhere['chart'] = $whereClause;
			// as we are modifying the main getData query, we need to make sure and
			// clear table data, forcing next getData() to do the query again, no cache
			$this->set('_data', NULL);
		}
		// return true just for the heck of it
		return true;
	}

	/** plugins sometimes need to clear their where clauses
	 * @param string plugin name
	 * @return bol
	 */
	function unsetPluginQueryWhere($pluginName)
	{
		if (array_key_exists($pluginName, $this->_pluginQueryWhere)) {
			unset($this->_pluginQueryWhere[$pluginName]);
		}
		return true;
	}

	/**
	 * if all filters are set to read only then don't return a clear button
	 * otherwised do
	 * @return string clear filter button link
	 */

	function getClearButton()
	{
		$filters = $this->getFilters('tableform_'. $this->_id, 'table');
		return count($filters) > 0 || count($this->getAdvancedFilterValues()) > 0 ? '<a href="#" class="clearFilters">'.JText::_('CLEAR').'</a>' : '';
	}

	/**
	 * get the join display mode - merge or normal
	 * @return bool true if merge
	 */

	protected function mergeJoinedData()
	{
		$params =& $this->getParams();
		$fbConfig =& JComponentHelper::getParams('com_fabrik');
		$display = $params->get('join-display', '');
		if ($display == '') {
			$merge = $fbConfig->get('use_wip', false);
		} else {
			$merge = $display == 'merge' ? true : false;
		}
		return $merge;
	}

	/**
	 * Collapses 'repeated joined' rows into a single row.
	 * If a group is not repeating we just use the first row's data (as subsequent rows will contain the same data
	 * Otherwise if the group is repeating we append each repeated record's data into the first row's data
	 * All rows execpt the first row for each group are then unset (as unique subsequent row's data will be contained within
	 * the first row)
	 * @param array $data
	 */

	function formatForJoins(&$data)
	{
		$merge = $this->mergeJoinedData();
		if (empty($merge)) {
			return;
		}
		$app =& JFactory::getApplication();
		$tableid = $this->getTable()->id;
		$dbprimaryKey = FabrikString::safeColNameToArrayKey($this->getTable()->db_primary_key);
		$formModel =& $this->getForm();
		foreach ($data as $groupk => $group) {
			$last_pk = '';
			$last_i = 0;
			$count = count($group);
			$can_repeats = array();
			for ($i = 0; $i < $count; $i++) {

				// $$$rob if rendering J article in PDF format __pk_val not in pdf table view
				//$next_pk = isset($data[$groupk][$i]->__pk_val) ? $data[$groupk][$i]->__pk_val : $data[$groupk][$i]->id;
				$next_pk = isset($data[$groupk][$i]->__pk_val) ? $data[$groupk][$i]->__pk_val : $data[$groupk][$i]->$dbprimaryKey;
				if (!empty($last_pk) && ($last_pk == $next_pk)) {
					foreach ($data[$groupk][$i] as $key => $val) {
						$origKey = $key;
						$tmpkey = FabrikString::rtrimword( $key, '_raw');
						// $$$ hugh - had to cache this stuff, because if you have a lot of rows and a lot of elements,
						// doing this many hundreds of times causes huge slowdown, exceeding max script execution time!
						// And we really only need to do it once for the first row.
						if (!isset($can_repeats[$tmpkey])) {
							$elementModel =& $formModel->getElement($tmpkey);
							$can_repeats[$tmpkey] = $elementModel ? $elementModel->getGroup()->canRepeat() : 0;
						}
						if (preg_match("#(^_|^fabrik_|^slug$|_table_heading$|_form_heading$)#", $key)) {
							continue;
						}
						if (isset($data[$groupk][$last_i]->$key) && $can_repeats[$tmpkey]) {
							// $$$ rob - what about data like yes/no where each viewable option needs to be shown?
							// $$$ rob - yeah commenting this out, not a good idea - as other cols for this record may have
							// different data so you end up with this col having 3 entries and the next col having four and you can't
							// see the correlation between the two sets of data.
							//if ($data[$groupk][$last_i]->$key != $val) {

							// $$$ rob meant that only one link to details was shown in admin - dont see the point in that
							//if (preg_match("#\W+fabrik___rowlink.*\W+tableid=$tableid\W#",$val)) {
							//continue;
							//}
							if ($origKey == $tmpkey) {
								$data[$groupk][$last_i]->$key .= "<br >\n" . $val;
							} else {
								$data[$groupk][$last_i]->$origKey .= GROUPSPLITTER.$val;
							}
						}
					}
					unset($data[$groupk][$i]);
					continue;
				}
				else {
					$last_i = $i;
					// $$$rob if rendering J article in PDF format __pk_val not in pdf table view
					$last_pk = $next_pk;
				}
			}
			// $$$ rob ensure that we have a sequental set of keys otherwise ajax json will turn array into object
			$data[$groupk] = array_values($data[$groupk]);
		}
	}

	function noTable()
	{
		return empty($this->_id);
	}

	/**
	 *
	 * save an individual element value to the fabrik db
	 *
	 * @param string $rowId
	 * @param string $key
	 * @param string $value
	 */

	public function storeCell($rowId, $key, $value)
	{
		$data[$key] = $value;
		$this->storeRow($data, $rowId);
	}

	/**
	 *
	 * increment a value in a cell
	 * @param string $rowId
	 * @param strin $key
	 * @param string $dir -1/1 etc
	 */

	public function incrementCell($rowId, $key, $dir)
	{
		$db =& $this->getDb();
		$table =& $this->getTable();
		$query = "UPDATE $table->db_table_name SET $key = COALESCE($key,0)  + $dir WHERE $table->db_primary_key = ".$db->Quote($rowId);
		$db->setQuery($query);
		return $db->query();
	}

	/**
	 * save the table from admin
	 *
	 * @return Jerror if not saved true if saved ok
	 */

	function save()
	{
		$session =& JFactory::getSession();
		$app 				=& JFactory::getApplication();
		$db 				=& JFactory::getDBO();
		$user 			=& JFactory::getUser();
		$config 		=& JFactory::getConfig();
		$id 				= JRequest::getInt('id', 0, 'post');
		$this->setId($id);
		$row 				=& $this->getTable(false, false);
		$formModel 	=& JModel::getInstance('Form', 'FabrikModel');

		$post				= JRequest::get('post');

		if (!$row->bind($post)) {
			return JError::raiseWarning(500, $row->getError());
		}

		$filter	= new JFilterInput(null, null, 1, 1);
		$introduction = JRequest::getVar('introduction', '', 'post', 'string', JREQUEST_ALLOWRAW);
		$row->introduction = $filter->clean($introduction);

		$details	= JRequest::getVar('details', array(), 'post', 'array');
		$row->bind($details);

		$aOrderBy = JRequest::getVar('order_by', array(), 'post', 'array');
		$row->order_by = implode(GROUPSPLITTER2, $aOrderBy);

		$aOrderDir = JRequest::getVar('order_dir', array(), 'post', 'array');
		$row->order_dir = implode(GROUPSPLITTER2, $aOrderDir);

		if (!$row->check()) {
			$app->setError($row->getError());
			return JError::raiseWarning(500, $row->getError());
		}

		if ($id == 0) {
			$newtable = trim(JRequest::getVar('_database_name', '', 'post'));
			// $$$ hugh - added some more sanity checking on table name, get rid of non-alphanumeric and _
			// @TODO - should prolly use a helper for this, like FabrikString::clean()
			// but need to think about case issues first
			$newtable = preg_replace('#[^0-9a-zA-Z_]#', '_', $newtable);

			//check the entered database table doesnt already exist
			if ($newtable != '' && $this->databaseTableExists($newtable)) {
				return JError::raiseWarning(500, JText::_('DATABASE TABLE ALREADY EXISTS'));
			}

			if (!$this->canCreateDbTable()) {
				return JError::raiseWarning(500, Jtext::_('YOUR_DB_USER_HAS_INSUFFICIENT_RIGHTS_TO_CREATE_TABLE'));
			}
			//create fabrik form
			$formModel =& $this->_createLinkedForm();

			//create fabrik group

			$groupData = array("name" => $row->label , "label" => $row->label);

			JRequest::setVar('_createGroup', 1, 'post');
			if ($newtable != '') {
				$groupId = $this->_createLinkedGroup($groupData, false);
				$row->db_table_name = $newtable;
				$row->db_primary_key = "`".$newtable.'`.`id`';
				$row->auto_inc = 1;
				$res = $this->createDBTable($formModel, $newtable);
			} else {
				// 2.0.5 balsamiq to fabrik tmpl generator sets up group properties in session, if we find those lets make
				// n groups each with their own groupid
				$groupDatas = array($groupData);
				if ($session->has('com_fabrik.table.create.groupmap')) {
					$groupDatas =& $session->get('com_fabrik.table.create.groupmap');
				}
				$groupMap = array();
				foreach ($groupDatas as $x => $groupData) {
					$groupId = $this->_createLinkedGroup($groupData, false);
					$groupMap[$x] = $groupId;
				}
				//set element group ids
				if ($session->has('com_fabrik.table.create.elementmap')) {
					$map = (array)$session->get('com_fabrik.table.create.elementmap');
					foreach ($map as &$m) {
						$m['groupid'] = $groupMap[$m['groupid']];
					}
					$session->set('com_fabrik.table.create.elementmap', $map);
				}
				// save elements in group
				foreach ($groupMap as $groupId) {
					$this->_createLinkedElements($groupId, $post);
				}

			}
			//	set the tables form id
			$this->_updateFormId($formModel->_form->id);
		}

		// 	save params - this file no longer exists? do we use models/table.xml instead??
		$params = new fabrikParams($row->attribs, JPATH_COMPONENT.DS.'xml'.DS.'table.xml');

		$row->attribs = $params->updateAttribsFromParams(JRequest::getVar('params', array(), 'post', 'array'));
		$row->rows_per_page 	= JRequest::getInt('rows_per_page', 10, 'post');

		if ($row->id != 0) {
			$datenow =& JFactory::getDate();
			$row->modified 		= $datenow->toMySQL();
			$row->modified_by 	= $user->get('id');
		}
		FabrikHelper::prepareSaveDate($row->publish_down);
		FabrikHelper::prepareSaveDate($row->created);
		FabrikHelper::prepareSaveDate($row->publish_up);

		$pk = JRequest::getVar('db_primary_key');
		if ($pk == '') {
			$aKey = $this->getPrimaryKeyAndExtra();
			$aKey = $aKey[0];
			$row->db_primary_key = "`".$row->db_table_name . "`.`" . $aKey['colname'] . "`";
			$row->auto_inc = stristr($aKey['extra'], 'auto_increment') ? true : false;
		}

		if (!$row->store()) {
			return JError::raiseWarning(500, $row->getError());
		}

		// load in all the tables data - even if it wasnt in the post data
		$table =& $this->getTable();
		//needed if saving a table for first time (otherwise id = 0)
		$this->setId($table->id);
		$this->updateJoins();
		if (!$this->isView()) {
			// this was only run on a new table - but I've put it here so that if you upload a new table you can ensure that its columns are fixed
			$this->makeSafeTableColumns();
			$this->updatePrimaryKey($row->db_primary_key, $row->auto_inc);
		}
		$row->checkin();

		//make an array of elments and a presumed index size
		//map is then used in creating indexes
		$map = array();
		$groups =& $this->getForm()->getGroupsHiarachy();
		foreach ($groups as $groupModel) {
			$elementModels =& $groupModel->getMyElements();
			foreach ($elementModels as $element) {
				//int elements cant have a index size attrib
				// $$$ hugh neither can DATETIME
				$coltype = $element->getFieldDescription();
				if (JString::stristr($coltype, 'int')) {
					$size = '';
				}
				else if (JString::stristr($coltype, 'datetime')) {
					$size = '';
				}
				else {
					$size = 10;
					// $$$ hugh - adding index will barf if key size > varchar size.
					$matches = array();
					if (preg_match('/varchar\((\d+)\)/i',$coltype,$matches)) {
						$varchar_size = (int)$matches[1];
						if ($varchar_size < 10) {
							$size = $varchar_size;
						}
					}
				}
				$map[$element->getFullName(false, false, false)] = $size;
				$map[$element->getElement()->id] = $size;
			}
		}

		//update indexes (added array_key_exists check as these may be during after CSV import)
		if (!empty($aOrderBy) && array_key_exists($row->order_by, $map)) {
			foreach ($aOrderBy as $orderBy) {
				if (array_key_exists($orderBy, $map)) {
					$this->addIndex($orderBy, 'tableorder', 'INDEX', $map[$orderBy]);
				}
			}
		}
		if ($row->group_by !== '' && array_key_exists($row->group_by, $map)) {
			$this->addIndex($row->group_by, 'groupby', 'INDEX', $map["$row->group_by"]);
		}
		if ($params->get('group_by_order') !== '') {
			$this->addIndex($params->get('group_by_order'), 'groupbyorder', 'INDEX', $map[$params->get('group_by_order')]);
		}
		$afilterFields = $params->get('filter-fields', '', '_default', 'array');
		foreach ($afilterFields as $field) {
			$field = str_replace('`', '', $field);
			$this->addIndex($field, 'prefilter', 'INDEX', $map[$field]);
		}
		$this->createCacheQuery();

		if (JFolder::exists(JPATH_ADMINISTRATOR.DS.'components'.DS.'com_joomfish'.DS.'contentelements')) {
			if ($params->get('allow-data-translation')) {
				if (!$this->makeJoomfishXML()) {
					JError::raiseNotice('E_ERROR', JTEXT::_("Unable to make Joomfish XML file"));
				}
			} else {
				$this->removeJoomfishXML();
			}
		}
		return true;
	}

	/**
	 * copy a table - copies form, group, formgroups, elements and validations
	 * @return unknown_type
	 */

	function copy()
	{
		$oldid = $this->_id;
		$db =& JFactory::getDBO();
		$user =& JFactory::getUser();
		$table =& $this->getTable();
		$table->id = 0;
		$names = JRequest::getVar('names', array());
		$names = JArrayHelper::getValue($names, $oldid, array());

		$table->label = JArrayHelper::getValue($names, 'tableLabel', $table->label);

		JRequest::setVar('newFormLabel', JArrayHelper::getValue($names, 'formLabel'));
		JRequest::setVar('newGroupNames', JArrayHelper::getValue($names, 'groupNames', array()));

		$formModel =& $this->_createLinkedForm($table->form_id);
		$this->_updateFormId($formModel->_form->id);

		$createdate =& JFactory::getDate();
		$createdate = $createdate->toMySQL();
		$table->created = $createdate;
		$table->modified = $db->getNullDate();
		$table->modified_by = $user->get('id');
		$table->hits = 0;
		$table->checked_out = 0;
		$table->checked_out_time = $db->getNullDate();
		if (!$table->store()) {
			return JError::raiseWarning(500, $table->getError());
		}
		$this->_id = $table->id;
		//test for seeing if joins correctly stored when coping new table
		$this->copyJoins($oldid, $table->id, $formModel->groupidmap);
	}

	/**
	 * translation has been turned off for the table so delete the content
	 * element xml file
	 */

	private function removeJoomfishXML()
	{
		$file = JPATH_ADMINISTRATOR.DS.'components'.DS.'com_joomfish'.DS.'contentelements'.DS.'fabrik-' . $this->getTable()->db_table_name . '.xml';
		if (JFile::exists($file)) {
			JFile::delete($file);
		}
	}

	/**
	 * write out the Joomfish contentelement xml file for the tables elements
	 * @return bol true if written out ok
	 */

	private function makeJoomfishXML()
	{
		$config =& JFactory::getConfig();
		$db =& JFactory::getDBO();
		$elements =& $this->getElements();

		//get all database join elements and check if we need to create xml files for them

		$table =& $this->getTable();
		$tableName = str_replace($config->getValue('dbprefix'), '',$table->db_table_name);
		$params =& $this->getParams();
		$titleElement = $params->get('joomfish-title');
		$str = '<?xml version="1.0" ?>
<joomfish type="contentelement">
  <name>Fabrik - ' . $table->label . '</name>
  <author>rob@pollen-8.co.uk</author>
  <version>1.0 for Fabrik 2.0</version>
  <description>Definition for Fabrik Table data - ' . $table->label . '</description>
  <reference type="content">
  	<table name="' . $tableName . '">';
		$titleset = false;
		foreach ($elements as $element) {
			if ($table->db_primary_key == FabrikString::safeColName($element->getFullName(false, false, false )))  {
				//primary key element
				$type = 'referenceid';
				$t = 0;
			} else {
				if (!$titleset && $titleElement == '') {
					$type ='titletext';
					$titleset = true;
				} else {
					if ($titleElement == $element->getFullName(false, false, false)) {
						$type ='titletext';
						$titleset = true;
					} else {
						$type = $element->getJoomfishTranslationType();
					}
				}
				$t = $element->getJoomfishTranslatable();
			}
			$opts = $element->getJoomfishOptions();
			$el = $element->getElement();
			$str .= "\n\t\t" . '<field type="'.$type.'" name="'.$el->name.'" translate="'.$t.'"';
			foreach ($opts as $k=>$v) {
				$str .= " $k=\"$v\"";
			}
			$str .='>' . $el->label . '</field>';
		}
		$str .='
  	</table>
  </reference>
</joomfish>';
		//file name HAS to be the same as the table name MINUS db extension
		return JFile::write( JPATH_ADMINISTRATOR.DS.'components'.DS.'com_joomfish'.DS.'contentelements'.DS.$tableName.'.xml', $str);
	}

	/**
	 * when saving a table that links to a database for the first time we
	 * automatically create a form to allow the update/creation of that tables
	 * records
	 * @access private
	 * @param int form id to copy from. If = 0 then create a default form. If not 0 then copy the form id passed in
	 * @return object form model
	 */

	function &_createLinkedForm($formid = 0)
	{
		$config		=& JFactory::getConfig();
		$user			=& JFactory::getUser();
		if ($formid == 0) {
			jimport('joomla.utilities.date');
			$createdate =& JFactory::getDate();
			$createdate = $createdate->toMySQL();

			$form = JTable::getInstance('Form', 'Table');
			$table =& $this->getTable();
			$form->label							= $table->label;
			$form->record_in_database = 1;

			$form->created 						= $createdate;
			$form->created_by 				= $user->get('id');
			$form->created_by_alias 	= $user->get('username');
			$form->error							= JText::_('SOME PARTS OF YOUR FORM HAVE NOT BEEN FILLED IN');
			// @TODO : fabrik : 0.5 : create a overall fabrik params admin page
			//which sets the default settings for all forms, from which these settings below can be populated
			$form->submit_button_label = JRequest::getVar('submit_button_label', JText::_('SAVE'));
			$form->state							= $table->state;
			$form->form_template			= JRequest::getCmd('form_template', 'default');
			$form->view_only_template	= JRequest::getCmd('view_only_template', 'default');


			//2./0.5 see if any params have been posted if so whack em in the form attribs
			$attribs = array();
			$post = JRequest::get('post');
			foreach ($post['params'] as $key => $val) {
				if (!is_array($val)) {
					$attribs[] = "$key=$val";
				}
			}
			$form->attribs = implode("\n", $attribs);

			if(! $form->store()) {
				return JError::raiseError(500, $form->getError());
			}

			$this->_oForm =& JModel::getInstance('Form', 'FabrikModel');
			$this->_oForm->setId($form->id);
			$this->_oForm->getForm();
			return $this->_oForm;
		} else {
			$this->_oForm =& JModel::getInstance('Form', 'FabrikModel');
			$this->_oForm->setId($formid);
			$this->_oForm->copy();
			$this->_oForm->getForm();
			return $this->_oForm;
		}
	}

	/**
	 * create a group
	 * used when creating a fabrik table from an existing db table
	 *
	 * NEW also creates the formgroup
	 *
	 * @access private
	 * @param array group data
	 * @param bol is the group a join default false
	 * @return int group id
	 */

	function _createLinkedGroup($data, $isJoin = false)
	{
		$user 			=& JFactory::getUser();
		$createdate = JFactory::getDate();
		$createdate = $createdate->toMySQL();
		$group 										=& JTable::getInstance('Group', 'Table');
		$group->bind( $data);
		$group->created 					= $createdate;
		$group->created_by 				= $user->get('id');
		$group->created_by_alias 	= $user->get('username');
		$group->state 						= 1;
		$group->attribs = "repeat_group_button=0
repeat_group_show_first=1
repeat_group_js_add=
repeat_group_js_delete=";
		$group->is_join = ($isJoin == true) ? 1 : 0;

		$group->store();
		if (!$group->store()) {
			JError::raiseError(500, $group->getError());
		}

		//create form group
		$formid = $this->_oForm->_id;
		$formGroup = JTable::getInstance('FormGroup', 'Table');
		$formGroup->form_id = $formid;
		$formGroup->group_id = $group->id;
		$formGroup->ordering = 999999;
		if (!$formGroup->store()) {
			JError::raiseError(500, $formGroup->getError());
		}

		$formGroup->reorder(" form_id = '$formid'");
		return $group->id;
	}

	/**
	 * when saving a table that links to a database for the first time we
	 * need to create all the elements based on the database table fields and their
	 * column type
	 *
	 * @access private
	 * @param int group id
	 * @param array newly created table data
	 * @param array of element objects - if this is not empty then we've come from the csv import and the elements
	 * have already been defined, use this instead of the field analysis to create correctly typed elements
	 */

	function _createLinkedElements($groupId, $aTableData, $aSpecificElements = array())
	{
		$db 			=& JFactory::getDBO();
		$user  		= &JFactory::getUser();
		$session 	=& JFactory::getSession();
		$config		=& JFactory::getConfig();
		$createdate = JFactory::getDate();
		$createdate = $createdate->toMySQL();
		$tableName = JRequest::getVar('db_table_name');
		$pluginManager =& $this->getPluginManager();
		$ordering = 0;
		$fabrikDb =& $this->getDb();
		if (!empty($aSpecificElements)) {
			//we're asking the method to create predefined elements - e.g. when installing sample data.
			foreach ($aSpecificElements as $elementModel) {
				$element =& $elementModel->getElement();
				if ($element->label == 'id') {
					$element->hidden = 1;
					$element->primary_key = 1;
					$element->auto_increment = 1;
				} else {
					$element->hidden = 0;
				}
				$element->name = strtolower(str_replace(' ', '', $element->name));
				$element->group_id = $groupId;
				$element->created = $createdate;
				$element->created_by = $user->get('id');
				$element->created_by_alias = $user->get('username');
				$element->state = 1;
				$element->show_in_table_summary = 1;
				$element->width = 30;
				$element->height = 6;
				$element->ordering = 99999;
				$element->attribs = $elementModel->getDefaultAttribs();
				$element->store();
				$where = " group_id = ".(int)$element->group_id;
				$element->reorder( $where);
			}
		} else {
			//here we're importing directly from the database schema
			$db->setQuery("SELECT id FROM #__fabrik_tables WHERE ".$db->nameQuote('db_table_name')." = ".$db->Quote($tableName));
			$id = $db->loadResult();
			$elementModel =& JModel::getInstance('Element', 'FabrikModel');
			if ($id) {
				//a fabrik table already exists - so we can copy the formatting of its elements

				$groupTableModel = JModel::getInstance('table', 'FabrikModel');

				$groupTableModel->setId($id);
				$table = $groupTableModel->getTable();
				//$this->_oForm = null; //reset form so that it loads new table form
				$groups = $groupTableModel->getFormGroupElementData();
				$newElements = array();
				foreach ($groups as $groupModel) {
					// if we are saving a new table and the previously found tables group is a join
					// then don't add its elements to the table as they don't exist in the database table
					// we are linking to
					// $$$ hugh - removed id == 0 test, as this gets called from _makeNewJoin as well, see #608
					//if ($groupModel->isJoin() && JRequest::getCmd('task') == 'save' && JRequest::getInt('id') == 0) {
					if ($groupModel->isJoin() && JRequest::getCmd('task') == 'save') {
						continue;
					}
					$elementModels =& $groupModel->getMyElements();
					foreach ($elementModels as $elementModel) {
						$element =& $elementModel->getElement();
						$copy = $elementModel->copyRow($element->id, '', $groupId);
						$newElements[$element->id] =  $copy->id;
					}

				}

				$elementModel =& JModel::getInstance('Element', 'FabrikModel');
				foreach ($newElements as $origId => $newId) {
					$plugin = $pluginManager->getElementPlugin( $newId);
					$plugin->finalCopyCheck( $newElements);
				}
			} else {

				//$$$ rob since 2.0.4 - lets see if theres a session element / plugin map set up (used in balsamiq to fabrik component)
				$map = (array)$session->get('com_fabrik.table.create.elementmap');
				$elementTypes = JRequest::getVar('elementtype', array(), 'default', 'array');
				$fields = $fabrikDb->getTableFields(array ("`$tableName`"));
				$fields = $fields["`$tableName`"];
				$key = $this->getPrimaryKeyAndExtra($tableName);

				// no existing fabrik table so we take a guess at the most
				//relavent element types to  create
				$elementLabels = JRequest::getVar('elementlabels', array(), 'default', 'array');
				foreach ($fields as $label => $type) {
					$element =& JTable::getInstance('Element', 'Table');
					$info = array();
					$element->sub_values = '';
					$element->sub_labels = '';
					//$element->label = str_replace("_", " ", $label);
					if (array_key_exists($ordering, $elementTypes)) {
						//if importing from a CSV file then we have userselect field definitions
						$plugin = $elementTypes[$ordering];
					} else {
						//$$$ rob since 2.0.4 - lets see if theres a session element / plugin map set up (used in balsamiq to fabrik component)
						if (array_key_exists($tableName.'___'.$label, $map)) {
							$info = $map[$tableName.'___'.$label];
							//check its in the right group (or not assinged if from tbl)
							if (!is_null($info['groupid']) && $info['groupid'] == $groupId) {
								$plugin = $info['plugin'];
								$element->bind($info);
							} else {
								continue;
							}
						} else {
							//if the field is the primary key and it's an INT type set the plugin to be the fabrik internal id
							if ($key[0]['colname'] == $label && strtolower(substr($key[0]['type'], 0, 3)) === 'int') {
								$plugin = 'fabrikinternalid';
							} else {
								//otherwise guestimate!
								switch ($type) {
									case "int" :
									case "tinyint" :
									case "varchar" :
										$plugin = 'fabrikfield';
										break;
									case "text" :
									case "tinytext" :
									case "mediumtext" :
									case "longtext" :
										$plugin = 'fabriktextarea';
										break;
									case "datetime" :
									case "date" :
									case "time" :
									case "timestamp" :
										$plugin = 'fabrikdate';
										break;
									default :
										$plugin = 'fabrikfield';
										break;
								}
							}
						}
					}
					if ($plugin == '') {
						continue;
					}
					$element->plugin 						= $plugin;
					$element->hidden 						= $element->label == 'id' ? '1' : '0';
					$element->group_id 					= $groupId;
					$element->name				 			= $label;
					$element->created 					= $createdate;
					$element->created_by 				= $user->get('id');
					$element->created_by_alias 	= $user->get('username');
					$element->state 						= '1';
					$element->show_in_table_summary = '1';
					switch ($plugin) {
						case 'fabriktextarea':
							$element->width = '40';
							break;
						case 'fabrikdate':
							$element->width = '10';
							break;
						default:
							$element->width = '30';
							break;
					}
					$a = new fabrikParams($elementModel->getDefaultAttribs(), JPATH_SITE . '/administrator/components/com_fabrik/models/element.xml');
					$aparams = JArrayHelper::getValue($info, 'params', array());
					$a->bind($aparams);
					$element->height 						= '6';
					$element->ordering 					= $ordering;
					$element->attribs 					= $a->toString();
					if ($element->label == '') {
						$element->label = JArrayHelper::getValue($elementLabels, $ordering, str_replace("_", " ", $label));
					}
					if (!$element->store()) {
						return JError::raiseError(500, $element->getError());
					}

					$elementModel =& $pluginManager->getPlugIn($element->plugin, 'element');
					$elementModel->setId($element->id);
					$elementModel->_element = $element;
					// hack for user element
					$details = array('group_id' => $element->group_id);
					JRequest::setVar('details', $details);

					$elementModel->onSave();
					$ordering ++;
				}
			}
		}
	}


	/**
	 * when copying a table we need to copy its joins as well
	 * note that the group and elements already exists - just the join needs to be saved
	 * @param int $fromid table id to copy from
	 * @param int table id to copy to
	 * @param array group id map saying which groups got copied to which new group id (key = old id, value = new id)
	 * @return null
	 */

	function copyJoins($fromid, $toid, $groupidmap)
	{
		$db =& JFactory::getDBO();
		$db->setQuery("SELECT * FROM #__fabrik_joins WHERE table_id = ".(int)$fromid);
		$joins = $db->loadObjectList();
		foreach ($joins as $join) {
			$size = 10;
			$els =& $this->getElements();
			foreach ($els as $el) {
				if( $el->getElement()->name == $join->table_key) {
					$size = JString::stristr($el->getFieldDescription(), 'int') ? '' : '10';
				}
			}
			$this->addIndex($join->table_key, 'join', 'INDEX', $size);
			$joinTable =& JTable::getInstance('Join', 'Table');
			$joinTable->load($join->id);
			$joinTable->id = 0;
			$joinTable->group_id = $groupidmap[$joinTable->group_id];
			$joinTable->table_id 		= $toid;
			if (!$joinTable->store()) {
				return JError::raiseWarning(500, $join->getError());
			}
		}
	}

	/**
	 * deals with ensuring joins are managed correctly when table is saved
	 */

	function updateJoins()
	{
		$db =& JFactory::getDBO();
		// $$$rob getJoins adds in element joins as well - dont use as we can get ids to delete that
		//arent table joins
		//$aOldJoins 			= $this->getJoins();
		$db->setQuery("SELECT * FROM #__fabrik_joins WHERE table_id = ".(int)$this->_id);

		$aOldJoins = $db->loadObjectList();

		$aOldJoinsToKeep 	= array();
		$joinModel			=& JModel::getInstance('Join', 'FabrikModel');
		$joinIds 				= JRequest::getVar('join_id', array(), 'post');
		$joinTypes 			= JRequest::getVar('join_type', array(), 'post');
		$joinTableFrom  = JRequest::getVar('join_from_table', array(), 'post');
		$joinTable 			= JRequest::getVar('table_join', array(), 'post');
		$tableKey				= JRequest::getVar('table_key', array(), 'post');
		$joinTableKey		= JRequest::getVar('table_join_key', array(), 'post');
		$groupIds				= JRequest::getVar('group_id', array(), 'post');
		for ($i = 0; $i < count($joinTypes); $i++) {
			$existingJoin = false;
			foreach ($aOldJoins as $oOldJoin) {
				if ($joinIds[$i] == $oOldJoin->id) {
					$existingJoin = true;
				}
			}
			//$$$rob make an index on the join element (fk)
			$els =& $this->getElements();
			foreach ($els as $el) {
				if ($el->getElement()->name == $tableKey[$i]) {
					$size = JString::stristr($el->getFieldDescription(), 'int') ? '' : '10';
				}
			}
			$this->addIndex($joinTableFrom[$i].'___'.$tableKey[$i], 'join', 'INDEX', $size);

			if (!$existingJoin) {
				$this->_makeNewJoin( $tableKey[$i], $joinTableKey[$i], $joinTypes[$i], $joinTable[$i], $joinTableFrom[$i]);
			} else {
				/* load in the exisitng join
				 * if the table_join has changed we need to create a new join
				 * (with its corresponding group and elements)
				 *  and mark the loaded one as to be deleted
				 */
				$joinModel->setId($joinIds[$i]);
				$joinModel->_join = null;
				$join =& $joinModel->getJoin();

				if ($join->table_join != $joinTable[$i]) {
					$this->_makeNewJoin( $tableKey[$i], $joinTableKey[$i], $joinTypes[$i], $joinTable[$i], $joinTableFrom[$i]);
				} else {
					//the talbe_join has stayed the same so we simply update the join info
					$join->table_key 		= str_replace('`', '', $tableKey[$i]);
					$join->table_join_key 	= $joinTableKey[$i];
					$join->join_type 		= $joinTypes[$i];
					$join->store();
					$aOldJoinsToKeep[] 		= $joinIds[$i];
				}
			}
		}
		/* remove non exisiting joins */
		if (is_array($aOldJoins)) {
			foreach ($aOldJoins as $oOldJoin) {
				if (!in_array($oOldJoin->id, $aOldJoinsToKeep)) {
					/* delete join */
					$join =& JTable::getInstance('Join', 'Table');
					$joinModel->setId($oOldJoin->id);
					unset($joinModel->_join);
					$joinModel->getJoin();
					$joinModel->deleteAll( $oOldJoin->group_id);
				}
			}
		}
	}

	/**
	 * replaces the table column names with a safer name - ie removes white
	 * space and none alpha numeric characters
	 */

	function makeSafeTableColumns()
	{
		if (!$this->_canAlterFields()) {
			return;
		}
		$form =& $this->getForm();
		$groups = $form->getGroupsHiarachy();
		$db = $this->getDb();
		$table =& $this->getTable();
		$origColNames = $this->getDBFields();
		foreach ($origColNames as $origColName) {
			$colName = strtolower($origColName->Field);
			$type = $origColName->Type;
			$fbConfig =& JComponentHelper::getParams('com_fabrik');
			if ($fbConfig->get('use_wip', false)) {
				// $$$ was causing some problems for a site that was foolishly using - in columns names
				$newColName = preg_replace("/[^A-Za-z0-9-]/", "_", $colName);
			}
			else {
				$newColName = preg_replace("/[^A-Za-z0-9]/", "_", $colName);
			}


			if ($colName != $newColName) {
				// $$$rob - if we change an underlying column name, then we need to also alter the coresponding
				// element name itself, otherwise things go hinky!
				foreach ($groups as $groupModel) {
					$elementModels =& $groupModel->getMyElements();
					foreach ($elementModels as $elementModel) {
						$element =& $elementModel->getElement();
						if ($element->name == $colName) {
							$element->name = $newColName;
							$element->store();
						}

					}
				}
				$sql = "ALTER TABLE ".$db->nameQuote($table->db_table_name)." CHANGE ".$db->nameQuote($colName)." ".$db->nameQuote($newColName)." $type";
				$db->setQuery($sql);
				if (!$db->query()) {
					JError::raiseWarning(500, $db->getErrorMsg());
				}
			}
		}
	}

	/**
	 * adds a primary key to the database table
	 * @param string the column name to make into the primary key
	 * @param bol is the column an auto incrementing number
	 * @param string column type definition (eg varchar(255))
	 */

	function updatePrimaryKey($fieldName, $autoIncrement, $type = 'int(11)')
	{
		if (!$this->_canAlterFields()) {
			return;
		}
		$fabrikDatabase =& $this->getDb();
		$aPriKey = $this->getPrimaryKeyAndExtra();
		if (!$aPriKey) { // no primary key set so we should set it
			$this->_addKey($fieldName, $autoIncrement, $type);
		} else {
			if (count($aPriKey ) > 1) {
				// $$$ rob multi field pk - ignore for now
				return;
			}
			$aPriKey = $aPriKey[0];
			$shortKey = $this->_shortKey($fieldName, true); // added true for second arg so it strips quotes, as was never matching colname with quotes
			if ($fieldName !=  $aPriKey['colname'] && $shortKey != $aPriKey['colname']) {
				$this->_dropKey($aPriKey); // primary key already exists so we should drop it
				$this->_addKey($fieldName, $autoIncrement, $type);
			} else {
				//update the key
				// $$$ hugh - only update it if we need to
				$priInc = $aPriKey['extra'] == 'auto_increment' ? '1' : '0';
				if ($priInc != $autoIncrement || $type != $aPriKey['type']) {
					$this->_updateKey($fieldName, $autoIncrement, $type);
				}
			}
		}
	}

	/**
	 * internal function: update an exisitng key in the table
	 * @param string primary key column name
	 * @param bol is the column auto incrementing
	 * @param string the primary keys column type
	 */

	function _updateKey($fieldName, $autoIncrement, $type = "INT(11)")
	{
		$db =& $this->getDb();
		if (strstr($fieldName, '.')) {
			$fieldName = array_pop(explode(".", $fieldName));
		}
		$fieldName = trim($fieldName, '`'); // we're going to nameQuote() it, so need to chomp any existing back quotes
		$table =& $this->getTable();
		$sql = "ALTER TABLE ".$db->nameQuote($table->db_table_name)." CHANGE ".$db->nameQuote($fieldName).' '.$db->nameQuote($fieldName)." $type NOT NULL ";
		/* update primary key */
		if ($autoIncrement) {
			$sql .= " AUTO_INCREMENT";
		}
		$db->setQuery($sql);
		if (!$db->query()) {
			JError::raiseWarning(500, 'update key:'.$db->getErrorMsg());
		}
	}

	/**
	 * internal function: add a key to the table
	 * @param string primary key column name
	 * @param bol is the column auto incrementing
	 * @param string the primary keys column type (if autoincrement true then int(6) is always used as the type)
	 */

	function _addKey($fieldName, $autoIncrement, $type = "INT(6)")
	{
		$db        =& $this->getDb();
		$type      = $autoIncrement != true ? $type : 'INT(6)';
		$table     =& $this->getTable();
		$fieldName = FabrikString::shortColName($fieldName);
		if ($fieldName === "") {
			return false;
		}
		$sql = "ALTER TABLE ".$db->nameQuote($table->db_table_name)." ADD PRIMARY KEY ($fieldName)";
		/* add a primary key */

		$db->setQuery($sql);
		if (!$db->query()) {
			JError::raiseWarning(500, $db->getErrorMsg());
		}
		if ($autoIncrement) {
			$sql = "ALTER TABLE ".$db->nameQuote($table->db_table_name)." CHANGE $fieldName $fieldName $type NOT NULL AUTO_INCREMENT"; //add the autoinc
			$db->setQuery($sql);
			if (!$db->query()) {
				return JError::raiseError(500, 'add key: ' . $db->getErrorMsg());
			}
		}
	}


	/**
	 * internal function: drop the table's key
	 * @param array existing key data
	 * @return bol true if ke droped
	 */

	function _dropKey($aPriKey)
	{
		$db = $this->getDb();
		$table =& $this->getTable();
		$sql = "ALTER TABLE ".$db->nameQuote($table->db_table_name)." CHANGE " . $db->nameQuote($aPriKey['colname']) . ' '. $db->nameQuote($aPriKey['colname']) . ' '  . $aPriKey['type'] . " NOT NULL";
		/* removes the autoinc */
		$db->setQuery($sql);
		if (!$db->query()) {
			JError::raiseWarning(500, $db->getErrorMsg());
			return false;
		}
		$sql = "ALTER TABLE ".$db->nameQuote($table->db_table_name)." DROP PRIMARY KEY";
		/* drops the primary key */
		$db->setQuery($sql);
		if (!$db->query()) {
			JError::raiseWarning(500, 'alter table: ' . $db->getErrorMsg());
			return false;
		}
		return true;
	}

	/**
	 *  new join make the group, group elements and formgroup entries for the join data
	 * @param string table key
	 * @param string join to table key
	 * @param string join type
	 * @param string join to table
	 * @param string join table
	 */

	function _makeNewJoin( $tableKey, $joinTableKey, $joinType, $joinTable, $joinTableFrom)
	{
		$db 	=& JFactory::getDBO();
		$formModel 	=& $this->getForm();
		$aData = array(
			"name" => $this->_table->label ."- [" .$joinTable. "]",
			"label" =>  $joinTable,
		);
		$groupId = $this->_createLinkedGroup($aData, true);

		$origTable = JRequest::getVar('db_table_name');
		JRequest::setVar('db_table_name', $joinTable);
		$this->_createLinkedElements($groupId, array());
		JRequest::setVar('db_table_name', $origTable);
		$join =& JTable::getInstance('Join', 'Table');
		$join->table_id 		= $this->_id;
		$join->join_from_table = $joinTableFrom;
		$join->table_join 		= $joinTable;
		$join->table_join_key 	= $joinTableKey;
		$join->table_key 		= str_replace('`', '', $tableKey);
		$join->join_type 		= $joinType;
		$join->group_id 		= $groupId;
		if(!$join->store()) {
			return JError::raiseWarning(500, $join->getError());
		}
	}

	/**
	 *
	 * @param bol add slashes to reutrn data
	 */

	function getFilterJoinDd($addslashes = true, $name = 'join')
	{
		$aConditions = array();
		$aConditions[] = JHTML::_('select.option', 'AND');
		$aConditions[] = JHTML::_('select.option', 'OR');
		$dd = str_replace("\n", "", JHTML::_('select.genericlist',  $aConditions, $name, "class=\"inputbox\"  size=\"1\" ", 'value', 'text', ''));
		if ($addslashes) {
			$dd = addslashes( $dd);
		}
		return $dd;
	}

	/**
	 *@param bol add slashes to reutrn data
	 *@param string name of the drop down
	 *@param int mode - states what values get put into drop down
	 */

	function getFilterConditionDd($addslashes = true, $name = 'conditions', $mode = 1)
	{
		$aConditions = array();
		switch ($mode) {
			case 1:
				/* used for search filter */
				$aConditions[] = JHTML::_('select.option', '<>', 'NOT EQUALS');
				$aConditions[] = JHTML::_('select.option', '=', 'EQUALS');
				$aConditions[] = JHTML::_('select.option', 'like', 'BEGINS WITH');
				$aConditions[] = JHTML::_('select.option', 'like', 'CONTAINS');
				$aConditions[] = JHTML::_('select.option', 'like', 'ENDS WITH');
				$aConditions[] = JHTML::_('select.option', '>', 'GREATER THAN');
				$aConditions[] = JHTML::_('select.option', '>=', 'GREATER THAN OR EQUALS');
				$aConditions[] = JHTML::_('select.option', '<', 'LESS THAN');
				$aConditions[] = JHTML::_('select.option', '<=', 'LESS THAN OR EQUALS');
				break;
			case 2:
				/* used for prefilter */
				$aConditions[] = JHTML::_('select.option', 'equals', 'EQUALS');
				$aConditions[] = JHTML::_('select.option', 'notequals', 'NOT EQUAL TO');
				$aConditions[] = JHTML::_('select.option', 'begins', 'BEGINS WITH');
				$aConditions[] = JHTML::_('select.option', 'contains', 'CONTAINS');
				$aConditions[] = JHTML::_('select.option', 'ends', 'ENDS WITH');
				$aConditions[] = JHTML::_('select.option', '>', 'GREATER THAN');
				$aConditions[] = JHTML::_('select.option', '>=', 'GREATER THAN OR EQUALS');
				$aConditions[] = JHTML::_('select.option', '<', 'LESS THAN');
				$aConditions[] = JHTML::_('select.option', 'IS NULL', 'IS NULL');
				$aConditions[] = JHTML::_('select.option', 'IS NOT NULL', 'IS NOT NULL');
				$aConditions[] = JHTML::_('select.option', '<=', 'LESS THAN OR EQUALS');
				$aConditions[] = JHTML::_('select.option', 'in', 'IN');
				$aConditions[] = JHTML::_('select.option', 'not_in', 'NOT IN');
				$aConditions[] = JHTML::_('select.option', 'earlierthisyear', JText::_('EARLIER_THIS_YEAR'));
				$aConditions[] = JHTML::_('select.option', 'laterthisyear', JText::_('LATER_THIS_YEAR'));
				break;
		}
		$dd = str_replace("\n", "", JHTML::_('select.genericlist',  $aConditions, $name, "class=\"inputbox\"  size=\"1\" ", 'value', 'text', ''));
		if ($addslashes) {
			$dd = addslashes( $dd);
		}
		return $dd;
	}

	/**
	 * update a series of rows with a key = val , works across joined tables
	 * Enter description here ...
	 * @param array $ids
	 * @param string $col
	 * @param string $val
	 */

	public function updateRows($ids, $col, $val)
	{
		if ($col == '') {
			return;
		}
		if (empty($ids)) {
			return;
		}
		$db =& $this->getDb();
		$nav	=& $this->getPagination(1, 0, 1);
		$data = $this->getData();
		// $$$ rob dont unshift as this messes up for grouped data
		//$data = array_shift($data);
		$table =& $this->getTable();

		$update = "$col = ".$db->Quote($val);
		$tbl = array_shift(explode('.', $col));

		$joinFound = false;
		JArrayHelper::toInteger($ids);
		$ids = implode(',', $ids);
		$dbk = $k = $table->db_primary_key;

		$joins = $this->getJoins();

		// if the update element is in a join replace the key and table name with the
		// join table's name and key
		foreach ($joins as $join) {
			if ($join->table_join == $tbl) {
				$joinFound = true;
				$db->setQuery('DESCRIBE '.$tbl);
				$fields = $db->loadObjectList('Key');
				$k = $tbl.'___'.$fields['PRI']->Field;
				$dbk = $tbl.'.'.$fields['PRI']->Field;
				$db_table_name = $tbl;
				$ids = array();
				foreach ($data as $groupdata) {
					foreach ($groupdata as $d) {
						$v = $d->{$k.'_raw'};
						if ($v != '') {
							$ids[] = $v;
						}
					}
				}
				if (!empty($ids)) {
					$ids = implode(',', $ids);
					$db->setQuery("UPDATE $db_table_name SET $update WHERE $dbk IN ($ids)");
					$db->query();
				}
			}
		}
		if (!$joinFound) {
			$db_table_name = $table->db_table_name;
			$db->setQuery("UPDATE $db_table_name SET $update WHERE $dbk IN ($ids)");
			$db->query();
		}
	}
}
?>
