<?php

/*	SM_SQLEdit
	
	A PHP class for displaying and editing SQL databases.
	
	Instant start: put all required options into an array in a calling file,
	such as index.php. Call this class at the end of it to take over from there.
	
	Real docs will be forthcoming.
	
	Output is formatted via the Smarty template engine. Templates also handle
	the generation of Add, Edit and Delete forms.
	
	Version history:
	
	0.1-0.8.3	various development versions with numerous features added as
			the need arose
	0.8.4	added preliminary trigger support. Only postedit is currently
			tested, although there are placeholder calls for preinsert,
			postinsert, preedit, postedit, predelete and postdelete. The idea is
			that if a function by that name exists, it will be called at the
			appropriate time. The user can write whatever code she wishes within
			the function.
	0.8.5	added ditto functionality (passes _show_dittos from request and
			$opts['showdittos'] from config file to allow templates to display
			ditto buttons. Templates can use these as desired, but the idea is
			to make buttons that display beside every field to allow instant
			finding of matching values (hence ditto).
	0.8.6	not really released; changed trigger support to include the trigger
			code instead of calling it as a function.
	0.8.7	added rudimentary reports support. Array $table_reports contains an
			array for each report. Key is the unique title of the report as it
			will appear in the menu; values are ['template'] which is the Smarty
			template file name and ['query'] which is a query specific to this
			report.
	0.9b	major rewrite to use session variables, improve support for
			additional formats, clean up code and much more.
	0.9.1b	add search_more functionality to display/hide additional search
			fields.
	0.9.2d	add exportable where_clause and order_by_clause variables which can
			be used in table reports. These variables make the current selection
			and sorting criteria available for use in the $tpl['table_reports']
			array.
	0.9.3	add array-editing feature. This allows all displayed records to be
			edited spreadsheet-style, then updated in a single operation.
			Editability is controlled by the "array_edit => true" flag on each
			field; if omitted it defaults to false.
	0.9.5	add multiple-table support. Config file can now contain a joining
			clause which will be appended to the where clause; for example,
			including
			$join_clause = isset($_SESSION['join_clause']) ?
				$_SESSION['join_clause'] : 't1.field1=t2.field2';
			will append " AND t1.field1=t2.field2" to the main query.
			You also have to include all tables in the $db['tb'] field, thus:
			$db['tb'] = 'table1 t1,table2 t2';
			and qualify ALL the field names with their table prefixes (e.g.
			t1 or t2).
			YOU MUST ALSO SPECIFY a single table which will be used for adds,
			edits and deletes, thus:
			$db['tbedit'] = 'table t1'];
			Only this table will be affected by inserts, updates and deletes.
	0.9.6	bug-fix release to escape quotes and other special characters before
			inserting them into the MySQL database.
	0.9.7	convert to Smarty version 3. There are almost no changes in the cors
			sm_sqledit code; most changes are in the accompanying template files
			to reflect the syntax and feature changes in Smarty 3.0.
	0.9.8	add exportable detail_record variable so that selected record can be
			used in table reports. This variable makes the record ID of the
			currently opened record available for use in the $tpl['table_reports'] array.
	0.9.8.1	changed name flag character from "*" to "__" to make them legal
			Javascript variables.
	0.9.8.2	fixed ambiguous table names in WHERE clauses selecting from multiple
			tables
	0.9.8.3	added data type conversion. Currently only works for date entry, but
			will accept any reasonable typed-in date and format it to keep MySQL
			happy. The column specification contains 'type' => 'date' to allow
			date parsing on input.
			Added format option for formatted date output in columns. The column
			specification contains 'format' => 'PHP strftime() date format
			spec'. See the PHP documentation for strftime() for details. Note
			that data is still stored internally as MySQL datetime values.
	0.9.8.4	added experimental calculated column feature. Calculated column
			specifications are distinguished by a callback parameter consisting
			of PHP code that explcitly returns a value, as well as display and
			heading options. The callback is invoked via eval() for every row,
			so efficiency is desirable. Check the PHP documentation for various
			pitfalls and caveats regarding the use of eval(). Calculated columns
			can't be sorted, and the smse_sortable_headings.tpl template has
			been modified to account for this.
	0.9.8.5	revised search for duplicates ("==" in search criterion) to display
			all records with duplicates, not just an arbitrary one. For example,
			all the Smiths will now show, not just one of them.
	0.9.8.6	added beginnings of a logging facility for tracking changes and
			edits to records. Also tightened up session key to include IP
			address of client.
	0.9.8.7	removed $where_clause exportable variable and replaced it with two
			more versatile exportable variables: $selections and
			$where_selections. $selections contains the MySQL selection
			fragments gathered from the selection criteria in the record list,
			without the leading WHERE. It can be used in the $query elements of
			report templates, for example "SELECT field1, field2 FROM mydb WHERE
			$selections ORDER BY field1". It can thus be combined with
			additional WHERE conditions which wasn't possible with the old
			$where_clause.
			$where_selections is the same as $selections but includes the
			leading WHERE. It has the advantage that it is empty if there are no
			$selections, which often makes it more convenient to use.
	0.9.8.8	added a way to negate a search criterion. Entering "!" as the first
			character of a search criterion negates it, so while "smith" will
			find all Smiths, "!smith" will find all those who aren't Smith.
			Also a bug fix to the Log_Entry function.
			
			Added a "widget" parameter to the config file specification. This is
			not processed in SM_SQLEdit core but in the smse_recordedit.tpl
			template file so doesn't qualify for a version number change.
			Currently the only supported widgets are:
			- 'widget' => 'datetimepicker' which invokes the jQuery
				datetimepicker popup calendar. datetimepicker is an extension to
				the standard jQuery datepicker module for handling times.
			- 'widget' => 'menu' which displays a popup menu for the field. It
				requires that allowedvalues and allowedlabels be defined as
				well. Other widget actions could be defined in
				smse_recordedit.tpl easily.
			- 'widget' => 'checkbox' which displays a checkbox with 0/1 values
			- 'widget' => 'checkboxyn' which displays a checkbox with Yes/No
				values
	0.9.8.9	added 'download_file' => 'filename' parameter to report structure so
			that files can be downloaded instead of rendered. This is handy for
			XML exports and things.
			Finally added stripslashes to clean up database data before display.
	0.9.9.0	added sub-array for session variables so that multiple apps can
			share the same session name. Also fixed download code so Smarty
			templates can be used for output formatting.
	0.9.9.1	added template variable for arrayedit file so we don't always have
			to use the desfault smse_arrayedit.tpl. This makes it possible to
			add an edit-in-place version of arrayedit; but that needs us to -
			export the $db database specification array so we can make the
			arrayeditinplace.tpl template generic.
	0.9.9.2	added <> operator for "BETWEEN...AND..." searches, thus:
			2014-01-01<>2014-01-03 displays dates between and including those two dates;
			A<>C displays Able and Baker but not Charlie ("Charlie" is greater than "C")
			also works with ! (negation), as in
			!2013-01-01<>2014-01-01
	0.9.9.5	Updated to MySQLi. Uses $GLOBALS["dbConnect"] for database
			connection, which is opened by the host application.
	0.9.9.6	Added $globals to automatically assigned Smarty variables.
	0.9.9.6	(no version change) Removed mysqli_connect(...) just below so as not
			to disrupt other actions of the host application's login. For
			example, with the move to MySQL 5.7.23 it was necessary to issue a
			SET SESSION sql_mode=REPLACE(@@SESSION.sql_mode, 'ONLY_FULL_GROUP_BY', '')
			to restore the earlier, less restrictive GROUP BY clause behaviour.
	0.9.9.7	added session variable to store view selection (standard or
			spreadsheet) so the user's selection is sticky.
	0.9.9.8	Added multiquery support for reports that would benefit from it.
	1.0.0.0	Upgrades for PHP 8.0 and PHP 8.1
	1.0.0.1	Bug fix for MySQL queries that don't return result sets (i.e. not SELECTs)
	1.2.0.0	Stripped out several unused functions (Insert, Duplicate, Edit, Kill,
			Log_Entry).
*/
require $smarty_dir . 'Smarty.class.php';
DebugPrint('Smarty dir', SMARTY_DIR);
// session_name('SMSE_' . $db['db'] . $db['tb']);
// session_start();
DebugPrint('Session ' . session_name() . ' variables (initial)', $_SESSION);

