<?php
/**
* PmWiki recipe to provide a user-friendly interface to pages produced by the
* DataQuery recipe.
* copyleft 2006-10-03, 2007-08-15 Ben Stallings <Ben@InterdependentWeb.com> .
*/

$RecipeInfo['DataPlates']['Version'] = '2007-08-15';
$FmtPV['$DPversion'] = "'2007-08-15'";
$FmtPV['$ServerSoftware'] = "'".$_SERVER['SERVER_SOFTWARE']."'";

$WikiLibDirs[] = new DataPlate();

# Use a group's custom edit form, if it exists (e.g. if this script generates it)
# and the page being edited is not in the "special" list
unset($PageExistsCache[$DQgroup.".".array_search('edit',$DQglobals['auto'])]);
if ((PageExists($DQgroup.".".array_search('edit',$DQglobals['auto']))) 
	and (!in_array($DQname,$DQglobals['special'])) and ($DQname != $DQgroup) 
	and (($DQgroup == 'DataQuery') or (DQkeymatch($DQgroup,$DQname)))) 
	$PageEditForm = '$Group.'.array_search('edit',$DQglobals['auto']);
	
unset($PageExistsCache[$DQgroup.".".array_search('search',$DQglobals['auto'])]);
if ((PageExists($DQgroup.".".array_search('search',$DQglobals['auto']))) and ($action==search))
	 XLSDV('en', array('SearchFor' => 'Results of your search:',
	 		'SearchFound' => '$MatchCount pages found.'));

class DataPlate {
	var $dirfmt;
	var $iswrite;
	function DataPlate() {
		$this->dirfmt = 'DataPlates';
		$this->iswrite = 0;
	} //end DataPlate::DataPlate()
	
	function read($pagename, $since=0) {
		global $DQglobals;
		//print "attempting to read $pagename "; //uncomment to debug
		$group = FmtPageName('$Group',$pagename);
		if ($group != 'DataQuery') {
			$query = DQloadquery($group);
			if (!is_object($query)) return false;
			if (!$query->key) return false;
		} //endif
		$display = FmtPageName('$Name',$pagename);
		if ($display == $group) $display = 'HomePage';
		if (in_array($DQglobals['auto'][$display],array('view','recordlist','templates','edit','search')))
			//auto-generate a page
			return $this->$DQglobals['auto'][$display]($group,$display);
		return false;
	} //end DataPlate::read()
	
	function write($pagename,$page) {return false;}
	
	function exists($pagename) {
		global $DQglobals;
		//print "Checking existence of $pagename\n"; //uncomment for debugging
		if (!$pagename) return false;
		$query = FmtPageName('$Group',$pagename);
		if (($query != 'DataQuery') and (!in_array($query,$DQglobals['queries']))) return false;
		$display = FmtPageName('$Name',$pagename);
		return ($DQglobals['auto'][$display] > '');
	} //end DataPlate::exists()
	
	function ls($pats) {return array();}
	
	function recordlist($group,$display) {return array('text' => 
		"(:nogroupheader:)\n(:title $group:)\n(:pagelist group=$group list=normal order=natural fmt=#DQlist request=1:)");}
	