// Constants

$version = 'SM_SQLEdit v1.2, 20 December, 2025';

/*	if there's no edit table ($db['tbedit']) specified, set it to
	the main table spec ($db['tb']) . We need this before anything else. */
	
if (!isset($db['tbedit'])) {
	$db['tbedit'] = $db['tb'];
}
	

/*	Code modified by MySQLi converter */

// $GLOBALS["dbConnect"] = mysqli_connect($db['hn'], $db['un'], $db['pw'], $db['db']) or die('Couldn\'t connect to database server: '. mysqli_error($GLOBALS["dbConnect"]));
// $dbSelect = mysqli_query($GLOBALS["dbConnect"], "USE " . $db['db']) or die('Couldn\'t use database: '. mysqli_error($GLOBALS["dbConnect"]));
$user = $_SERVER['PHP_AUTH_USER'];

// We always calculate the following

$start_record =  (isset($_SESSION[$appname]['start_record'])) ? $_SESSION[$appname]['start_record'] : 0;

// The user may input the following. If not (e.g. on first call) we initialise

$detail_record = isset($_REQUEST['_detail']) ? $_REQUEST['_detail'] : (isset($_SESSION[$appname]['detail_record']) ? $_SESSION[$appname]['detail_record'] : (isset($opts['detail_record']) ? $opts['detail_record'] : 0));

$pagelimit = isset($_REQUEST['_pagelimit']) ? $_REQUEST['_pagelimit'] : (isset($_SESSION[$appname]['pagelimit']) ? $_SESSION[$appname]['pagelimit'] : (isset($opts['pagelimit']) ? $opts['pagelimit'] : 20));

$show_dittos = isset($_REQUEST['_show_dittos']) ? $_REQUEST['_show_dittos'] : (isset($_SESSION[$appname]['show_dittos']) ? $_SESSION[$appname]['show_dittos'] : (isset($opts['show_dittos']) ? $opts['show_dittos'] : false));

$sort_field = isset($_REQUEST['_sort_field']) ? $_REQUEST['_sort_field'] : (isset($_SESSION[$appname]['sort_field']) ? $_SESSION[$appname]['sort_field'] : (isset($opts['sort_field']) ? $opts['sort_field'] : ''));

$sort_table = isset($_REQUEST['_sort_table']) ? $_REQUEST['_sort_table'] : (isset($_SESSION[$appname]['sort_table']) ? $_SESSION[$appname]['sort_table'] : (isset($opts['sort_table']) ? $opts['sort_table'] : ''));

$join_clause =  $opts['join_clause'];

$sort_order = isset($_REQUEST['_sort_order']) ? $_REQUEST['_sort_order'] : (isset($_SESSION[$appname]['sort_order']) ? $_SESSION[$appname]['sort_order'] : (isset($opts['sort_order']) ? $opts['sort_order'] : ''));

$selections = isset($_REQUEST['_selections']) ? $_REQUEST['_selections'] : (isset($_SESSION[$appname]['selections']) ? $_SESSION[$appname]['selections'] : (isset($opts['selections']) ? $opts['selections'] : ''));

$visibility = isset($_REQUEST['_visibility']) ? $_REQUEST['_visibility'] : (isset($_SESSION[$appname]['visibility']) ? $_SESSION[$appname]['visibility'] : (isset($opts['visibility']) ? $opts['visibility'] : ''));

$show_more_search_fields = isset($_REQUEST['_show_more_search_fields']) ? $_REQUEST['_show_more_search_fields'] : (isset($_SESSION[$appname]['show_more_search_fields']) ? $_SESSION[$appname]['show_more_search_fields'] : (isset($opts['show_more_search_fields']) ? $opts['show_more_search_fields'] : ''));

$report_name = isset($_REQUEST['_report_name']) ? $_REQUEST['_report_name'] : (isset($_SESSION[$appname]['report_name']) ? $_SESSION[$appname]['report_name'] : (isset($opts['report_name']) ? $opts['report_name'] : 'Default'));

$letter_name = isset($_REQUEST['_letter_name']) ? $_REQUEST['_letter_name'] : (isset($_SESSION[$appname]['letter_name']) ? $_SESSION[$appname]['letter_name'] : (isset($opts['letter_name']) ? $opts['letter_name'] : 'Default'));

$view_name = isset($_REQUEST['_view_name']) ? $_REQUEST['_view_name'] : (isset($_SESSION[$appname]['view_name']) ? $_SESSION[$appname]['view_name'] : (isset($opts['view_name']) ? $opts['view_name'] : 'Default'));

$record_count = isset($_SESSION[$appname]['record_count']) ? $_SESSION[$appname]['record_count'] : $pagelimit;

if (!isset($_SESSION[$appname]['search_fields'])) {$_SESSION[$appname]['search_fields'] = '';}
if (!isset($_SESSION[$appname]['more_search_fields'])) {$_SESSION[$appname]['more_search_fields'] = '';}
if (!isset($_SESSION[$appname]['search_values'])) {$_SESSION[$appname]['search_values'] = '';}
if (!isset($_SESSION[$appname]['selections'])) {$_SESSION[$appname]['selections'] = '';}
if (!isset($_SESSION[$appname]['join_clause'])) {$_SESSION[$appname]['join_clause'] = '';}
if (!isset($_SESSION[$appname]['order_clause'])) {$_SESSION[$appname]['order_clause'] = '';}

DebugPrint('Join clause (session)', $_SESSION[$appname]['join_clause']);
DebugPrint('Join clause (default)', $opts['join_clause']);
DebugPrint('Join clause (effective)', $join_clause);
DebugPrint('Selections (session)', $_SESSION[$appname]['selections']);
DebugPrint('Visibility (session)', $_SESSION[$appname]['visibility']);
DebugPrint('Sort table (requested)', $_REQUEST['_sort_table']);
DebugPrint('Sort field (requested)', $_REQUEST['_sort_field']);
DebugPrint('Sort table (session)', $_SESSION[$appname]['sort_table']);
DebugPrint('Sort field (session)', $_SESSION[$appname]['sort_field']);
DebugPrint('Sort table (default)', $opts['sort_table']);
DebugPrint('Sort field (default)', $opts['sort_field']);
DebugPrint('$_REQUEST: ', $_REQUEST);

$filesuffix = $opts['sf'];			// Passed to Smarty to control which templates are displayed

/* Get values from form and user input */

$searchvalue = $_REQUEST['_searchvalue'];	// from URL if given
$sqlwhere = $_REQUEST['_sqlwhere'];			// from URL if given
$allrecords = $_REQUEST['_allrecords'];
$action = $_REQUEST['_action'];				// always from form

foreach ($fields as $this_key => $thisfield) {	// find fields to search on
	if ($_REQUEST[$this_key] != '') {	// value specified?
		$fields[$this_key]['filter'] = $_REQUEST[$this_key];
	}
}

$sort_parameters = array(
	'name' => $sort_field,
	'table' => $sort_table,
	'order' => $sort_order
);

DebugPrint('Requests',$_REQUEST);
DebugPrint('Selections',$selections);
DebugPrint('Visibility',$visibility);
DebugPrint('show_more_search_fields',$show_more_search_fields);
DebugPrint('Session variables (initial)',$_SESSION);

$pagecounts = (isset($tpl['pagecounts'])) ? $tpl['pagecounts'] : array(10,20,30,50,100);

$table_reports = array_merge(array('Default' => array('template' => 'smse_default.tpl', 'query' => '')), $tpl['table_reports']);
$tpl['bulk_letter_templates'] = ($tpl['bulk_letter_templates'] ?? []); //	If empty, force array
$bulk_letter_templates = array_merge(['Default' => ['template' => 'smse_default.tpl', 'query' => '']], $tpl['bulk_letter_templates']);

DebugPrint('Table Reports',$tpl['table_reports']);
DebugPrint('Bulk Emails',$tpl['bulk_letter_templates']);


if (empty($action)) {	// Could happen on initial call
	$action = 'First';
}

$primarykeyfield = '';	// find primary key field
foreach ($fields as $this_key => $thisfield) {
	if ($fields[$this_key]['primarykey'] == true) {
		$primarykeyfield = $this_key;
		break;
	}
}
if ($primarykeyfield == '') {	// Did we get a primary key?
	die("No primary key field defined for table {$db['db']}.{$db['tbedit']}. Cannot continue.");
}

DebugPrint('Action requested', $action);