	function templates($group,$display) { //pagelist templates
		global $DQglobals;
		if ($group == 'DataQuery') { // status screen, to replace DataQuery's more basic one
			foreach(array("{=\$Name}.".array_search('recordlist',$DQglobals['auto'])
			, "{\$FullName}?q=+?group={=\$Name}?action=search") as $list) {
				$links[] = "[[Configure -> DataQuery.{=\$Name}?action=edit]]"
				. " - [[List -> $list]] | Templates: [[PageList -> {=\$Name}."
				. array_search('templates',$DQglobals['auto'])."?action=edit]]"
				. " - [[EditForm -> {=\$Name}."
				. array_search('edit',$DQglobals['auto'])."?action=edit]]"
				. " - [[Search -> {=\$Name}."
				. array_search('search',$DQglobals['auto'])."?action=edit]]";
			} //end foreach
			if (array_search('recordlist',$DQglobals['auto']) == false) $links[0] = $links[1];
			$page['text'] = "(:nogroupheader:)\n[@\n[[#DQstatus]]\n"
			. "(:if ( !equal {=\$Name} and equal {<\$Group} ) :)\n"
			. "(:messages:)\n[[(Cookbook:)DataQuery]] is aware of the following queries:"
			. "\n\n(:zapform key=use:)\n(:zap datapage=\"DataQuery.QueriesToUse\" use:)"
			. "\n(:zap savedata=\"queries\" use:)\n||border=0"
			. "\n(:if [ !equal {=\$Name} and ( inlist {=\$Name}|queries|DataQuery.QueriesToUse "
			. "or !exists DataQuery.QueriesToUse ) ] :)"
			. "\n|| {=\$Name}:||(:input checkbox queries[] {=\$Name} checked=checked:) ".$links[0]." ||"
			. "\n(:if ( !equal {=\$Name} and !inlist {=\$Name}|queries|DataQuery.QueriesToUse "
			. "and exists DataQuery.QueriesToUse ) :)"
			. "\n|| {=\$Name}:||(:input checkbox queries[] {=\$Name}:) ".$links[1]." ||"
			. "\n(:if equal {>\$Group}:)\n\nCheck the boxes of the queries you wish to use. "
			. "(:input submit value='Save Changes':)(:zapend:)\n\n"
			. "(:zapform key=new:)Create a New Query named (:input text query:)"
			. "(:zap nextpage=\"DataQuery.{query}?action=edit\" new:)(:input submit value=\"Edit\":)\n"
			. "(:zapend:)\n----\nVersion information "
			. "(click to upgrade):\n* [[PmWiki -> PmWiki:Download]] {\$Version}\n"
			. "* [[(Cookbook:)DataQuery]] {\$DQversion}\n* [[(Cookbook:)DataPlates]] {\$DPversion}\n"
			. "* [[ZAP -> http://www.fast.st/zapbeta/index.php?n=main/Downloads]] {\$ZAPversion}\n"
			. "* [[ZAPtoolbox -> http://www.fast.st/zapbeta/index.php?n=main/Downloads]] {\$ZAPtoolboxversion}\n"
			. "* {\$ServerSoftware}\n(:ifend:)\n[[#DQstatusend]]\n@]\n";
		} else { // generate $group.Templates based on query structure
			$query = DQloadquery($group);
			$page['text'] = "[@\n[[#DQlist]]\n(:if ( equal {<\$Group} and ! equal {=\$Name} ):)\n|| class=DQlist\n";
			$cols = $query->columns;
			$primary = $query->config['key'];
			foreach ($cols as $name => $col) { // column headings for #DQlist template
				if (substr($name,0,3) == 'pm_') continue; //skip all pm_ fields
				$page['text'] .= "||! [[$name -> {*\$ScriptUrl}?{*\$QueryString}&order="
				. ($name == $primary ? "natural" : "\$:$name")."]] ";
				//determine whether the column is numeric or not
				$cols[$name]->mt = $query->db->MetaType($col->type);
				$cols[$name]->numeric = ((in_array($cols[$name]->mt, array('N','I','R'))));
			} //end foreach column heading
			$page['text'] .= "||\n(:if:)\n";
			foreach ($cols as $name => $col) { // data cells
				if (substr($name,0,3) == 'pm_') continue; //skip all pm_ fields
				$data = ($name == $primary ? '[[{=$Group}/{=$Name}]]' : '{=$:'.$name.'}');
				//right-justify numbers, left justify text
				$page['text'] .= ($col->numeric ? "|| $data" : "||$data ");
			} //end foreach data cell
			$page['text'] .= "||\n[[#DQlistend]]\n\n[[#DQview]]\n|| class=DQview align=center\n"
			. "(:if !equal {=\$FullName}:)\n";
			foreach (array_keys($cols) as $name) // DQview template
				if (($name) and (substr($name,0,3) != 'pm_'))
					$page['text'] .= "||! ".$name."||{=\$:".$name."} ||\n";
			$page['text'] .= "(:ifend:)\n[[#DQviewend]]\n@]";
		} //end if group = DataQuery
		return $page;
	} //end DataPlate::templates
	