switch ($action) {

case 'Reset_View':	//	Reset to default view, kill any active report template
//	$view_name = 'Default';
	$report_name = 'Default';
	$letter_name = '';
//	unset($_SESSION[$appname]['report_name'], $_SESSION[$appname]['view_name']);
//	die($_SESSION[$appname]['view_name']);
//	Drop into next case to switch to default view

case 'Set_View':	//	Set the desired view and save it in a session variable
	switch ($view_name) {
	case 'Default':
		if (isset($tpl['recordlist'])) {	// Use designated template if any
			$template = $tpl['recordlist'];
		} else {							// otherwise default
			$template = 'smse_default.tpl';
		}
		break;
	case 'Spreadsheet':
		if (isset($tpl['arrayedit'])) {	/* Use designated template if any */
			$template = $tpl['arrayedit'];
		} else {						/* other wise default */
			$template = 'smse_arrayedit.tpl';
		}
		break;
// 	case 'Miniview':
// 		if (isset($tpl['miniview'])) {	/* Use designated template if any */
// 			$template = $tpl['miniview'];
// 		} else {						/* other wise default */
// 			$template = 'smse_miniview.tpl';
// 		}
// 		break;
	default:
		die("Invalid value \"$view_name\" passed to Set_View");
	}
	$_SESSION[$appname]['view_name'] = $template;
	break;

case 'First':	// start = 0; current record = 0; detail record = 0; build query
	$start_record = 0;
	$detail_record = 0;

	break;

case 'Prev':	// start = start - count or 0; current record = 0; detail record = 0; build query
	$start_record = max(0, ($start_record - $pagelimit));
	$detail_record = 0;
	break;

case 'Next':	// start = start + count or max; current record = 0; detail record = 0; build query
	$start_record = min($record_count, ($start_record + $pagelimit));
	$detail_record = 0;
	break;

case 'Last':	// start = max - count or 0; current record = 0; detail record = 0; build query
	$start_record = max(0, ($record_count - $pagelimit));
	$detail_record = 0;
	break;
	
case 'Sort':	// change sort field. Value is already set above; just display.
	$start_record = 0;
	$detail_record = 0;
	break;

case 'Show Field':	// change show/hide of  field. Value already set above; just display.
	$start_record = 0;
	$detail_record = 0;
	break;

case 'Clear Search':	// set all fields empty; start = 0; build query
	foreach ($fields as $this_key => $thisfield) {
		$fields[$this_key]['filter'] = '';		// Clear filtering
	}
	$start_record = 0;
	$detail_record = 0;
	break;

case 'Detail':	// detail record = current record; action is Display
	$editaction['action'] = 'Detail';
	$editaction['entry'] = $detail_record;
	break;

case 'Change Count':	// change number of records viewed (already set above)
	break;

case 'View':			// don't do anything, just redisplay
	break;

case 'New':		// clear search; start = 0; insert blank record via SQL; current record = 0; detail record = 0
	$defaultarray = '';
	foreach ($fields as $this_key => $thisfield) {	// Build array of default values
		if ($fields[$this_key]['default'] != '') {	// default specified?
			$defaultarray[$this_key] = $fields[$this_key]['default'];
		}
	}
	Insert($GLOBALS["dbConnect"], $defaultarray, $db['tbedit']);
	foreach ($fields as $this_key => $thisfield) {
		$fields[$this_key]['filter'] = '';		// Clear filtering
	}
	$editaction['action'] = 'Edit';			// call up Edit screen
	$start_record = 0;						// Goes to top of list
	$detail_record = mysqli_insert_id($GLOBALS["dbConnect"]);	// force to latest addition
	break;

// case 'Edit':	// detail record = current record; action is Edit
// 		$editaction['action'] = 'Edit';
// 		$editaction['entry'] = $detail_record;
// 	break;
// 	
// case 'Copy':	// detail record = current record; insert via SQL
// 	$editaction['action'] = 'Copy';
// 	foreach ($fields as $this_key => $thisfield) {	// collect update values
// 		$editfields[$this_key] = $_REQUEST[$this_key];
// 	}
// 	Duplicate($GLOBALS["dbConnect"], $primarykeyfield, $editfields, $db['tbedit'], $detail_record);
// 	break;
// 	
// case 'Delete':	// detail record = current record; delete via SQL
// 	Kill($GLOBALS["dbConnect"], $primarykeyfield, $db['tbedit'], $detail_record);
// 	break;
// 
// case 'Commit':	// detail record = current record; update via SQL
// 	$editfields = '';	// capture edited values, flagged with __
// 	foreach ($fields as $this_key => $thisfield) {
// 		if (array_key_exists('__' . $this_key, $_REQUEST)) {
// 			$editfields[$this_key] = $_REQUEST['__' . $this_key];
// 		}
// 	}
// 	DebugPrint('EDIT commit requested. Action',$action);
// 	DebugPrint('Requests array',$_REQUEST);
// 	DebugPrint('EDIT record key', $detail_record);
// 	Edit($GLOBALS["dbConnect"], $primarykeyfield, $editfields, $fields, $db['tbedit'], $detail_record);
// 	DebugPrint('EDIT commit completed.','');
// 	break;
// 
// case 'Save ALL':		// kludge to keep IE happy (IE will not send value of the button,
// 						// it sends the text between the <button/ and </button> tags!!)
// case 'ArrayCommit':	// an entire array of records updated; update via SQL
// 	$this_key_prev = '';
// 	foreach ($_REQUEST as $this_key => $thisfield) { // extract update fields and
// 													// build a list of SET clauses
// 		if (substr($this_key, 0, 18) == '_smse_arrayupdate|') {
// 			DebugPrint("Edited field found at $this_key", $_REQUEST[$this_key]);
// 			$edititem = explode('|', $this_key);
// 			
// 			if ($edititem[1] != $this_key_prev) { // new record?
// 				// Yes, update latest record (if any) and start new list of fields
// 				if ($this_key_prev != '') { // update latest
// 					DebugPrint('Arrayfields: ',$arrayfields);
// 					DebugPrint('Arrayvalues: ',$arrayvalues);
// 					Edit($GLOBALS["dbConnect"], $primarykeyfield, $arrayvalues, $arrayfields, $db['tbedit'], $this_key_prev);
// 				}
// 				$this_key_prev = $edititem[1];
// 				$arrayfields = '';
// 				$arrayvalues = '';
// 			} // Not new ,drop through to  append new fieldname and value
// 			$arrayvalues[$edititem[2]] = $_REQUEST[$this_key];
// 			$arrayfields[$edititem[2]] = $_REQUEST[$this_key];
// 		}
// 	}
// 	if ($arrayvalues != '') { // We need to process last record if there is one
// 		Edit($GLOBALS["dbConnect"], $primarykeyfield, $arrayvalues, $arrayfields, $db['tbedit'], $this_key_prev);
// 	}
// 
// 	break;

case 'Cancel':	// redisplay with current start record
	break;		// Nothing to do

case 'Table Report':	//change formats
	$report_name = $_REQUEST['_report_name'];
	$template = $table_reports[$report_name]['template'];
	$query = $table_reports[$report_name]['query'];
	$report_download_format = $table_reports[$report_name]['report_download_format'];
	$download_file = $table_reports[$report_name]['download_file'];
	$template_text = $table_reports[$report_name]['template_text'];
	DebugPrint('Table Report',$table_reports[$report_name]);
	break;

case 'Bulk Email':	//change formats
die("BULK!");
	$letter_name = $_REQUEST['_letter_name'];
	$template = $bulk_letter_templates[$letter_name]['template'];
	$query = $bulk_letter_templates[$letter_name]['query'];
	$report_download_format = $bulk_letter_templates[$letter_name]['report_download_format'];
	$download_file = $bulk_letter_templates[$letter_name]['download_file'];
	$template_text = $bulk_letter_templates[$letter_name]['template_text'];
	DebugPrint('Bulk Email',$bulk_letter_templates[$letter_name], true);
	break;

case 'Single Report':	//Print a report from a single record
	DebugPrint('Single Report parameters',$_REQUEST);
	$report_name = $_REQUEST['_report_name'];
	$template = $table_reports[$report_name]['template'];
	$table_name = $_REQUEST['_table_name'];
	$pkfn = $_REQUEST['_pkfn'];
	$pkfv = $_REQUEST['_pkfv'];
	$query = "{$table_reports[$report_name]['query']} WHERE {$_REQUEST['_query']}";
//	die("About to print Single Report '$report_name' for $pkfn=$pkfv; QUERY=<br />$query<br />");
	DebugPrint("Single Report BASE Query=", $table_reports[$report_name]['query']);
	DebugPrint("Single Report WHERE clause=", $_REQUEST['_query']);
	DebugPrint("Single Report COMPLETE \$query=", $query);
	$report_download_format = $table_reports[$report_name]['report_download_format'];
	$download_file = $table_reports[$report_name]['download_file'];
	$template_text = $table_reports[$report_name]['template_text'];
	DebugPrint('Single Report',$table_reports[$report_name]);
	break;

case 'Show More Search Fields':	// show more search fields
	break;		// nothing to do; display changes are handled by templates

case 'Array Edit':	// put up array-editing (spreadsheet-style) screen
	DebugPrint('Array edit requested', $action);
	break;

default:
	die('Internal error: undefined action code to SM_SQLEdit: ' . $action);
	
}

// Now find the template we want.

if (isset($_SESSION[$appname]['view_name']) and !isset($template)) {
	$template = $_SESSION[$appname]['view_name'];
} elseif ($template == '') {	// Do we have one?
	$template = 'smse_default.tpl';	// No, use the default
//	$query = '';
}

DebugPrint('Report name selected',$report_name);
DebugPrint('Template selected',$template);
DebugPrint('Query selected',$query);

$fieldlist = '';	// find fields flagged for display
$fieldeditlist = '';	// find fields flagged for edit
foreach ($fields as $this_key => $thisfield) {
	if (($thisfield['display'] == true) OR
		($thisfield['select'] == true) OR
		($thisfield['primarykey'] == true)) {
		/*	Don't select computed columns but note them for processing after data is
			selected */
		if (isset($thisfield['callback'])) {
			DebugPrint("Field $this_key has callback", $thisfield);
			$fieldscomputed[$this_key]['callback'] = $thisfield['callback'];
		} else {
			if ($thisfield['table'] != '') {
				$fieldlist .= $thisfield['table'] . '.' . $this_key . ', ';
			} else {
				$fieldlist .= $this_key . ', ';
			}
		}
//		break;
	}
	if (($thisfield['display'] == true) OR
		($thisfield['select'] == true) OR
		($thisfield['primarykey'] == true)) {
		if ($thisfield['queryname'] != '') {
			$fieldeditlist .= $thisfield['table'] . '.' . $this_key . ', ';
		} else {
			$fieldeditlist .= $this_key . ', ';
	//		break;
		}
	}
}
if ($fieldlist == '') {	// Did we get a field list?
	die("No fields defined for display. Cannot continue.");
} else {
	$fieldlist = trim($fieldlist, ', ');
}

DebugPrint('Field list',$fields);
DebugPrint('Field names',$fieldlist);
DebugPrint('Computed fields',$fieldscomputed);

$distinct = ($allrecords == 'true') ? '' : 'DISTINCT';

// Build query statement

// Were we given an explicit query (as for custom reports?)

$selections = Build_Selections($fields);	// gather selection criteria fragments
if ($selections == '') {
	$where_selections = '';
} else {
	$where_selections = " WHERE $selections ";	// they're the default
}
if ($query != '') {
	$sql = $query;	// Use explicit query if given
	DebugPrint('Explicit $query given', $sql);
} else {	// otherwise build one

	if ($join_clause != '') {	// if there is a join clause
		if ($selections != '') {	// if there is also a selection
			$specifier = " WHERE $join_clause AND $selections "; // output both
		} else {	// join but no selection
			$specifier = " WHERE $join_clause ";	// output only join
		}
	} else {	// there is no join clause, so
		if ($selections != '') {	// if there is a selection
			$specifier = " WHERE $selections ";	// we have only a selection
		} else {
			$specifier = ' ';	// we have no spec
		}
	}

	$order_clause = Build_Order($sort_parameters);
	if ($record_count < $start_record) {	// back up if we've already paged
		$start_record = 0;					// past this point
	}
	$limit_clause = Build_Limit((max(0, $start_record)), $pagelimit);
	$sql = "select $distinct $fieldlist FROM {$db['db']}.{$db['tb']} $specifier $order_clause $limit_clause";
}
DebugPrint('Final $selections', $selections);
DebugPrint('Final $join_clause', $join_clause);
DebugPrint('Final $specifier', $specifier);

DebugPrint('Final SQL query',$sql);

/* Test adding a second query to $sql */

// $sql = 'select DISTINCT r.timestamp,r.firstname FROM sm_accounts.regall r ORDER BY r.lastname LIMIT 0,500';
// $sql .= ';';	//	Separator
// $sql .= 'SELECT prod_id, category, name FROM ybphotoproducts LIMIT 5;';

/*	New code to support multiple queries. */

/*	All queries are run in a multiquery. All results are returned in a single array, $row,
	thus:
	$row[0] is an associative array of rows from the first (or only) query
	$row[1...] are associative arrays of rows from subsequent queries.
	If only one query is run then $row is set to $row[0] to support legacy code that
	doesn't know about multiqueries (such as the master display listing).
*/

$mq_success = mysqli_multi_query($GLOBALS["dbConnect"], $sql) or die('Database error on main query: '.mysqli_error($GLOBALS["dbConnect"]).'<br />Query='.$sql);
DebugPrint('<b>Final SQL query after multiquery call</b>', $sql);
DebugPrint('mq_success', $mq_success);
if ($mq_success) {
	$row = array();		//	Results returned in enumerated array
	$query_count = 0;
	do {
		// store first result set
		if ($result = mysqli_store_result($GLOBALS["dbConnect"])) {
			$row[] = mysqli_fetch_all($result, MYSQLI_ASSOC);
			mysqli_free_result($result);
			DebugPrint("<b>\$row (\$query_count=$query_count)</b>", $row);
		} else {	//	no result returned, maybe not a SELECT query (i.e. INSERT, DELETE etc.)
			$record_count = 0;
			$row[] = array();	//	Create empty array to keep everything happy
		}
		// Any more query results? 
		if (mysqli_more_results($GLOBALS["dbConnect"])) {
			$query_count++;		//	Count queries
			print "<b>More results to come!; \$query_count is now $query_count</b><br />";
		}
	} while (mysqli_next_result($GLOBALS["dbConnect"]));
}

if ($query_count == 0) {	//	Did we only get one result?
	$row = $row[0];		//	Flatten the array into a single dimension
	/* -- */

	/*	Original single-query code
	$result = mysqli_query($GLOBALS["dbConnect"], $sql) or die('Database error on main query: '.mysqli_error($GLOBALS["dbConnect"]).'<br />Query='.$sql);
	$row_count_result = mysqli_query($GLOBALS["dbConnect"], 'select FOUND_ROWS() as row_count') or die('Database error (counting):'.mysqli_error($GLOBALS["dbConnect"]));
	$temp = mysqli_fetch_array($row_count_result);
	/* -- */

	DebugPrint('<b>Row count</b>', count($row));
	$record_count = count($row);

	// Set up pagination values for display

	$pagination['first_shown'] = $start_record;
	$pagination['last_shown'] = min($record_count, ($start_record + $pagelimit));
	$pagination['count_records'] = $record_count;

	$count_shown = 0;
	// Get all the rows and process if required
	foreach ($row as $this_key => $this_row) {

	DebugPrint('Current row at line' . __LINE__, $this_row);

		/* Compute and include computed columns if any for each row */
	
		foreach ($fieldscomputed as $this_key => $thisfield) {
			if (isset($thisfield['callbacklibrary'])) {
				require_once($thisfield['callbacklibrary']);
			}
			$this_row[$this_key] = eval($thisfield['callback']);
			DebugPrint("Computed {$thisfield['callback']} for $this_key in row", $row[$this_key]);
		}

		$data[] = $this_row;
		foreach ($row as $k => $v) {		// Clean up for display
			foreach ($v as $r => $f) {		// Clean up for display
				$row[$r][$f] = stripslashes($f);
			}
		}
		DebugPrint('Data row is', $row);
		$count_shown++;
	}
} else {
$data = $row;
}
DebugPrint('<b>Data before sending on</b>', $data);
$pagination['count_shown'] = $count_shown;	// actual record count