	function view($group,$display) { // display a single record
		global $DQgroup,$action,$DQglobals,$DQname;
		if (($group == 'DataQuery') and (!in_array($DQname,$DQglobals['special']))) { 
			//display query configuration in its EditForm
			$action = 'edit';
			return $this->edit($group,$DQname);
		} //endif
		$q = DQloadquery($DQgroup);
		return array('text' => "(:if !equal {*\$:".$q->config['key']."}:)\n(:messages:)\n"
		. "(:pagelist name={*\$FullName} fmt=#DQview:)\n(:ifend:)");
	} //end DataPlate::view
	
	function edit($group,$display) { // generate $Group.EditForm
		global $DataQueries, $DQglobals, $DQgroup, $DQname, $WikiDir, $DB;
		$page['text'] = "(:title Editing {*\$Group} {*\$Name}:)\n"
		. "(:messages:)\n(:zapform key=edit:)\n(:table class=DQedit:)\n";
		if ($group == 'DataQuery') { // generate DataQuery.EditForm
			$fields = array('database','table');
			$page['text'] .= "(:cellnr colspan=5:)After making a change, click an Update button "
			. "before proceeding.\n(:cellnr class=head:)Database:\n"
			. "(:cell colspan=4:)(:input default database {*\$:database}:)\n";
			$thisq = DQloadquery($DQname);
			$config = $thisq->config;
			$i = 1; $joinedtables = array($config['table']);
			while ($config["join_field$i"]>'') { //generate list of joined tables
				list($joinedtables[],$f) = explode('.',$config["join_field$i"]);
				list($joinedtables[],$f) = explode('.',$config["join_to$i"]);
				$i++;
			} //end while
			$db = ($config['database'] ? $config['database'] : $DQglobals['databases'][0]);
			foreach ($DQglobals['databases'] as $d)
				$page['text'] .= "(:input select database $d:)\n";
			$page['text'] .= "(:cellnr class=head:)Primary Table:\n"
			. "(:cell colspan=4:)(:input default table {*\$:table}:)\n";
			$joinfields = array(); $tofields = array();
			foreach ($DB[$db]->MetaTables() as $t) {
				$page['text'] .= "(:input select table $t:)\n";
				//generate lists of possible join fields
				foreach ($DB[$db]->MetaColumnNames($t,true) as $f) {
					$n = '';
					while (in_array("$t$n",$joinedtables)) {
						$joinfields[] = "$t$n.$f";
						$tofields[] = "$t$n.$f";
						$n = ($n=='' ? 2 : $n+1);
					} //end while
					$tofields[] = "$t$n.$f";
				} //end foreach field
			} //end foreach table
			$page['text'] .= "(:input submit value=Update:)\n";
			if (in_array($config['table'],$DB[$db]->MetaTables())) { 
				//create drop-down menus for joins
				$page['text'] .= "(:cellnr class=head:)Joins:\n(:cell colspan=4:)\n";
				$i = 1;
				do {
					array_push($fields,"join_field$i","join_to$i","join_type$i");
					$page['text'] .= "(:input default join_field$i '{*\$:join_field$i}':)\n"
					. "(:input select join_field$i '':)";
					foreach ($joinfields as $f) 
						$page['text'] .= "\n(:input select join_field$i '$f':)";
					$page['text'] .= " (:input default join_type$i '{*\$:join_type$i}':)\n"
					. "(:input select join_type$i '' '1 <-> 1':)\n"
					. "(:input select join_type$i 'LEFT' '1 <-> ?':)\n"
					. "(:input select join_type$i 'RIGHT' '? <-> 1':)\n"
					. "(:input default join_to$i '{*\$:join_to$i}':)\n"
					. "(:input select join_to$i '':)";
					foreach ($tofields as $f) $page['text'] .= "\n(:input select join_to$i '$f':)";
					$i++; $page['text'] .= "\\\\\n";
				} while ($config["join_field".($i-1)]);
				array_push($fields,'display','key','group','order','limit','where');
				$page['text'] .= "\n(:cellnr:)Fields to use:\\\\\n(in desired order)\\\\\n"
				. "(:input submit value=Update:)\n"
				. "(:cell colspan=4:)(:textarea name=display rows=5 cols=50:)"
				. "(:keep {*\$:display}:)(:textareaend:)\n"
				. "(:input default key {*\$:key}:)"
				. "(:cellnr class=head:)Field\n(:cell class=head:)Key\n(:cell class=head:)Group\n"
				. "(:cell class=head:)Order\n(:cell class=head:)"
				. "Criteria (use =<>, LIKE '%', or [parameters])\n";
				$display = str_replace(",,",",",str_replace("[[<<]]",",",$config['display']));
				$grp = explode(",",$config['group']);
				$order = explode(",",$config['order']);
				foreach(explode(",",$display) as $d) { //ex. "foo AS bar"
					$d = substr(stristr($d," AS "),4); //ex. "bar"
					if ($d == "") continue;
					$page['text'] .= "(:cellnr:)$d\n(:cell:)(:input radio key '$d':)\n"
					. "(:cell:)(:input checkbox group[] '$d'"
					. (in_array($d,$grp) ? " checked=checked" : "")
					. ":)\n(:cell:)\n(:input select order[] '':)\n(:input select order[] '$d' ^"
					. ((in_array($d,$order) or (in_array($config['table'].".".$d,$order))) ? " selected=selected" : "")
					. ":)\n(:input select order[] '$d DESC' v"
					. (in_array("$d DESC",$order) ? " selected=selected" : "")
					. ":)\n(:cell:)\n(:input text cond_$d \"{*\$:cond_$d}\" size=35:)\n";
					$fields[] = "cond_".$d;
				} //end foreach
				$page['text'] .= "(:cellnr class=head:)Where:\n(:cell colspan=4:)"
				. "(:textarea name=where rows=2 cols=50:)(:keep {*\$:where}:)(:textareaend:)\n"
				. "(:cellnr class=head:)Limit:\n(:cell colspan=4:)"
				. "(:input text limit {*\$:limit} size=3".$this->mask("###")
				. ":) (leave blank to return all matching records)";
			} //end if table exists
		} else { // generate $Group.EditForm based on query structure
			$query = DQloadquery($group);
			foreach ($query->structure as $key => $t) if (strcasecmp($key,$group)==0) {$cols=$t; break;}
			//print_r($cols); //uncomment this line to help you match field types
			$fields = $linked = array();
			foreach (explode("\n",$query->display) as $d) { //each field to be displayed
				list($c,$name) = explode(" AS ",trim($d," ,\r")); //column and "as" name
				if (substr($name,0,3) == 'pm_') continue; //skip all pm_ fields
				if ($query->joins[$c]>'') { //a join exists on this field
					 list($t,$f) = explode(".",$query->joins[$c]);
					 $linked[$t]['name'] = $name;
					 continue; //skip for now; we'll come back to linked fields later
				} //end if joined
				list($t,$f) = explode(".",$c); //table and field name
				if ($t != $query->config['table']) { // field is in a joined table
					 $linked[$t][] = $c;
					 continue; //again, we'll come back to linked fields later
				} //end if joined
				$col = $query->structure[$query->config['table']][$f]; //structure of the current field
				$mt = $query->db->MetaType($col->type);
				//print "field $name is of type ".$col->type." and metatype $mt\n";
				if ($col->type == 'decimal') $col->max_length++; //room for decimal point
				$type = ($DQglobals['fieldtypes'][$col->type] 
				? $DQglobals['fieldtypes'][$col->type]
				: (substr($col->type,0,3)=='set' ? $DQglobals['fieldtypes']['set']
					: $DQglobals['fieldtypes'][$mt]));
				if (($type == 'text') and (in_array($name,$DQglobals['passwordfields']))) $type = 'password';
				$fields[] = $name;
				if (($mt == 'R') or ($col->auto_increment == 1) or ($type == 'hidden')) {
					$page['text'] .= "\n(:input hidden $name '{*\$:$name}':)\n";
					continue;
				} //end if hidden
				$row = "\n(:cellnr class=head:)".($col->not_null == 1 ? "%class=required%" : "")
				. "$name:\n(:cell:)";
				if ($type == 'textarea') {
					$row .= "(:textarea name=$name rows=".$DQglobals['rows']
					. " cols=".$DQglobals['maxsize'].":)(:keep {*\$:$name}:)(:textareaend:)";
				} elseif ($type == 'date') {
					$row .= "(:input text $name '{*\$:$name}' size=10 maxlength=10".$this->mask("####-##-##")
					. ":) (YYYY-MM-DD)";
				} elseif ($type == 'timestamp') {
					$row .= "(:input text $name '{*\$:$name}' size=19 maxlength=19"
					. $this->mask("####-##-## ##:##:##").":) (YYYY-MM-DD HH:MM:SS)\n";
				} elseif ($type == 'checkbox') {
					$row .= "(:input default $name '{*\$:$name}':)"
					. "(:input checkbox $name 1:)";
				} elseif ($type == 'radios') {
					$row .= "(:input default $name '{*\$:$name}':)";
					if (is_array($col->enums)) {
						foreach ($col->enums as $option) $row .= "(:input radio $name $option:)"
							. trim($option,"'")." ";
						if ($col->not_null != 1) $row .= "(:input radio $name '':)None ";
					} else $row .= "(:input radio $name 1:)Yes (:input radio $name 0:)No";
				} elseif ($type == 'numeric') {
					if ($col->type == 'decimal') $d = $col->max_length - $col->scale - 1;
					else $d = "foo";
					$mask = ''; for ($i=0; $i<$col->max_length; $i++) 
						$mask .= ($i === $d ? "." : "#");
					$row .= "(:input text $name '{*\$:$name}' size=". $col->max_length
					. " maxlength=".$col->max_length.$this->mask($mask).":) (Numbers only)";
				} elseif ($type == 'file') {
					$row .= "File uploads not yet implemented.";
				} elseif ($type == 'select') {
					$row .= "(:input default $name {*\$:$name}:)";
					if ($col->not_null != 1) $row .= "\n(:input select $name '':)";
					foreach ($col->enums as $option) $row .= "\n(:input select $name ".trim($option,"'").":)";
				} elseif ($type == 'multiple') {
					$options = explode(",",trim(substr($col->type,3),"()"));
					$size = (count($options) > $DQglobals['rows'] ? $DQglobals['rows'] : count($options));
					$row .= "(:if inlist ".trim($options[0],"'")."|$name|{*\$FullName}:)(:input select ".$name."[] "
					. $options[0]." size=$size multiple=multiple selected=selected:)"
					. "\n(:else:)(:input select ".$name."[] ".$options[0]." size=$size multiple=multiple:)";
					foreach ($options as $option) {
						if ($option == $options[0]) continue;
						$row.= "\n(:if inlist ".trim($option,"'")."|$name|{*\$FullName}:)"
						. "(:input select ".$name."[] $option selected=selected:)"
						. "\n(:else:)(:input select ".$name."[] $option:)";
					}//end foreach $options
					$row .= "[-(press ctrl (Windows) or command (Mac) to select multiple)-]";
				} elseif ($type == 'checkboxes') {
					$options = explode(",",trim(substr($col->type,3),"()"));
					foreach ($options as $option) {
						$row.= "\n(:if inlist ".trim($option,"'")."|$name|{*\$FullName}:)"
						. "(:input checkbox ".$name."[] $option checked=checked:)".trim($option,"'")
						. "\n(:else:)(:input checkbox ".$name."[] $option:)".trim($option,"'");
					}//end foreach $options
				} elseif ($type == 'password') {
					$row .= "(:input password $name '{*\$:$name}' "
					. ($col->max_length > 0 ? "maxlength=".$col->max_length." size="
					. ($col->max_length < $DQglobals['maxsize'] ? $col->max_length 
					: $DQglobals['maxsize']) : "").":)";
				} else { // use a text input
					$row .= "(:input text $name '{*\$:$name}' "
					. ($col->max_length > 0 ? "maxlength=".$col->max_length." size="
					. ($col->max_length < $DQglobals['maxsize'] ? $col->max_length 
					: $DQglobals['maxsize']) : "").":)";
				} //end if type
				/*if ($DQglobals['validate'][$type] > "")
					$row .= "(:zap validate_$name=\"".$DQglobals['validate'][$type]
					. " ? action=zap : msg=+Invalid format for $name\" edit:)";*/
				$page['text'] .= "$row\n";
			} //end foreach $col
			foreach ($linked as $t => $f) { //display linked fields as (:data:) markups
			  if (!in_array($t, $query->db->MetaTables())) $t = rtrim($t,'0123456789');
				if ((!in_array($t, $query->db->MetaTables())) or (!$f['name'])) continue;
				$fields[] = $name = $f['name']; unset($f['name']);
				foreach ($f as $k => $v) { //remove numbers from duplicate tables
					list($ft,$ff) = explode('.',$v);
					if (!in_array($ft, $query->db->MetaTables())) $f[$k] = rtrim($ft,'0123456789').'.'.$ff;
				} //end foreach $f
				$c = "\"SUBSTRING(".(count($f)>1 ? $query->db->Concat(implode(",' ',",$f)) : $f[0])
				. ",1,{$DQglobals['maxsize']})\"";
				$page['text'] .= "(:cellnr class=head:)$t\n(:cell:)"
				. "(:input default $name {*\$:$name}:)\n(:data "
				. $DQglobals['fieldtypes']['linked']." $name $c:)\n";
			} //end foreach $linked
		} //end if
		$page['text'] .= "\n(:tableend:)\n(:zap savedata=\"".implode(",",$fields)."\" edit:)"
		. "\n%center%(:input submit post ' Save ':)\n(:zapend:)\n";
		if (function_exists('ZAPXdelete'))
			$page['text'] .= "(:zapform key=del:)(:input submit name=DelButton value=Delete:)\n"
			. "(:zap if1=\"equal {DelButton} Delete ? delete=^\" del:)\n"
			. "(:zap delete=\"\" del:)\n(:zapend:)";
		return $page;
	} //end DataPlate::edit()
	