DebugPrint('SM_SQLEdit: Template parameters',$tpl);
DebugPrint('SM_SQLEdit: Report titles',array_keys($tpl['table_reports']));
DebugPrint('Session vars array',$_SESSION);
DebugPrint('SM_SQLEdit: Pagination array (again)',$pagination);

// Store everything away in session variables for next time
$_SESSION['appname'] = $appname;
$_SESSION[$appname]['start_record'] = $start_record;
$_SESSION[$appname]['record_count'] =  $record_count;
$_SESSION[$appname]['detail_record'] = $detail_record;
$_SESSION[$appname]['pagelimit'] = $pagelimit;
$_SESSION[$appname]['show_dittos'] = $show_dittos;
$_SESSION[$appname]['show_more_search_fields'] = $show_more_search_fields;
$_SESSION[$appname]['sort_table'] = $sort_table;
$_SESSION[$appname]['sort_field'] = $sort_field;
$_SESSION[$appname]['sort_order'] = $sort_order;
$_SESSION[$appname]['join_clause'] = $join_clause;
$_SESSION[$appname]['report_name'] = $report_name;
$_SESSION[$appname]['letter_name'] = $letter_name;
$_SESSION[$appname]['search_fields'] = $search_fields;
$_SESSION[$appname]['search_values'] = $search_values;
$_SESSION[$appname]['selections'] = $selections;
$_SESSION[$appname]['visibility'] = $visibility;
$_SESSION[$appname]['where_selections'] = $where_selections;
$_SESSION[$appname]['order_clause'] = $order_clause;
$_SESSION[$appname]['sql'] = $sql;
// $_SESSION[$appname]['data'] = $data;

DebugPrint("Session variable ".session_name()." (final)", $_SESSION);

DebugPrint('Report parameters', $table_reports[$report_name]);

DebugPrint('Rows going to Smarty ($fields)',$fields);

DebugPrint('Data going to Smarty ($data)',$data);

DebugPrint('SQL in \$_SESSION', $_SESSION[$appname]['sql']);

$smarty = new Smarty;
$smarty->compile_check = true;
$smarty->debugging = false;
$smarty->addPluginsDir(isset($tpl['pluginsdir']) ? $tpl['pluginsdir'] : './plugins');
// $smarty->addPluginsDir("{$_SERVER['DOCUMENT_ROOT']}/plugins/");
// $smarty->addTemplateDir($_SESSION['appname'] . '/templates');
$smarty->addTemplateDir('../templates');
$smarty->assign('globals',$GLOBALS);	//	New in 0.9.9.6
$smarty->assign('pagination',$pagination);
$smarty->assign('db',$db);
$smarty->assign('primarykeyfield',$primarykeyfield);
$smarty->assign('sort_table',$_SESSION[$appname]['sort_table']);
$smarty->assign('sort_field',$_SESSION[$appname]['sort_field']);
$smarty->assign('sort_order',$_SESSION[$appname]['sort_order']);
$smarty->assign('show_dittos',$_SESSION[$appname]['show_dittos']);
$smarty->assign('show_more_search_fields',$_SESSION[$appname]['show_more_search_fields']);
$smarty->assign('header', $header); 
$smarty->assign('data', $data);
$smarty->assign('fields', $fields);
$smarty->assign('visible_columns', count($fields));
$smarty->assign('tpl', $tpl);	// template options from config file
$smarty->assign('pagecounts', $pagecounts);
$smarty->assign('pagelimit', $pagelimit);
$smarty->assign('editaction', $editaction);
$smarty->assign('filesuffix', $filesuffix);
$smarty->assign('template', $template);
$smarty->assign('query', $query);
$smarty->assign('final_query', $sql);
$smarty->assign('selections', $selections);
$smarty->assign('visibility', $visibility);
$smarty->assign('where_selections', $where_selections);
$smarty->assign('join_clause', $join_clause);
$smarty->assign('order_clause', $order_clause);
$smarty->assign('detail_record', $detail_record);
$smarty->assign('report_names', array_keys($table_reports)); // convenience for menu
$smarty->assign('report_name', $report_name);
$smarty->assign('letter_name', $letter_name);
$smarty->assign('report_options', $table_reports[$report_name]);
$smarty->assign('pdf', $table_reports[$report_name]['pdf']);
$smarty->assign('download_file', $table_reports[$report_name]['download_file']);
$smarty->assign('template_text', $table_reports[$report_name]['template_text']);
$smarty->assign('version', $version);


DebugPrint("Template dirs", $smarty->getTemplateDir());
DebugPrint("Plugin dirs", $smarty->getPluginsDir());


DebugPrint('Edit action',$editaction);

// Produce desired output format

if ($report_download_format == 'PDF') {		// Generating a PDF.

	// HTML2FPDF output generation

	require_once '../html2fpdf/html2fpdf.php' or die('PDF converter not available - sorry.');
	$pdfOut = $smarty->fetch('pdf.tpl');
	$pdf= new HTML2FPDF();
	$pdf->AddPage();
	$pdf->WriteHTML($pdfOut);
	$pdf->Output();

} elseif ($report_download_format == 'Word') {
	$html = $smarty->fetch(htmlspecialchars_decode('string:'.$template_text));
	print 'Word format selected!';
	require_once("{$_SERVER['DOCUMENT_ROOT']}/phpdocx/classes/CreateDocx.inc") or die('Word converter not available - sorry.');
	print "Looking for {$_SERVER['DOCUMENT_ROOT']}/phpdocx/classes/CreateDocx.inc";
//	echo $wordOut;
	$docx = new CreateDocx();
	$docx->embedHTML($html);
	$docx->createDocx('downloadfile');
	DebugPrint('Downloading Word doc. Filename', $download_file);
	DebugPrint('Downloading Word doc. Template name', $template);
	DebugPrint('Downloading Word doc. Template text', $template_text);
	header('Content-Description: File Transfer');
	header('Content-type: "text/xml"; charset="utf8"');
	// Actually create the attachment output file.
	header('Content-Disposition: attachment; filename="'.basename($download_file) .'"');
	header('Content-Transfer-Encoding: binary');
	print $downloadfile;
		
} elseif ($download_file) {	// Generating a download file.

	DebugPrint('Downloading. Filename', $download_file);
	DebugPrint('Downloading. Template name', $template);
	DebugPrint('Downloading. Template text', $template_text);
	header('Content-Description: File Transfer');
	header('Content-type: "text/plain"; charset="utf8"');
	// Actually create the attachment output file.
	header('Content-Disposition: attachment; filename="'.basename($download_file) .'"');
	header('Content-Transfer-Encoding: binary');
	// Dump the bare template output, no screen trimmings.
	// Render HTML characters as is (not escaped) so XML is rendered correctly.
	echo $smarty->fetch(htmlspecialchars_decode('string:'.$template_text));

} else {				// Generating an on-screen display.

	$smarty->display('main.tpl');
	
}

// mysql_free_result($result);
mysqli_close($GLOBALS["dbConnect"]); // not strictly necessary

/*	
	Common functions
*/

// function Edit($connection, $pkfield, $edited, $fieldlist, $table = '', $pk = 0) {
// /*
// 	Updates the record indicated by $entrynum with the values in the arrays
// 	$origfieldlist and $fieldlist. These are associative arrays with field names
// 	as keys. Any difference between $origfieldlist and $fieldlist is updated.
// 	This forces fields that have been cleared in the edit to be updated rather
// 	than ignored.
// */
// 	global $opts, $user;
// 	DebugPrint('EDIT: Primary key field',$pk);
// 	if ($pk == 0) {
// 		die('Edit error: no record number provided to update.');
// 	}
// 	if ($pkfield == '') {
// 		die('Edit error: no primary key field name provided.');
// 	}
// 	if ($table == '') {
// 		die('Edit error: no table name supplied.');
// 	}
// 	$sqlwhere = "WHERE $pkfield = $pk";
// 	$sqlset = ' SET ';
// 	
// 	$sqlfetch = "SELECT * FROM $table $sqlwhere";
// 	$result = mysqli_query($GLOBALS["dbConnect"], $sqlfetch) or die('Database error while fetching original values for edit: '.mysqli_error($GLOBALS["dbConnect"])."<br>Query was: $sqlfetch<br>");
// 	$originals = mysqli_fetch_array($result);	// get the original field values
// 	
// 	if (isset($opts['triggers']['update']['before'])) {
// 		include($opts['triggers']['update']['before']);
// 		$validated = array_diff_assoc($newvals, $originals); // $newvals come back from trigger
// 	} else {
// 		$validated = array_diff_assoc($edited, $originals);
// 	}
// 	
// 	DebugPrint("Editing $table",'');
// 	DebugPrint('Fieldlist',$fieldlist);
// 	DebugPrint('Original fields',$originals);
// 	DebugPrint('Edited fields',$edited);
// 	DebugPrint('Validated fields',$validated);
// 
// 	foreach ($validated as $fieldname => $thisfield) {
// 		if ($fieldname != $pkfield) {
// 		
// 		/*	Check for any data types that may need special interpretation.
// 			Currently this is only for 'type' => 'date'.
// 		*/
// 			if ($fieldlist[$fieldname]['type'] == 'date') {
// 				$validated[$fieldname] = date('Y-m-d H:i:s', strtotime($validated[$fieldname]));
// 				DebugPrint('Date field entered', $validated[$fieldname]);
// 			}
// 			$sqlset .= "$fieldname=\"" . mysqli_real_escape_string($GLOBALS["dbConnect"], $validated[$fieldname]) . '", ';
// 			
// 		}
// 	}
// 	if ($sqlset != ' SET ') {	// any updates added?
// 		$sqlset = trim($sqlset, ', ');	// trim off trailing comma
// 		$sqlquery = "UPDATE $table $sqlset $sqlwhere";
// 		
// 		DebugPrint('Edit: SQL Query',$sqlquery);
// 	
// 		$result = mysqli_query($GLOBALS["dbConnect"], $sqlquery) or die('Database error while updating values after edit: '.mysqli_error($GLOBALS["dbConnect"]));
// 		Log_Entry($GLOBALS["dbConnect"], $user, "Updated record in $table: $sqlset $sqlwhere");
// 		if (isset($opts['triggers']['update']['after'])) {
// 			include($opts['triggers']['update']['after']);
// 		}
// 	}
// 	
// return;
// }

// function Insert($connection, $defaultarray, $table = '') {
// /*
// 	Inserts a new blank record. Include any default values specified, then
// 	merge with the results of the preinsert trigger before actually inserting.
// */
// 
// global $opts, $fields, $user;
// 
// 	if ($table == '') {
// 		die('Insert error: no table name supplied.');
// 	}
// 	if (isset($opts['triggers']['insert']['before'])) {
// 		include($opts['triggers']['insert']['before']);
// 	}
// 	$fieldvalues = array_merge($newvals, $defaultarray);
// 	$fieldnames = '';
// 	$fieldvalues = '';
// 	$defaultfields = '';
// 	$defaultvalues = '';
// 	foreach ($fields as $this_key => $thisfield) {	// Build array of default values
// 		if ($thisfield['default'] != '') {	// default specified?
// 			$defaultfields .= $this_key . ', ';
// 			$defaultvalues .= '"' . mysqli_real_escape_string($GLOBALS["dbConnect"], $thisfield['default']) .'", ';
// 		}
// 	}
// 	$defaultfields = trim($defaultfields, ', ');
// 	$defaultvalues = trim($defaultvalues, ', ');
// 	$sqlquery = "INSERT INTO $table ($defaultfields) VALUES ($defaultvalues)";
// 	
// 	DebugPrint('Insert: SQL statement', $sqlquery);
// 	
// 	$result = mysqli_query($GLOBALS["dbConnect"], $sqlquery) or die('Database error after insert: '.mysqli_error($GLOBALS["dbConnect"]));
// 	Log_Entry($GLOBALS["dbConnect"], $user, "Inserted blank record into $table");
// 	if (isset($opts['triggers']['insert']['after'])) {
// 		include($opts['triggers']['insert']['after']);
// 	}
// 
// return;
// }

// function Duplicate($connection, $pkfield, $fieldlist, $table = '', $pk = 0) {
// /*
// 	Inserts a new record with the values in the record whose primary key is $pk.
// 	Uses MySQL INSERT INTO ... SELECT. 
// */
// 	if ($pk == 0) {
// 		die('Copy error: no record number provided to update.');
// 	}
// 	if ($pkfield == '') {
// 		die('Copy error: no primary key field name provided.');
// 	}
// 	if ($table == '') {
// 		die('Copy error: no table name supplied.');
// 	}
// 	$sqlwhere = "WHERE $pkfield=$pk";
// 	$sqlfields = '';
// 	
// 	foreach ($fieldlist as $fieldname => $thisfield) {
// 		if ($fieldname != $pkfield) {	//don't copy primary key
// 			$sqlfields .= "$fieldname, ";
// 		}
// 	}
// 	if ($sqlfields == '') {	// any updates?
// 		die('Copy error: No updates supplied.');
// 	} else {
// 		$sqlfields = trim($sqlfields, ', ');	// trim off trailing comma
// 	}
// 	if (isset($opts['triggers']['copy']['before'])) {
// 		include($opts['triggers']['copy']['before']);
// 	}
// 
// 	$sqlquery = "INSERT INTO $table ($sqlfields) SELECT $sqlfields FROM $table WHERE $pkfield=$pk";
// 	$result = mysqli_query($GLOBALS["dbConnect"], $sqlquery) or die('Database error after insert/copy: '.mysqli_error($GLOBALS["dbConnect"]));
// 	Log_Entry($GLOBALS["dbConnect"], $user, "Duplicated record $pk in $table: $sqlfields");
// 	if (isset($opts['triggers']['copy']['after'])) {
// 		include($opts['triggers']['copy']['after']);
// 	}
// 
// return;
// }