	function search($group,$display) {
		global $DQglobals;
			$page['text'] = "{\$FullUrl}(:searchresults fmt=#DQlist list=normal:)\n"
			. "Enter any part of one or more fields to match.\n\n"
			. "Use =, <, or > to show equality or inequality, .. for a range, % as a wildcard.\n"
			. "(:input form method=get:)\n(:input defaults request=1:)\n(:input hidden action search:)\n"
			. "(:input hidden q 'group=$group':)\n(:table class=DQedit:)\n";
			$query = DQloadquery($group);
			//print_r($query->columns);
			foreach ($query->structure as $key => $t) if (strcasecmp($key,$group)==0) {$cols=$t; break;}
			//print_r($cols); //uncomment this line to help you match field types
			$fields = array();
			foreach (explode("\n",$query->display) as $d) { //each field to be displayed
				list($c,$name) = explode(" AS ",trim($d," ,\r")); //column and "as" name
				if (substr($name,0,3) == 'pm_') continue; //skip all pm_ fields
				list($t,$f) = explode(".",$c); //table and field name
				$col = $query->structure[$t][$f]; //structure of the current field
				$mt = $query->db->MetaType($col->type);
				//print "field $name is of type ".$col->type." and metatype $mt\n";
				$type = ($DQglobals['fieldtypes'][$col->type] 
				? $DQglobals['fieldtypes'][$col->type]
				: (substr($col->type,0,3)=='set' ? $DQglobals['fieldtypes']['set']
					: $DQglobals['fieldtypes'][$mt]));
				if (($type == 'text') and (in_array($name,$DQglobals['passwordfields']))) continue;
				$fields[] = $name;
				$row = "\n(:cellnr class=head:)$name:\n(:cell:)";
				if ($type == 'textarea') {
					$row .= "(:textarea name=$name rows=".$DQglobals['rows']
					. " cols=".$DQglobals['maxsize'].":)(:keep {*\$:$name}:)(:textareaend:)";
				} elseif ($type == 'date') {
					$row .= "(:input text $name '{*\$:$name}' size=10:) (YYYY-MM-DD)";
				} elseif ($type == 'timestamp') {
					$row .= "(:input text $name '{*\$:$name}' size=19:) (YYYY-MM-DD HH:MM:SS)\n";
				} elseif ($type == 'checkbox') {
					$row .= "(:input default $name '{*\$:$name}':)"
					. "(:input checkbox $name 1:)";
				} elseif ($type == 'radios') {
					$row .= "(:input radio $name '' checked=checked:)any";
					if (is_array($col->enums)) {
						foreach ($col->enums as $option) $row .= "(:input radio $name $option:)"
							. trim($option,"'")." ";
						if ($col->not_null != 1) $row .= "(:input radio $name '':)None ";
					} else $row .= "(:input radio $name 1:)Yes (:input radio $name 0:)No";
				} elseif ($type == 'numeric') {
					$row .= "(:input text $name '{*\$:$name}' size=". $col->max_length
					. ":) (Numbers only)";
				} elseif ($type == 'file') {
					$row .= "File uploads not yet implemented.";
				} elseif ($type == 'select') {
					$row .= "(:input select $name value='' label='any':)";
					if ($col->not_null != 1) $row .= "\n(:input select $name '':)";
					foreach ($col->enums as $option) $row .= "\n(:input select $name ".trim($option,"'").":)";
				} elseif ($type == 'multiple') {
					$options = explode(",",trim(substr($col->type,3),"()"));
					$size = (count($options) > $DQglobals['rows'] ? $DQglobals['rows'] : count($options));
					$row .= "(:if inlist ".trim($options[0],"'")."|$name|{*\$FullName}:)(:input select ".$name."[] "
					. $options[0]." size=$size multiple=multiple selected=selected:)"
					. "\n(:else:)(:input select ".$name."[] ".$options[0]." size=$size multiple=multiple:)";
					foreach ($options as $option) {
						if ($option == $options[0]) continue;
						$row.= "\n(:if inlist ".trim($option,"'")."|$name|{*\$FullName}:)"
						. "(:input select ".$name."[] $option selected=selected:)"
						. "\n(:else:)(:input select ".$name."[] $option:)";
					}//end foreach $options
					$row .= "[-(press ctrl (Windows) or command (Mac) to select multiple)-]";
				} elseif ($type == 'checkboxes') {
					$options = explode(",",trim(substr($col->type,3),"()"));
					foreach ($options as $option) {
						$row.= "\n(:if inlist ".trim($option,"'")."|$name|{*\$FullName}:)"
						. "(:input checkbox ".$name."[] $option checked=checked:)".trim($option,"'")
						. "\n(:else:)(:input checkbox ".$name."[] $option:)".trim($option,"'");
					}//end foreach $options
				} else { // use a text input
					$row .= "(:input text $name '{*\$:$name}' "
					. ($col->max_length > 0 ? " size="
					. ($col->max_length < $DQglobals['maxsize'] ? $col->max_length 
					: $DQglobals['maxsize']) : "").":)";
				} //end if type
				$page['text'] .= $row;
			} //end foreach
		$page['text'] .= "\n(:tableend:)\n%center%(:input submit submit 'Search':)\n(:input end:)\n";
		return $page;
	}
	
	function mask($pattern) {
		global $DQglobals;
		if ($DQglobals['scriptfile'] == '') return;
		return " onKeyDown='javascript:return dFilter (event.keyCode, this, \"$pattern\");'";
	} //end DataPlate::mask()
	
} //end of DataPlate object