// function Kill($connection, $pkfield, $table = '', $pk = 0) {
// /*
// 	Deletes the record identified by the primary key $pk.
// */
// 	if ($pk == 0) {
// 		die('Copy error: no record number provided to update.');
// 	}
// 	if ($pkfield == '') {
// 		die('Copy error: no primary key field name provided.');
// 	}
// 	if ($table == '') {
// 		die('Copy error: no table name supplied.');
// 	}
// 	if (isset($opts['triggers']['delete']['before'])) {
// 		include($opts['triggers']['delete']['before']);
// 	}
// 	$sqlquery = "DELETE FROM $table WHERE $pkfield=$pk";
// 	$result = mysqli_query($GLOBALS["dbConnect"], $sqlquery) or die('Database error after delete: '.mysqli_error($GLOBALS["dbConnect"]));
// 	if ($user == '') {
// 		$user = $_SERVER['PHP_AUTH_USER'];
// 	}
// 	Log_Entry($connection, $user, "DELETED record $pk from $table");
// 	if (isset($opts['triggers']['delete']['after'])) {
// 		include($opts['triggers']['delete']['after']);
// 	}
// 
// return;
// }

function DebugPrint($title, $content,$unconditional=false) {
	global $opts;
	if ($opts['debug'] or $unconditional) {
		if (is_array($content)) {
			echo "<br>DEBUG: $title:<pre>";
			print_r($content);
			echo '</pre>';
		} else {
			echo "<br>DEBUG: $title: $content<br>";
		}
	}
}

/*	Build_Selections
	Builds the WHERE clause for the query, given a list of fields that can be
	used in it and values for those fields from the user.
*/

function Build_Selections($fields) {

	$sel = '';
	$dup_search = '';
	global $db;

	foreach ($fields as $this_key => $thisfield) {
//	echo "This_Key=$this_key; Thisfield={$thisfield['filter']}<br>";
		if ($thisfield['display'] or $thisfield['search_more']) {	// Can we look at this field?
			if ($thisfield['filter'] != '') {	// Were we given a value?
				$thisfield['filter'] = str_replace('*', '%', $thisfield['filter']);	// Yes, clean up wildcards
				$negate = (substr($thisfield['filter'], 0, 1) == '!') ? 'NOT' : '';
				$thisfield['filter'] = ltrim($thisfield['filter'], '!');
				if (preg_match('/"([^"]+)"/', $thisfield['filter'], $m)) {
					$curfield = $this_key;
					$sel .= "$curfield={$m[0]} AND ";
					DebugPrint('Double Quote Settings', $m);
				} else {
					if ($thisfield['filter'] == '%') {	// single wildcard means search for anything
						$thisfield['filter'] = '_%';	// force at least one character
					}
					if ($thisfield['table'] > '') {	// is there a table specified?
						$curfield = "{$thisfield['table']}.$this_key";
					} else {
						$curfield = $this_key;
					}
				
				
				
					if (strpos($thisfield['filter'], '<>') > 0) { // Between
						$between = explode('<>', $thisfield['filter']);
						$sel .= "$curfield $negate BETWEEN '{$between[0]}' AND '{$between[1]}' AND ";
						DebugPrint('Between settings', $between);
					} else {
						if (strpos($thisfield['filter'], '%') === false) {	// custom wildcards?
							$sel .= "$curfield $negate LIKE '%{$thisfield['filter']}%' AND ";	// No, wrap in default wildcards
						} else {	// yes, use user's
							$sel .= "$curfield $negate LIKE '{$thisfield['filter']}' AND ";
						}
					}
					if ($thisfield['filter'] == '==') { // '==' means search for duplicates
						$dup_search = $curfield;
						break;	// Leave loop; if searching for duplicates, it must be the only criterion
					}
				}
			}
		}
	}

	if ($dup_search) {
		$sel = " $dup_search in (SELECT $dup_search from {$db['db']}.{$db['tb']} GROUP BY $dup_search HAVING COUNT(*)>1)";
	}elseif ($sel !='') {
		$sel = substr($sel, 0, -4);
	}
	DebugPrint("Select where clause", $sel);
	return $sel;
}

/*	Build_Limit
	Build LIMIT clause for query. Pretty simple; just wraps some text around
	the values passed.
*/

function Build_Limit($from, $to) {

	if (!isset($from)) {
		die('Internal error: no FROM value supplied to Build_Limit');
	}
	if (!isset($to)) {
		die('Internal error: no TO value supplied to Build_Limit');
	}
	return " LIMIT $from,$to";
}

/*	Build_Order
	Build ORDER clause for query. Assembles field and sequence (or eventually a
	list of them) and makes an ORDER BY clause out of it.
*/

function Build_Order($fieldlist) {
	
	if (empty($fieldlist)) {
		die('Internal error: no fieldlist supplied for Build_Order');
	}
	if ($fieldlist['table'] != '') {
		$table = $fieldlist['table'] . '.';
	} else {
		$table = '';
	}
	return " ORDER BY $table{$fieldlist['name']} {$fieldlist['order']}";
}

/*	Log_Entry
	Add an entry to the log database. Timestamp is applied automatically.
	Expects the name of the log table in $db['log'], usually set in the
	associated config file.
*/

// function Log_Entry($connection, $username, $entry) {
// 	global $db;
// 	if (!isset($db['log'])) {
// 		die ("Can't add log entry. No logfile name provided.");
// 	}
// //	if ($username == '') {
// //		die("Can't add log entry. No username provided.");
// //	}
// 	$log = $db['log'];
// 	$timestamp = date('Y-m-d H:i:s');	// for this posting
// 	$sqlquery = "INSERT INTO $log (time, user, entry)
// 		VALUES ('$timestamp', '$username', 'V1200 $entry')";
// 	DebugPrint('Log entry SQL', $sqlquery);
// 	$result = mysqli_query($GLOBALS["dbConnect"], $sqlquery) or die('Database error adding log entry: '.mysqli_error($GLOBALS["dbConnect"]));
// }

?>
