Location: PHPKode > projects > CHASERS > chasers_gpl_snapshot1a/engine_functions.php
<?php
/*
<LICENSE>
This file is part of CHASERS.
CHASERS is Copyright (c) 2003-2008 Downtown Emergency Service Center (DESC).
All rights reserved.
For more information, about DESC, see http://www.desc.org/.
For more information about CHASERS, see http://chasers.desc.org/.

CHASERS is free software: you can redistribute it and/or modify
it under the terms of version 3 of the GNU General Public License 
as published by the Free Software Foundation.

CHASERS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with CHASERS.  If not, see <http://www.gnu.org/licenses/>.

</LICENSE>
*/

// ENGINE FUNCTIONS
function elink($object,$id,$label,$options=null)
{
	/*
	 * A quick-link engine function, for default view action
	 */

	return link_engine(array('object'=>$object,'id'=>$id),$label,'',$options);
}

function qelink($rec,$def,$label,$options=null)
{
	return elink($def['object'],$rec[$def['id_field']],$label,$options);
}

function link_engine($control_array,$label='',$control_array_variable='',$link_options=null)
{
	$control_array_variable = orr($control_array_variable,'control');
      $page=orr($control_array['page'],'display.php');  //figure out destination page
      $extras=explode('?',$page);
      $page=orr($extras[0],$page);
      $extras=$extras[1];
      $anchor=$control_array['anchor'];

      $main_controls=array('object','action','id','rec_init','list','format','sql');

      if (is_array($control_array)) {
	    $control_array=array_change_key_case($control_array,CASE_LOWER); //WORK IN LOWER CASE
	    $control_array['action']=orr($control_array['action'],'view'); //DEFAULT IS VIEW

	    //CHECK PERMISSIONS TO DETERMINE WHETHER A LINK IS GENERATED
	    $perm = engine_perm($control_array);
	    foreach($control_array as $key=>$value) {
		  //initiallize things like object, action, rec_init and id
		  if (in_array($key,$main_controls)) {
			$$key=$value;
			unset($control_array[$key]);
		  }
	    }

	    $def = get_def($object);
	    $allow = $def['allow_'.$action];

      } else {
	    return dead_link('LINK_ENGINE() REQUIRES AN ARRAY');
      }

      $page = ($action=='list') ? $page : 'display.php'; //only list may be displayed elsewhere (for now)

      if (!$object) {
		return dead_link(alt($label,'LINK_ENGINE() CANNOT SEND A NULL OBJECT TO ENGINE'));
      }

      if ($rec_init and (in_array($action,array('add','widget')))) {
	    foreach($rec_init as $key=>$value) {
		    if (!get_magic_quotes_gpc()) {
			    $value = addslashes($value);
		    }
		    $init_str .= '&'.$control_array_variable."[step]=new&{$control_array_variable}[rec_init][$key]=" . $value;
	    }
      }

	if ($sql) {
		if (!is_array($sql)) {
			$sql = array($sql);
		}
		foreach ($sql as $key => $link_sql) {
			if (!get_magic_quotes_gpc()) {
				$link_sql = addslashes($link_sql);
			}
			$init_str .='&'.$control_array_variable.'[sql]['.$key.']='.$link_sql;
		}
	}
      //pass the remaining elements in control_array to engine
      if (is_array($control_array)) {
		foreach ($control_array as $key=>$value) {
			if (!get_magic_quotes_gpc()) {
				$value = addslashes($value);
			}
			$control_str .= '&'.$control_array_variable."[$key]=" . $value;
		}
      }
      if ($list) {
	    $control_str .= '&'.$control_array_variable.'[list]='.urlencode(percent_encode(serialize($list)));
      }
      return ( ($perm && $allow)||engine_perm('super_user') )//super user
		? hlink($page.'?'
			  . ($extras ? $extras.'&' : '')
			  . $control_array_variable."[action]=$action&"
			  . $control_array_variable."[object]=$object&"
			  . $control_array_variable.'[format]='.$format.'&'
			  . $control_array_variable."[id]={$id}{$init_str}{$control_str}"
			  . ($anchor ? '#'.$anchor : '') 
			  ,orr($label,$action),'',$link_options)
		: dead_link(orr($label,$action),$link_options);
}

function call_engine($control,$control_array_variable='control',$NO_TITLE=false,$NO_MESSAGES=false,&$TOTAL_RECORDS,&$PERM)
{
      //USE IN PLACE OF display.php
      //RETURNS FORMATTED OUTPUT FOR PLACEMENT ON PAGE
	$control = unserialize_control($control);
      if (!engine_perm($control)) {
		$PERM = false;
		$action = $control['action'];
		$def = get_def($control['object']);
		$singular = $def['singular'];
		return oline("You don't have permission to $action $singular records.");
      } else { 
		$PERM=true; 
	}
      $ENGINE   = engine($control,$control_array_variable);
      $title    = $ENGINE['title'];
      $output   = $ENGINE['output'];
      $message  = $ENGINE['message'];
      $commands = $ENGINE['commands'];   //Commands aren't used
	$TOTAL_RECORDS = $ENGINE['total_records'];

      $out = $NO_TITLE ? '' : $title;
      $out .= $NO_MESSAGES ? '' : ($message ? oline() . oline(box(bigger(red($message))),2) : '');
      $out .= $output;

      return $out;
}

function unserialize_control($control)
{
	//FIXME: this function is getting called too many times, and removing too many slashes

      // HAS THE ABILITY TO UNSERIALIZE EITHER A COMPLETELY SERIALIZED ARRAY,
      // OR AN ARRAY WITH SERIALIZED SUB-ARRAYS. AS OF NOW, IT WON'T WORK IF 
      // THERE ARE SERIALIZED ELEMENTS IN FURTHER SUB-ARRAYS (OF SUB-ARRAYS).
      if ($a=unserialize(percent_decode(urldecode(stripslashes($control))))) {
		return $a;
      }
      if (is_array($control)) {
		foreach ($control as $key=>$value) {
			$control[$key] = unserialize_control($value);
		}
		return orr($control,array());
      }
	return $control;
}

function engine_control_array_security($name = 'control')
{
	$security = get_engine_def('control_array_security');

	foreach ($security as $el) {
		unset($_REQUEST[$name][$el]);
		unset($_GET[$name][$el]);
		unset($_POST[$name][$el]);
	}
	
}

function engine_perm($control,$access_type='')
{
      $PERM = false;

	//hard coding a preliminary super-user check here
	if ( has_perm('super_user','S') ) {
		return true;
	}
	
	if (!$control || $control=='super_user') {
		return false;
	}

	$action = $control['action'];
      $object = $control['object'];
	$id     = $control['id'];

      $def = get_def($object);

      $perm=$def["perm_$action"];
      $access_type = orr($access_type,
				 ($action=='view' || $action=='list') ? 'R' : 'W');

	//check for read access to verything
	if ( $access_type == 'R' && has_perm('read_all') ) {
		return true;
	}

	// read-only mode?
	if (db_read_only_mode() && $access_type == 'W') {
		return false;
	}

	//determine protected status
	if (isset($id) and $id != 'list' and ($access_type=='W') && is_protected_generic($object,$id)) {
		return false;
	}

      if (!is_array($perm)) {
		$perm=split(',',$perm);
      }
      foreach($perm as $permission) {
		if ($permission=='self') {
			global $UID;
			//must fetch record to determine if self
			if ( (sql_num_rows(get_generic(build_self_filter($control),'','',$def)) > 0) 
			     || ($action=='list' && in_array($UID,$control['list']['filter'])) ) {
				$tPERM = true;
				//$tPERM = is_self($control['id']); //is this person editing or accessing their own record?
			} else {
				$tPERM=false;
			}
		} elseif(preg_match('/^my_/',$permission)) {
			// check to see if client_id from record,filter or rec-init is related to user 
			$tPERM = staff_client_relation_generic($control,$permission);

		} elseif(false) {
			// OTHER FUNCTIONALITY CAN BE BUILT HERE.
		} elseif (($permission=='any') || !$permission ) {
			//default to granting access if perm_type hasn't been specified
			//this only generates a link, it doesn't by-pass engine's permission checks.
			$tPERM=true;
		} else {
			// STANDARD PERMISSION PROCESSING
			$tPERM=has_perm($permission,$access_type);
		}
		$PERM = $tPERM ? $tPERM : $PERM;
      }
	//per-record perm checking - after general check
	if ($action=='add') {
		$rec = $control['rec_init'];
	} elseif ($def && ($action !== 'list') && ($object !== 'generic_sql_query') ) {

		if (!is_numeric($id) || $id < CG_POSTGRESQL_MAX_INT) {
			$res = get_generic(array($def['id_field']=>$id),'','',$def);
			if (sql_num_rows($res) > 0) {
				$rec = sql_fetch_assoc($res);
			}
		}
	}

	$fn = $def['fn']['engine_record_perm'];
	if ($rec && ($fn !== 'engine_record_perm_generic') ) {
		$PERM = call_user_func($fn,$control,$rec,$def);
	}

      return $PERM;
}

function build_self_filter($control)
{
	global $UID;
	$def = get_def($control['object']);
	$id = $control['id'];
	$fields = $def['fields'];
	$staff_filt = array();
	foreach ($fields as $key=>$conf) {
		if ($conf['data_type'] == 'staff') {
			$staff_filt[$key]=$UID;
		}
	}
 	return ($id) ? array($def['id_field']=>$id,$staff_filt) : array($staff_filt);
}

function staff_client_relation_generic($control,$type)
{

	/*
	 * Determines relationships between clients/staff or staff/staff
	 */

	global $UID;
	
	$def = get_def($control['object']);
	
	$action = $control['action'];

	switch ($action) {
	case 'delete':
	case 'view':
	case 'edit':
		//grab record and check for client id
		$id  = $control['id'];
		$res = get_generic(array($def['id_field']=>$id),'','',$def);
		if (sql_num_rows($res)<1) {
			return false;
		}
		$rec = sql_fetch_assoc($res);
		$cid = $rec[CG_MAIN_OBJECT_DB.'_id'];
		$sid = $rec['staff_id'];
		break;
	case 'add':
		//check rec-init for client id
		$cid = $control['rec_init'][CG_MAIN_OBJECT_DB.'_id'];
		break;
	case 'list':
		//check filter for client id
		$cid = $control['list']['filter'][CG_MAIN_OBJECT_DB.'_id'];
		$sid = $control['list']['filter']['staff_id'];
		break;
	default:
		$cid = false;
		$sid = false;
	}


	switch ($type) {

	case 'my_client':

		if (be_null($cid)) { return false; }
		return staff_client_assigned($cid);

	case 'my_client_clinical':

		if (be_null($cid)) { return false; }
		return staff_client_assigned($cid) && has_perm('clinical');

	case 'my_client_position_project_clinical':

		//check to see if staff's position/project combination has access
		if (be_null($cid)) { return false; }
		return staff_client_position_project_clinical($cid);
		       
	case 'my_client_project':

		//check to see if client and staff project match
		if (be_null($cid)) { return false; }
		return staff_client_project($cid);

	case 'my_supervisee':

		//check to see if staff is supervised by user, directly or indirectly
		if (be_null($sid)) { return false; }
		return staff_is_supervised_by($sid);

	default:

		return false;

	}
}

function engine_record_perm_generic($control,$rec,$def) 
{ // a place holder for custum functions of the form engine_record_perm_{$object}
	return true;
}

function get_engine_config_array()
{
	$filter = $t_filter = array('val_name'=>CG_ENGINE_CONFIG_ARRAY);

	if (CG_SESSION_ENGINE_ARRAY and $e = $_SESSION['STATIC_ENGINE_ARRAY']) {

		$last_mod_time = $_SESSION['STATIC_ENGINE_ARRAY_TIME'];
		$t_filter['<=:changed_at'] = $last_mod_time;
		$mod_time_res = desc_query('SELECT changed_at FROM '.CG_ENGINE_CONFIG_TABLE,$t_filter);

		if (sql_num_rows($mod_time_res) < 1) {
			//config has been updated since last load. continue
			
		} else {
			return $e;
		}
	}

      $res=desc_query('SELECT * FROM '.CG_ENGINE_CONFIG_TABLE,$filter);
      if (sql_num_rows($res) < 1) {
	    outline('Unable to read engine configuration from '.CG_ENGINE_CONFIG_TABLE.' for '.CG_ENGINE_CONFIG_ARRAY);
	    out(hlink('update_engine_config.php?noexists','Create '.CG_ENGINE_CONFIG_ARRAY.' Engine Config Array'));
	    exit;
      }
      $tmp=sql_fetch_assoc($res);

	$e = unserialize($tmp['value']);
	if (CG_SESSION_ENGINE_ARRAY) {
		$_SESSION['STATIC_ENGINE_ARRAY'] = $e;
		$_SESSION['STATIC_ENGINE_ARRAY_TIME'] = $tmp['changed_at'];
	}
      return $e;
}

function get_def($object)
{
	global $engine;

	// this function should always be used to get the object definition,
	// as this will protect code against changes that may come to how
	// the engine array is stored and retrieved.

	if ($def = $engine[$object]) {
		return $def;
	}

	$survey_table = preg_match('/^survey_/',$object);
	$lookup_table = preg_match('/^l_/',$object);

	if (!isTableView($object)) {
		return false;
	} elseif (!$survey_table //surveys are okay
		    and !$lookup_table //lookups are okay too
		    and !engine_perm(null) and !has_perm('any_table','RW')) {
		return false;
	}

	$def = config_undefined_object($object);

	if ( $survey_table ) {
		$def['add_link_always'] = orrn($def['add_link_always'],false);
	}

	if (has_perm('any_table','RW')) { //set read/write permissions for table similar to super-user permissions
		$def = config_any_table_perm($def);
	}

	$engine[$object] = $def;

	return $def;
}

function get_engine_def($element)
{
	// for now, function identical to above (get_def())
	// once db storage is done, this will need to be kept elsewhere

	global $engine;

	return $engine[$element];
}

function get_singular($object)
{
	$def = get_def($object);

	return $def['singular'];
}

function config_object($object)
{

	/*
	 * Configure a CHASERS object, returning a configuration array
	 */

      global $engine;

      /*
	 * set global options not set elsewhere
	 */

      $OBJECT = array('object'=>$object,
			    'table'=>orr($engine[$object]['table'],$object) //the metadata functions require a table
			    );

      $table         = ($OBJECT['table']<>$OBJECT['object']) ? $OBJECT['table'] : $OBJECT['object'];
      $sql_table     = orr($engine[$object]['table_post'],((is_view($table) && is_table('tbl_'.$table)) ? 'tbl_'.$table : $table));
      $config_object = $engine[$object];  //CONFIG FILE OPTIONS
      $sql_object    = sql_metadata_wrapper(sql_metadata($sql_table)); //SQL METADATA 
	$fields_table  = $sql_object; //this must be calculated prior to possible merge w/ view below

	if ($table !== $sql_table) {

		/*
		 * There is more metadata to be gleaned from the db.
		 * Metadata from the table overrides the view.
		 */
		$sql_object = array_merge(sql_metadata_wrapper(sql_metadata($table)),$sql_object);

	}
      $engine_default = set_engine_defaults($object,$table);

	/*
	 * Initialize fields
	 */
      $fields_view = (is_view($OBJECT['table'])) 
		? sql_metadata($OBJECT['table'])
		: array();

      $fields_config_file = is_array($config_object['fields'])
		? $config_object['fields'] : array();

      $engine_object_virtual = array_merge(engine_metadata(array_keys($fields_view),array(),$object,$sql_table),
							 engine_metadata(array_keys($fields_config_file),array(),$object,$sql_table));

      $FIELDS = array_keys(array_merge($fields_view,$fields_table,$fields_config_file));

      $engine_object = engine_metadata($FIELDS,$sql_object,$object,$sql_table); //ENGINE METADATA

      //ACTION SPECIFIC OPTIONS
      $config_object = write_action_options($config_object,$FIELDS);

      //SET GLOBAL OPTIONS
      $global_options = array_keys($engine_default['global_default']); //this contains a complete list of global options

      foreach ($global_options as $option) {

		$value=orrn($config_object[$option],    // 1
				$engine_object[$option],     // 2 
				null,                        // 3 meta_data doesn't come into play here
				$engine_default['global_default'][$option]);   // 4

	    if ($option == 'table_post') {

		  $value=(is_table($value)) ? $value : $OBJECT['table'];

	    }

	    if (!is_null($value)) { //no need to carry around a bunch of null values (that weren't set)

		  $OBJECT[$option]=$value;

	    }

      }

      //FUNCTIONS
      foreach($engine['functions'] as $fn_type => $function) {

		$config_defined_function = $config_object['fn'][$fn_type];
		$OBJECT['fn'][$fn_type]=orr($config_defined_function,$OBJECT['fn'][$fn_type],$fn_type . '_' . $object);

	    if (!function_exists($OBJECT['fn'][$fn_type])) {

		  $OBJECT['fn'][$fn_type] = $function;

	    }

      }

      // FIELDS NOT IN POST TABLE
      $fields_db       = array_keys(array_merge($fields_view,$fields_table));
      $FIELDS_NOT_DB   = array_diff(array_keys($fields_config_file),$fields_db);
      $FIELDS_NOT_POST = array_diff(array_keys($fields_view),array_keys($fields_table));

      $FIELD_OPTIONS=array_keys($engine_default['field_default']);

      foreach ($FIELDS as $field) {

	    if (in_array($field,$FIELDS_NOT_DB)) {

		  foreach ($FIELD_OPTIONS as $option) {

			// SPECIAL TREATMENT FOR FIELDS NOT IN DB
			$value=orrn($config_object['fields'][$field][$option],    // 1
				    $engine['virtual_field_options'][$option],  // 1A
				    $engine_object_virtual['fields'][$field][$option], //2A
				    $engine_object['fields'][$field][$option],    // 2 
				    $sql_object[$field][$option],                 // 3
				    $engine_default['field_default'][$option]);   // 4

			if (!is_null($value)) {

			      $OBJECT['fields'][$field][$option]=$value;

			}

		  }

	    } elseif (in_array($field,$FIELDS_NOT_POST)) {

		  foreach ($FIELD_OPTIONS as $option) {

			// SPECIAL TREATMENT FOR FIELDS NOT IN POST TABLE
			$value=orrn($config_object['fields'][$field][$option],    // 1
				    $engine['view_only_field_options'][$option],  // 1A
				    $engine_object_virtual['fields'][$field][$option], //2A
				    $engine_object['fields'][$field][$option],    // 2 
				    $sql_object[$field][$option],                 // 3
				    $engine_default['field_default'][$option]);   // 4

			if (!is_null($value)) {

			      $OBJECT['fields'][$field][$option]=$value;

			}

		  }

	    } else { //if (!$engine_object['fields'][$field]['system_field'])

		  foreach ($FIELD_OPTIONS as $option) {

			$value=orrn($config_object['fields'][$field][$option],    // 1
				    $engine_object['fields'][$field][$option],    // 2 
				    $sql_object[$field][$option],                 // 3
				    $engine_default['field_default'][$option]);   // 4

			if (!is_null($value)) {

			      $OBJECT['fields'][$field][$option]=$value;

			}

		  }

	    }

      }

      return $OBJECT;

}

function config_undefined_object($object) {

	/*
	 * A wrapper for config_object for on-the-fly object configuration
	 */

	global $engine, $off;

	/*
	 * Check for config files
	 */
	if (is_readable($off . ENGINE_CONFIG_FILE_DIRECTORY . '/'.CG_MAIN_OBJECT_DB.'/config_'.$object.'.php')) {

		include $off . ENGINE_CONFIG_FILE_DIRECTORY . '/'.CG_MAIN_OBJECT_DB.'/config_'.$object.'.php';

	} elseif (is_readable($off . ENGINE_CONFIG_FILE_DIRECTORY . '/config_'.$object.'.php')) {

		include $off . ENGINE_CONFIG_FILE_DIRECTORY . '/config_'.$object.'.php';

	}

	$def=config_object($object);


	/*
	 * Global attributes different from defaults
	 * fixme: these different defaults could still live in 
	 *        a config file somewhere, instead of being hard-coded
	 */
	$def['cancel_add_url'] = CG_ADMIN_URL;
 	$def['perm_add']       = false;
 	$def['perm_edit']      = false;


	/*
	 * Field level attributes different from defaults
	 * fixme: see fixme for global defaults above
	 */
	$fields = $def['fields'];
	foreach ($fields as $name => $field_defs) {

		if ($field_defs['data_type']=='lookup') {

			/*
			 * disable lookup handling
			 */
			$fields[$name]['data_type'] = 'varchar';

		}

	}

	$def['fields']=$fields;

	return $def;
}

function config_generic_sql($def,$res)
{

	/*
	 * Generate a configuration array for sql statements/queries
	 */

	global $engine;

	/*
	 * The defaults for queries are stored in config_generic_sql_query.php
	 */
	$object = $def['object'] = orr($def['object'],'generic_sql_query');
	
	$fields      = sql_field_names($res);
	$list_fields = array();
	$f_default   = $engine['field_default'];
	$sql_meta    = sql_metadata_wrapper(sql_query_metadata($res));
	$engine_meta = engine_metadata($fields,$sql_meta,$object);

	foreach($fields as $field) {


		/*
		 * Fields starting with "_" are hidden by default
		 */
		if (substr($field,0,1) !== '_') {

			array_push($list_fields,$field);

		}

		$defaults['fields'][$field] = $f_default;
		$def['fields'][$field] = array_merge($f_default,$sql_meta[$field],$engine_meta['fields'][$field],orr($def['fields'][$field],array()));

	}

	$def['list_fields'] = $list_fields;

	return $def;
}

function sql_metadata_wrapper($sql_metadata)
{
	/*
	 * A wrapper to fill in additional metadata based on naming conventions etc
	 */

      global $engine;

      $engine_types=$engine['data_types'];
      foreach($sql_metadata as $field => $metadata) {
		//data type 
		$data_type = $metadata['data_type'];

		if (preg_match('/(.*)\(([0-9]*),?([0-9]*)\)$/',$metadata['data_type'],$m)) {
			// strip off length info and set
			$data_type = $m[1];
			$sql_metadata[$field]['length'] = $m[2];
			$sql_metadata[$field]['length_decimal_places'] = $m[3];
		}

	    $found = false;

	    foreach($engine_types as $type => $sql_type) {

		  if (in_array($data_type,$sql_type)) {

			$data_type=$type;
			$found=1;

		  }

	    }

	    if (!$found) {

		  $data_type='unknown';

	    }

	    $sql_metadata[$field]['data_type'] = $data_type;

	    /*
	     * Field default values
	     */
	    $default = $metadata['default'];

	    if ($default==='POSTGRES_SEQUENCE') {

		    unset($sql_metadata[$field]['default']);

		    foreach($engine['actions'] as $action=>$type) {

			    if ($type=='write') {
				    $sql_metadata[$field]['post_'.$action]=false;
			    }

			    $sql_metadata[$field]['display_'.$action]='display';
		    }

		    $sql_metadata[$field]['null_ok']=sql_true();
		    $sql_metadata[$field]['display_add']='hide';

	    }

	    /*
	     * lookup table handling
	     */
	    if (preg_match('/_code$/i',$field) and $l_field = $metadata['lookup_column'] and $l_table = $metadata['lookup_table']
		  and is_table($l_table) and is_field($l_table,$l_field)) {

		    $l_label = is_field($l_table,'description') ? 'description' : $l_field;
		    $sql_metadata[$field]['data_type'] = 'lookup';
		    $sql_metadata[$field]['lookup'] = array('table'=>$l_table,
									  'value_field'=>$l_field,
									  'label_field'=>$l_label);

	    } elseif ($data_type=='array' and preg_match('/((.*)_code)s$/i',$field,$m) and isTableView('l_'.$m[2])) {

		    /*
		     * Varchar[] type "lookup"
		     */
		    $sql_metadata[$field]['data_type'] = 'lookup_multi';
		    $sql_metadata[$field]['lookup'] = array('table'=>'l_'.$m[2],
									  'value_field'=>$m[1],
									  'label_field'=>'description');
		    $sql_metadata[$field]['comment'] = '(ctrl-click to select multiple values)';

	    }
	    
	}
      
      return $sql_metadata;

}

function engine_metadata($fields,$meta=array(),$object='',$table_post='')
{
	/*
	 * Generate config options based on field  naming conventions, etc.
	 */

      global $engine;

	$enmeta  = array();

      $system_fields = $engine['system_fields'];

	/*
	 * some global options will be set for specific actions, and the associated
	 * system fields
	 */
      $deletes=array('is_deleted','deleted_at','deleted_by','deleted_comment');
      $adds=array('added_at','added_by');
      $changes=array('changed_at','changed_by');

      if (in_array_array($deletes,$fields)) {

	    $enmeta['stamp_deletes']=true;

      }

      if (in_array_array($adds,$fields) && is_table($table_post)) {

	    $enmeta['stamp_adds']=true;
	    $enmeta['allow_add']=true;

      }

      if (in_array_array($changes,$fields) && is_table($table_post)) {

	    $enmeta['stamp_changes']=true;
	    $enmeta['allow_edit']=true;

      }

      foreach ($fields as $field) {

	    $new=array();

	    /*
	     * set labels
	     */
	    $tmp_lab = preg_replace('/'.CG_MAIN_OBJECT_DB.'/',CG_MAIN_OBJECT,$field);
	    $tmp_lab = preg_replace('/^desc_/','',$tmp_lab); //bug 14059 (hiding DESC from labels)
	    $new['label'] = preg_replace('/ /','&nbsp;',ucwords(preg_replace('/_/',' ',$tmp_lab)));

	    if (array_key_exists($field,$system_fields)) {
		    //handle system fields here
		    $new = $system_fields[$field];
	    }

	    if (strtolower(substr($field,-3))=='_at') {

		  $new['data_type']='timestamp';

	    } elseif (  (strtolower(substr($field,3))=='is_') ||
			    (strtolower(substr($field,4))=='has_') ||
			    (strtolower(substr($field,4))=='was_')  ) {

		  $new['data_type']='boolean';

	    } elseif ((strtolower(substr($field,-3))=='_by')
			  || ($field == 'staff_id')) {

		    /*
		     * Staff field
		     */

		    if ($field=='staff_id') {

			    $new['label']='Staff';

		    }

		    $new['data_type']         = 'staff';
		    $new['value_format_list'] = 'smaller($x)';
		    $new['order_by_instead']  = 'staff_name('.$field.')';

	    } elseif ($field == CG_MAIN_OBJECT_DB.'_id') {

		    /*
		     * main object field (eg, client, donor, subject etc)
		     */

		  $new['data_type']=CG_MAIN_OBJECT_DB;
		  $new['label']=ucwords(CG_MAIN_OBJECT);
		  $new['order_by_instead']=CG_MAIN_OBJECT_DB.'_name('.$field.')';

	    } elseif ($field == 'dob' ) {

		    /*
		     * fixme: this really belongs in the config_client file
		     */
		    $new['data_type']='date_past';
		    $new['label']='Date of Birth';

	    } elseif ($field == 'ssn' ) {

		    /*
		     * fixme: this really belongs in the config_client file
		     */

		  $new['data_type']='ssn';
		  $new['label']='Social Security #';

	    } elseif (preg_match('/(.*)_code$/i',$field,$matches)
		    && isTableView('l_' . $matches[1])) {

		    /*
		     * sql_metadata might return this information (if the lookup table is referenced in the db), 
		     * with the exception of the description field...
		     * I don't see any 'generic' workaround for that.
		     */

		    $new['data_type'] = 'lookup';

		    //$new = set_engine_actions('show_lookup_code','BOTH',$new); //returns array with actions set
		    
		    $new['show_lookup_code_list']='DESCRIPTION';  //will display only DESCRIPTION for list action
		    $new['label'] = ucwords(substr($new['label'],0,strlen($new['label'])-10)); //10 = length(&nbsp;code)
		    $new['lookup'] = array('table' => 'l_' . $matches[1],
						   'value_field' => $matches[1].'_code',
						   'label_field' => 'description'
						   );

		    $new['lookup']['data_type'] = $meta[$field]['data_type'];
		    $new['lookup']['length']    = $meta[$field]['length'];
		    if (in_array($field,array('desc_project_code','desc_project_housing_code'))) {

			    $new['label'] = 'Project';

		    }		  

	    } elseif (preg_match('/(.*)_code_other$/i',$field,$m) and in_array($m[1].'_code',$fields)) {

		    /*
		     * lookup_code _other functionality - if a table has this_is_a_code field,
		     *    and a this_is_a_code_other text field, this will automatically prompt
		     *    users for a description if they select "other" as the value in the
		     *    this_is_a_code field.
		     *
		     * below, 21=length(&nbsp;code&nbsp;other)
		     */
		    $new['label'] = ucwords(substr($new['label'],0,strlen($new['label'])-21)).' (please specify if Other)';
		    $new['valid'] = array('be_null($x) xor $rec['.$m[1].'_code'.']=="OTHER"'
						  =>'{$Y} must (only) be filled in if Other is selected');

	    }  elseif (preg_match('/(.*)_accuracy$/i',$field,$matches)
			  && in_array($matches[1],$fields)) {

		    /*
		     * Data accuracy functionality
		     */
		    $new['data_type'] = 'lookup';
		    $new['lookup'] = array('table'=>'l_accuracy',
						   'value_field'=>'accuracy_code',
						   'label_field'=>'description',
						   'data_type' => $meta[$field]['data_type'],
						   'length' => $meta[$field]['length']);

	    } elseif (preg_match('/(.*)_source_code$/i',$field,$matches)
			  && in_array($matches[1],$fields)) {

		    /*
		     * Data source functionality
		     */

		    $new['data_type'] = 'lookup';
		    $new['lookup'] = array('table'=>'l_data_source',
						   'value_field'=>'data_source_code',
						   'label_field'=>'description',
						   'data_type' => $meta[$field]['data_type'],
						   'length' => $meta[$field]['length']);

	    } elseif (preg_match('/(.*)_code$/i',$field,$m)) { //catch other '_code' fields

		    $new['label'] = ucwords(substr($new['label'],0,strlen($new['label'])-10)); //10 = length(&nbsp;code)

	    } else {
		  // this is all engine metadata can do
		  // so do nothing here
	    }	

	    if ($meta[$field]['data_type']=='array') {

		    /*
		     * Setting maximum number for the free-flowing (meaning no corresponding lookup table)
		     * array fields. Since this isn't currently in use anywhere, I picked 10...this could be
		     * 100 or 1000...
		     */

		    $new['array_max_elements'] = 10;

	    }

	    foreach(array_keys($engine['actions']) as $action) {

		    $label=$new['label'];
		    if ($action=='list') {

			    $tmp_obj = str_replace(' ','&nbsp;',ucwords(str_replace('_',' ',$object)));
			    $label   = str_replace($tmp_obj.'&nbsp;','',$label);
			    $label   = str_replace('&nbsp;'.$tmp_obj,'',$label);

		    }

		    $new['label_'.$action]=isset($new['label_'.$action]) ? $new['label_'.$action] : $label;

	    }

	    unset($new['label']);
	    $enmeta['fields'][$field]=$new;

      }

	/*
	 * Validity checks
	 */
	if (isset($enmeta['fields'][$object.'_date']) && isset($enmeta['fields'][$object.'_date_end'])) {

		/*
		 * If a date, and a date_end field, assume date_end must be greater than or equal to the date field
		 */
		$sdate_field = $object.'_date';
		$check = array('($x >= $rec["'.$sdate_field.'"]) || be_null($x)'=>'{$Y} must be greater than '.$sdate_field);
		$enmeta['fields'][$object.'_date_end']['valid'] = $check;

	}

      return $enmeta;

}

function set_engine_actions($parameter,$value,$def) {

	/*
	 * Fill in action specific values in $def if not already set.
	 * 
	 * ie: {paramater}_add = value, {parameter}_edit = value, etc
	 */

	global $engine;

	foreach(array_keys($engine['actions']) as $action) {

		if (is_null($def[$parameter.'_'.$action])) {
			$def[$parameter.'_'.$action]=$value;
		}

	}

	return $def;

}

function set_engine_defaults($object,$table='')
{

      global $engine;

      $table = orr($table,$object);

      //set defaults that must be set at runtime
      $DEFAULTS['global_default'] = $engine['global_default'];
      $DEFAULTS['field_default'] = $engine['field_default'];
      $DEFAULTS['global_default']['table'] = $table;
      $DEFAULTS['global_default']['table_post'] = is_table('tbl_'.$object)
	    ? 'tbl_' . $object : $table;
	$primary=sql_primary_keys($table);
	$indexes=array_keys(sql_indexes($table));
	$fields=array_keys(sql_metadata($table)); 
      $DEFAULTS['global_default']['id_field'] = ( is_field($table,$table.'_id')
								  ? $table.'_id'                       //if it exists, table_id
								  : ( is_field($table,substr($table,2,strlen($table)-2))  //trim off l_
									? $table.'_code' //match on code
									: ( $primary[0] 
									    ? $primary[0]  //else go for the primary key
									    : $fields[0] )  //failing that, the first field
									)
								  );
      $singular=orr($engine[$object]['singular'],ucwords(preg_replace("/_/",' ',$object)));
	$singular=preg_replace("/".CG_MAIN_OBJECT_DB."/i",ucwords(CG_MAIN_OBJECT),$singular);
      $DEFAULTS['global_default']['singular'] = $singular;
      $DEFAULTS['global_default']['plural'] = (strtolower(substr($singular,-1))=='y') 
	    ? substr($singular,0,strlen($singular)-1) . 'ies'
	    : $singular . 's';
      $DEFAULTS['global_default']['sel_sql'] = 'SELECT * FROM ' . $table;

	/*
	 * This attempts to derive more meaningful default list fields by looking at primary keys
	 * and indexed columns.
	 *
	 * The logic is thus: 1) if object_id exists, this is the first field, otherwise primary key, otherwise first field
	 *                    2) 2nd index (1st index is primary key) or 2nd field
	 *                    3) 3rd index or 3rd field
	 *
	 * fixme: the object_id is rarely useful to display as a default list field. A far better set of defaults would
	 *        at least include, if available the object_date and object_date_end fields, and then the first non-primary-key-field
	 */
      $list_fields = array();

	/*
	 * 1st field
	 */
	array_push($list_fields,(is_field($table,$table.'_id')
					 ? $table.'_id'                       //if it exists, table_id
					 : ( is_field($table,substr($table,2,strlen($table)-2))  //trim off l_
					     ? $table.'_code' //match on code
					     : ( $primary[0] 
						   ? $primary[0]  //else go for the primary key
						   : $fields[0] )  //failing that, the first field
					     )));
	/*
	 * 2nd field
	 */
	array_push($list_fields,( (isset($indexes[1]) && !strpos($indexes[1],',')) //ignore multi-column indexes
					  ? $indexes[1] : $fields[1]
					  ));
	/*
	 * 3rd field
	 */
	array_push($list_fields, ( (isset($indexes[2]) && !strpos($indexes[2],',') ) //ignore multi-column indexes
					   ? $indexes[2] : $fields[2]
					   ));

	$DEFAULTS['global_default']['list_fields']=array_unique(array_filter($list_fields));
      return $DEFAULTS;

}

function write_action_options($config_object,$FIELDS)
{
	/*
	 * A pass-through function, wherin the action-specific options are filled in. Returns 
	 * a more complete config_object array.
	 */

      global $engine;

      if (!is_array($config_object)) {

	    return $config_object;

      }

      $fields=$config_object['fields'];

      /*
	 * Check for a 'global' to override all fields
	 */
      $config_tmp = $config_object;
      foreach (array_keys($engine['field_default']) as $option) {

		if (array_key_exists($option,$config_tmp)) {

			foreach ($FIELDS as $field) {

				$fields[$field][$option]=$config_tmp[$option];
			}
		}

      }

      foreach (array_keys($engine['action_specific_vars']['global']) as $option) {

		if (isset($config_object[$option])) {

			$value=$config_object[$option];
			unset($config_object[$option]);
			foreach (array_keys($engine['actions']) as $action) {

				if (!isset($config_object[$option.'_'.$action])) {

					$config_object[$option.'_'.$action]=$value;

				}

			}

		}

      }

      if (is_array($fields)) {

		foreach(array_keys($engine['action_specific_vars']['fields']) as $option) {
			
			foreach($fields as $field=>$val) {

				if (isset($fields[$field][$option])) {

					$value=$fields[$field][$option];
					unset($fields[$field][$option]);
					foreach (array_keys($engine['actions']) as $action) {

						if (!isset($fields[$field][$option.'_'.$action])) {

							$fields[$field][$option.'_'.$action]=$value;
						}

					}

				}

			}
		}
      }

      $config_object['fields'] = $fields;

      return $config_object;

}

function blank_generic(&$def, &$rec_init)
{

	/*
	 * Returns a blank record, filling in defaults, and values
	 * passed in rec_init()
	 */

	//-----------------------------------//
	// merging blank with clients previous record
	$id_field = CG_MAIN_OBJECT_DB.'_id';
	if ($def['rec_init_from_previous'] && !be_null($rec_init[$id_field]) ) {
		if ($old_rec = sql_fetch_assoc(get_generic(array($id_field=>$rec_init[$id_field])
									 , $def['id_field'].' DESC','1',$def)) ) {
			$old_rec_merge = array();
			foreach ($old_rec as $o_key=>$o_val) {
				if ($def['fields'][$o_key]['rec_init_from_previous_f']==true) {
					$old_rec_merge[$o_key] = $o_val;
				}
			}
			$rec_init = array_merge($old_rec_merge,$rec_init);
		}
	}
	//-------------------------------//


      $fields=$def['fields'];
      $a=array_keys($fields);
      foreach($a as $key) {

		$default=$fields[$key]['default'];    
		$type=$fields[$key]['data_type'];

		if (isset($rec_init[$key])) {

			$rec[$key]=$rec_init[$key];

		} elseif (isset($default)) {

			if ( $default === "current_$type" || $default === 'NOW' ) {
				
				/*
				 * Current date/timestamp/time 
				 */
				$current = datetimeof('NOW');

				switch ($type) {
				case 'date' :
				case 'date_past' :
				case 'date_future' :
					$rec[$key] = dateof($current);                    
					break;
				case 'time' :
				case 'time_past' :
				case 'time_future' :
					$rec[$key] = timeof($current);
					break;
				case 'timestamp' :
				case 'timestamp_past' :
				case 'timestamp_future' :
					$rec[$key] = datetimeof($current); 
					break;
				}
			} else {

				/*
				 * Evaluate defaults starting with a "$"
				 */
				if (substr($default,0,1)=='$') {

					$default=eval('return '.$default.';');

				} elseif (preg_match('/^EVAL:\s(.*)/',$default,$m)) {

					$default = eval('return '.$m[1].';');

				}

				$rec[$key]=$default;

			}

		} else {

			if ($type<>'multi_rec') {

				$rec[$key]='';

			}

		}

      }

      if ($def['multi_records']) {

		$rec=call_user_func($def['multi']['blank_fn'],$rec,$def);

      }
	
      return $rec;

}

function value_generic($value,$def,$key,$action,$do_formatting=true)
{
	global $engine;
      $field=$def['fields'][$key];
	if (!$field) {
		return $value;
	}

      $type=$field['data_type'];
	//fixme: this should really be done at the formatting stage, since it doesn't webify anything
	// that is occasionally a link (such as DAL progress note field).
	if (!in_array($type,array('html','lookup','table_switch','lookup_multi','array','staff_list')) && !$field['is_html']) {
		$value=webify($value); 
	}
      $show_value = $field['show_lookup_code_'.$action];
      if ($type=='multi_rec') {
	    $type=$field['multi_type'];
      }
      if ($type=='staff') {
	    switch ($show_value)
	    {
	    case 'CODE':
		  break;
	    case 'DESCRIPTION':
		  $value=(!be_null($value)) ? staff_link($value) : $value;
		  break;
	    case 'BOTH':
	    default:
		  $value=(!be_null($value)) ? staff_link($value) . smaller(" ($value)",2) : $value;
	    }
	} elseif ($type == 'staff_list') {

		if (is_array($value)) {

			$n_value = array();
			foreach ($value as $sid) {
				
				switch ($show_value) {
				case 'CODE':
					break;
				case 'DESCRIPTION':
					$n_value[] = staff_link($sid);
					break;
				case 'BOTH':
				default:
					$n_value[] = staff_link($sid) . smaller(" ($sid)",2);
				}
				
			}
			$value = implode(oline(),$n_value);

		}

      } elseif ($type == CG_MAIN_OBJECT_DB) {
	    switch ($show_value)
	    {
	    case 'CODE':
		  break;
	    case 'DESCRIPTION':
		  $value=(!be_null($value)) ? client_link($value) : $value;
		  break;
	    case 'BOTH':
	    default:
		  $value=(!be_null($value)) ? client_link($value) . smaller(" ($value)",2) : $value;
	    }
      } elseif (substr($type,0,4) == 'date') {
		$value = orr(dateof($value),$value); //this is required for pre-formatted values
      } elseif (substr($type,0,9) == 'timestamp') {
		$value = datetimeof(datetotimestamp($value),'US');
	} elseif ($type == 'time') {
		$value = timeof($value,'ampm');
	} elseif ($type == 'currency') {
		$value = orr(currency_of($value),$value); //this is required for pre-formatted values
      } elseif ($type == 'lookup') {
		if (!be_null($value)) {
			// <Optimize Me!>
			$look=$field['lookup'];
			$sql = "SELECT {$look['label_field']} FROM {$look['table']}";
			$filt[$look['value_field']]=$value;
			$new_val=sql_assign($sql,$filt);
			// </Optimize Me!>
			switch ($show_value) {
			case 'CODE':
				$value = alt($value,$new_val);
				break;
			case 'DESCRIPTION':
				$value=alt(orr($new_val,"($value not found in lookup table)"),$value);
				break;
			case 'BOTH':
			default:
				$value=orr($new_val,'(not found in lookup table)') . smaller(" ($value)",2);
			}
		}
	} elseif ($type == 'lookup_multi') {
		if (is_array($value) ) { 
			
			$look=$field['lookup'];
			$sql = "SELECT {$look['label_field']} FROM {$look['table']}";
			if ($action=='list') {
				$show_value = orr($show_value,'DESCRIPTION');
			}
			foreach($value as $t_v) {
				$filt[$look['value_field']]=$t_v;
				$desc = orr(sql_assign($sql,$filt),"($t_v not found in lookup table");
				switch ($show_value) {
				case 'CODE':
					$n_value[] = alt($t_v,$desc);
					break;
				case 'DESCRIPTION':
					$n_value[] = alt($desc,$t_v);
					break;
				case 'BOTH':
				default:
					$n_value[] = $desc. smaller(" ($t_v)",2);
				}
			}
			$value = $do_formatting ? implode(','.oline(),orr($n_value,$value)) : orr($n_value,$value);
		}
      } elseif ($type == 'boolean') {
	    $value = be_null($value) ? '' : (sql_true($value) ? 'Yes' : 'No');
	} elseif ($type =='array') {
		$value = implode(', '.oline(),orr($value,array()));
      } elseif ($type == 'table_switch') {
		list($tmp_id,$tmp_obj) = get_table_switch_object_id($value,$def,$key);
		if ($tmp_id && $tmp_obj) {
			$tmp_def = get_def($tmp_obj);
			$tmp_singular = ucwords($tmp_def['singular']);
			$label = $action=='list' ? $tmp_singular .' '.$tmp_id : 'View/Edit '.$tmp_singular.' Data Record';
			$value = link_engine(array('object'=>$tmp_obj,
							   'action'=>'view',
							   'id'=>$tmp_id),$label);
		}
	}
	elseif (($type=='text') || ($type=='varchar')) {
		$value = hot_link_objects($value);
	}
      //VALUE FORMAT
	$value= be_null(trim($value)) ? '&nbsp;' : $value; // avoid blank cells w/o border
	if ($do_formatting) {
		$a=$field['value_format_'.$action];
		$x=$value;
		$value=eval("return $a;");
	}
    return $value;
}

function label_generic($key,$def,$action,$do_formatting=true)
{
      $field=$def['fields'][$key];
      $type=$field['data_type'];
	if (!$field) {
		return ucfirst($key);
	}
 
      //FORMAT LABEL
	if ( ($action=='add'||$action=='edit') and !$field['null_ok'] ) {
		$req =' '.red('*');
	}

      $label=$field['label_'.$action];
	$display = $field['display_'.$action];
	if (($type=='timestamp') and in_array($action,array('add','edit')) and !in_array($display,array('regular','display'))) {
		if (!$field['null_ok']) {
			$req = red(' *');
		}
 		$label = oline($label . ' Date'.$req)
			. right('Time');
		unset($req);
	}

	if ($do_formatting) {
		$a=$field['label_format_'.$action];
		$x=$label;
		$label = eval("return $a;");
		$label .= $req;
	}

	if ($action == 'view' && in_array($type,array('lookup','lookup_multi'))) {

		$l_table = $field['lookup']['table'];
		$label = link_engine(array('object'=>$l_table,'action'=>'list'),alt($label,'Show lookup list (Table: '.$l_table.')'),'',' class="fancyLink" target="_blank"');

	}

      return $label;
}

function view_generic($rec,$def,$action,$control='',$control_array_variable='control')
{
      global $colors;
      $control=unserialize_control($control);
      if (isset($control['list']['fields'])) {
	    $list_links=right(bold(view_generic_links($control,$def,$control_array_variable)));
      }
      $fields=$def['fields'];
      $out = tablestart('','class="engineForm"');
      $out .= ($list_links) 
	    ? row(cell($list_links,'colspan="2"'),'class="listHeader"')
	    : '';
      foreach ($rec as $key=>$value) {

	    $disp = $fields[$key]["display_{$action}"];
	    if ($disp=='multi_disp') { // THIS STUFF ALL NEEDS TO GO SOON!!
		  $sub_value = $value[$fields[$key]['multi_field']];
		  //$multi_out .= view_generic_row($key,$sub_value,$def,$action); //SAVE FOR THE END
		  //this stuff is so far removed from engine now, it is becoming a pain to keep it in sync w/ functionality
		  //thus I am de-genericizing it:
		  $multi_out .= rowrlcell($fields[$key]['label_add'],$sub_value);
	    } else {
		  // EVALUATE $value
		  $x=$value;
		  $value = $fields[$key] 
			  ? eval('return '. $fields[$key]['value_'.$action].';')
			  : $value;
		  if ($disp=='hide') {
			// nothing...want to capture special fields that don't have $disp
			// set, so this test goes above the one below...!$disp
		  } elseif ($disp=='regular' || $disp=='display' ) {
			  if ($key=='sys_log') {
				  //special treatment for syslog for now
				  if (has_perm('system_log_field')) {

					  if ($action == 'edit') {

						  $old_value = div(smaller(value_generic($control['rec_last'][$key],$def,$key,$action)),'','class="append_only"');

					  }

					  $button = be_null($value)
						  ? smaller(gray('+/-'))
						  : Java_Engine::toggle_id_display(smaller('+/-'),'hiddenSystemLog'.$i,'block');
					  $system .= smaller(label_generic($key,$def,$action).' ('.$button.'):')
						  . div($old_value . smaller(value_generic($value,$def,$key,$action)),'hiddenSystemLog',' style="display: none;"');
				  }
			  } elseif ($fields[$key]['system_field']) {
				  $system .= oline(smaller(label_generic($key,$def,$action).': '.value_generic($value,$def,$key,$action)));
			  } else {
				  $out .= call_user_func($def['fn']['view_row'],$key,$value,$def,$action,$rec);
			  }
		  } else {
				  $out .= call_user_func($def['fn']['view_row'],$key,$value,$def,$action,$rec);
		  }
	    }
      }
      
      // need to handle calculated fields that aren't in the table's structure
      if ($fields) {
	    unset($x);
	    foreach($fields as $key => $field) {
		  // CAPTURE VIRTUAL FIELDS
		  if (!array_key_exists($key,$rec)) {
			$disp = $fields[$key]['display_'.$action];
			if ($disp=='hide') {
			      //nothing
			} else {
			      $value =  eval('return '.$fields[$key]['value_'.$action].';');
				$out .= call_user_func($def['fn']['view_row'],$key,$value,$def,$action,$rec);
			}
		  }
	    }
      }
      
      $out .= row(cell($system,'class="systemField" colspan="2"'));
      $out .= tableend()
	    . ($multi_out       // TACK ON MULTI RECORDS
	       ? ( oline() . bigger(bold(oline(orr($def['multi']['sub_title'],'')))) . $def['multi']['sub_sub_title']
		   . tablestart('','class="engineForm"')
		   . $multi_out
		   . tableend()
		   )
	       : '');
      return $out;
}

function view_generic_row($key,$value,$def,$action,$rec)
{
	global $colors;
      $value=value_generic($value,$def,$key,$action);
      $label=label_generic($key,$def,$action);

	//ideally, comment handling would be in the label_generic function, but for now, not breaking things is good
	if (($tmp = $def['fields'][$key]['comment']) && $def['fields'][$key]['comment_show_'.$action]) {
		$comment = div(webify($tmp),'',' class="generalComment"');
	}

	$l_opts = 'class="engineLabel"';
	$v_opts = 'class="engineValue"';

	$pr = $def['fields'][$key];

	switch ($pr['cell_align_label']) {
	case 'center':
		$label_cell = centercell($label.$comment,$l_opts);
		break;
	case 'left':
		$label_cell = leftcell($label.$comment,$l_opts);
		break;
	case 'right':
		$label_cell = rightcell($label.$comment,$l_opts);
		break;
	default:
		$label_cell = cell($label.$comment,$l_opts);
	}

	switch ($pr['cell_align_value']) {
	case 'center':
		$value_cell = centercell($value,$v_opts);
		break;
	case 'right':
		$value_cell = rightcell($value,$v_opts);
		break;
	case 'left':
		$value_cell = leftcell($value,$v_opts);
		break;
	default:
		$value_cell = cell($value,$v_opts);
	}

	return row($label_cell.$value_cell);

}

function view_generic_links($control,$def,$control_array_variable='control')
{
      $control=unserialize_control($control);

      //MIMIC THE LIST QUERY
      $object=$control['object'];
      $table=$def['table'];
      $position=$control['list']['position'];
      $filter=$control['list']['filter'];
      $order=$control['list']['order'];
      if (!$order) {
	    $order=array_flip($fields);
	    foreach($order as $field=>$value)
	    {
		  $order[$field]=false;
	    }
      }
      $result=list_query($def,$filter,$order,$control);
      $total=sql_num_rows($result);

      $rec_no=$position+1;
      $place = "Record $rec_no of $total ";

      if ( $total > $position+1 ) {
	    $next_rec=sql_fetch_assoc($result,$position+1);
	    $next_control=$control;
	    $next_control['list']['position']=$position+1;
	    unset($next_control['page']);
	    $next_link=list_view_link($next_rec,$next_control,$def,'','Next');
      }
      if ( $position-1 >= 0 ) {
	    $prev_rec=sql_fetch_assoc($result,$position-1);
	    $prev_control=$control;
	    $prev_control['list']['position']=$position-1;
	    unset($prev_control['page']);
	    $prev_link=list_view_link($prev_rec,$prev_control,$def,'','Previous');
      }

      // MIMIC THE LIST CONTROL ARRAY
      $list_control=$control;
      $max=$list_control['list']['max'];
      $list_control['list']['position']=floor($position/$max)*$max;
      $list_control['action']='list';
      $list_link = link_engine($list_control,'List',$control_array_variable);

      return $place . $prev_link.' '.$next_link.' '.$list_link;
}

function form_field_generic($key,$value,&$def,$control,&$Java_Engine,$formvar='rec')
{
	$action = $control['action'];
      $pr     = $def['fields'][$key];
      $label  = $pr['label_'.$action];
      $type   = $pr['data_type'];

	//JavaScript handling
	if (is_object($Java_Engine)) {
		$element_options=$Java_Engine->form_element_java($key);
	}

	if ($type=='currency') {
		$type='float';
	} elseif ($type=='phone') {
		$type='varchar';
	}
      $len = $pr['length'];
	switch ($type) {
	case 'staff':
	    $subset = $pr['staff_subset'];
	    $active_only = $action=='add' && !$pr['staff_inactive_add'] ? 'HUMAN' : false;
	    $field = pick_staff_to($formvar.'['.$key.']',$active_only,$value,$subset,$element_options);
	    break;
	case 'staff_list':
		$field = make_staff_list_form($value,$key,$def,$control,$formvar);
		break;
      case CG_MAIN_OBJECT_DB:
		$allowed=$pr[$action.'_main_objects'] || be_null($value); //edit & add
		$field = $value 
			? (client_link($value) . hiddenvar($formvar.'['.$key.']',$value))
			: '';
		if ($allowed) {
			$hide = !be_null($value);
			$hide_button = Java_Engine::hide_show_button($key.'ClientSelector',$hide);
			$little_box = Java_Engine::hide_show_content(client_selector(false),$key.'ClientSelector',$hide);
			$wipeout = be_null($value) ? '' : formvar_wipeout($formvar.'['.$key.']');
			$field .= $hide_button . $little_box . $wipeout
				. hiddenvar('engineQuickSearchField',$key);
		}
		break;
      case 'timestamp':
	case 'timestamp_past':
	case 'timestamp_future':
		switch ($pr['timestamp_format']) {
		case 'drop_list' :
			$time_field = Calendar::generate_time_list($formvar.'['.$key.'_time_]',timeof($value,'SQL'));
			break;
		default :
			global $form_name;
			$onclick = 'tmp=new Date(); '
				. 'document.'.$form_name.'.elements[&quot;'.$formvar.'['.$key.'_time_]&quot;].value=tmp.toLocaleTimeString(); '
				. 'return false;';
			$now_time = hlink('#','now','',' onclick="'.$onclick.'"') ;
			$time_field = formvartext($formvar.'['.$key.'_time_]',timeof($value),$element_options) 
				. smaller(' (hh:mm am/pm)' . $now_time);
		}
		
		$field = hiddenvar($formvar.'['.$key.']',$value)
			. oline(formdate($formvar.'['.$key.'_date_]',dateof(trim($value)),'Calendar',$element_options)) 
			. $time_field;
		$label = oline($label . ' date')
			. right('time');
		break;
      case 'lookup':
		if ($Java_Engine->FLAG[$key]['blank_selects']) {
			$field = selectto($formvar.'['.$key.']').selectend();
			//the lookup fields will be populated dynamically
		} else {
			$query = build_lookup_query($pr,$action);
			switch ($pr['lookup_format']) {
			case 'radio_v':
			case 'radio':
				$tmp_spacer = $pr['lookup_format'] == 'radio_v' ? oline() : '';
				$tmp_field = do_radio_sql($query,$formvar.'['.$key.']',$addnull=true,$value,$tmp_spacer);
				break;
			case 'droplist':
			case 'select':
			default:
				// $select=do_pick_sql(make_desc_query($query,$filt),$value,($pr["null_ok"] || $def["null_ok"]));
				// null option should always be present, even if null is not ok
				// that way, users are forced to choose a value rather than defaulting to first on list
				$select=do_pick_sql($query,$value,true);
				$tmp_field = $select
					? selectto($formvar.'['.$key.']',$element_options) . $select . selectend()
					: false;
			}
			$field = orr($tmp_field,formvartext($formvar.'['.$key.']',$value, $element_options));
		}
		break;
	case 'lookup_multi':
		$query = build_lookup_query($pr,$action);
		switch ($pr['lookup_format']) {
		case 'checkbox_v':
		case 'checkbox':
			if ($pr['comment'] == '(ctrl-click to select multiple values)') {
				$def['fields'][$key]['comment'] = '(select all that apply)';
			}
			$tmp_spacer = $pr['lookup_format'] == 'checkbox_v' ? oline() : '';
			$tmp_field = do_checkbox_sql($query,$formvar.'['.$key.']',$value,$tmp_spacer);
			break;
		case 'droplist':
		case 'select':
		default:
			$select = do_pick_sql($query,$value,true);
			$tmp_field = $select
				? selectto_multiple($formvar.'['.$key.'][]','',$element_options) . $select . selectend()
				: false;
		}
		$field = $tmp_field ? $tmp_field : formvartext($formvar.'['.$key.']',$value, $element_options);
		break;
	case 'time':
		if ($pr['time_drop_list']) {
			$field = Calendar::generate_time_list($formvar.'['.$key.']',$value);
			break;
		}
	case 'float':
		$len++;
      case 'varchar':
	case 'character':
	    $size = $len < 70 ? $len : '70'; // 92 matches default textarea width
	    $options = ($len ? "maxlength=\"$len\" size=\"$size\"" : '').$element_options;
	    $field = form_field($type,$formvar.'['.$key.']',$value,$options);        
	    break;
	case 'text':

		$options = ($len ? "maxlength=\"$len\"" : '').$element_options;
		$textarea_x = orr($pr['textarea_width'],60);
		$textarea_y = orr($pr['textarea_height'],10);

		if ($pr['append_only']) {

			$existing = div(value_generic($control['rec_last'][$key],$def,$key,$action),'','class="append_only"');

		}

		$field = $existing . formtextarea($formvar.'['.$key.']',$value,$options,$textarea_x,$textarea_y);
		break;
	case 'boolean':
		switch ($pr['boolean_form_type']){
		case 'checkbox':
			$field = form_field('boolcheck',$formvar.'['.$key.']',sql_true($value),$element_options);
			break;
		case 'allow_null':
			$wipeout = formradio_wipeout($formvar.'['.$key.']');
		default:
			$field = form_field($type,$formvar.'['.$key.']',$value,$element_options).$wipeout;
		}
		break;
	case 'array':
		if (!be_null($value) and is_array($value)) {
			$field = array();
			foreach ($value as $t_v) {
				$field[] = formvartext($formvar.'['.$key.'][]',$t_v, $element_options);
			}
			$field = implode(oline(),$field);
		} else {
			$field = formvartext($formvar.'['.$key.'][]',$t_v, $element_options);
		}
		$f_id = $formvar . $key . 'arrayGroup';
		$field = div($field,$f_id);
		$field .= js_add_content_link(oline().formvartext($formvar.'['.$key.'][]','', $element_options),
							$f_id,smaller('Add more...',2),' class="fancyLink"');
		break;
	default:
	    $field = form_field($type,$formvar.'['.$key.']',$value,$element_options);
	}
	return $field;
}

function form_generic_row($key,$value,&$def,$control,&$Java_Engine,$rec,$formvar='rec')
{

	$field = form_field_generic($key,$value,&$def,$control,&$Java_Engine,$formvar);

      //FORMAT LABEL
	$action = $control['action'];
      $pr=$def['fields'][$key];
      $not_valid_flag=$pr['not_valid_flag'];
      $type=$pr['data_type'];
      $label=label_generic($key,$def,$action);
      $label = $not_valid_flag ? red($label) : $label; //display in red invalid fields

	$label .= ($pr['comment'] && $pr['comment_show_'.$action])
		? div(webify($pr['comment']),'',' class="generalComment"')
		: ''; // comments look better on next line

	//append only comment appended
	if ($pr['append_only'] && $action == 'edit') {

		$label .= oline() . smaller('Append to this field');

	}

	$l_opts = 'class="engineLabel"';

	switch ($pr['cell_align_label']) {
	case 'center':
		$label_cell = centercell($label,$l_opts);
		break;
	case 'left':
		$label_cell = leftcell($label,$l_opts);
		break;
	case 'right':
		$label_cell = rightcell($label,$l_opts);
		break;
	default:
		$label_cell = cell($label,$l_opts);
	}

	$v_opts = 'class="engineValue"';

	switch ($pr['cell_align_value']) {
	case 'center':
		$value_cell = centercell($field,$v_opts);
		break;
	case 'right':
		$value_cell = rightcell($field,$v_opts);
		break;
	case 'left':
		$value_cell = leftcell($field,$v_opts);
		break;
	default:
		$value_cell = cell($field,$v_opts);
	}
	$out = row($label_cell.$value_cell);
      return $out;
}

function form_generic($rec,$def,$control)
{
	$Java_Engine = new Java_Engine($def,$rec);
	$action      = $control['action'];
      $fields      = $def['fields'];

      $out = tablestart('','class="engineForm"');
      foreach ($rec as $key=>$value) {

		if ($fields[$key]['never_to_form']) { continue; }
		$disp = $fields[$key]["display_$action"];

		if ($disp=='edit') { unset($disp); }

		if ($key=='sys_log' && has_perm('system_log_field','W')) {

			unset($disp);

		}

		if ($disp=='hide') {
			$out .= hiddenvar("rec[$key]",$value);	
		} elseif ($disp=='display') {
			$out .= hiddenvar("rec[$key]",$value); //moving hidden variable above $value change - JH 2/7/05
			// EVALUATE $value
			$x=$value;
			$value = eval('return '. $fields[$key]['value_'.$action].';');
			$out .= call_user_func($def['fn']['view_row'],$key,$value,$def,$action,$rec);
		} elseif ($disp=='multi_disp') {
			$multi_out .= call_user_func($def['multi']['form_row_fn'],$key,$value,$def);

		} else {
			$out .= call_user_func($def['fn']['form_row'],$key,$value,$def,$control,$Java_Engine,$rec);
		}
      }
      $out .= tableend() .
		( $multi_out
		  ? (oline() . bigger(bold(oline(orr($def['multi']['sub_title'],'')))) . $def['multi']['sub_sub_title']
		     . tablestart('','class="engineForm"')
		     . $multi_out
		     . tableend())
		  : '');
	$GLOBALS['CG_HEAD_TAG'].=$Java_Engine->get_javascript();
      return $out;
}

function get_generic($filter,$order='',$limit='',$def,$table_post=false,$group='')
{
	if ($group) {
		$group_fields = $group['fields'];
		$order=implode(',',orr($group['order'],$group_fields)); // if no explicit order, use group fields
		$values = orr($group['values'],array('count(*)'));
		$sql = "SELECT " . implode(',',$group_fields) . "," . implode(',',$values) . " FROM " 
				. orr($table_post,is_array($def) ? $def['table'] : $def);
	} elseif ($table_post) {
		$sql = "SELECT * FROM ".$def['table_post'];
	} elseif (is_array($def)) {
		$sql = $def['sel_sql'];
	} else {
		$sql = "SELECT * FROM $def";
	}
      // $def should be an def array, but you can stuff a table name in here too.
	  return desc_query($sql,$filter,$order,$limit,'',$group);
}

function get_active_generic($filter,$rec,$def,$order='')
{
	$fields = array_keys($def['fields']);
	$object = $def['object'];

	//check for start/end dates
	$start = orr($def['active_date_field'],$object.'_date');
	$end   = orr($def['active_date_end_field'],$object.'_date_end');
	if ($new_filter = $def['active_record_filter']) {
		//nothing
	} elseif (in_array($start,$fields) and in_array($end,$fields)) {
		$new_filter = array();

		$start_date_compare = ($e_comp = orr($rec[$end],$rec[$start]))   ? enquote1(sql_escape_string($e_comp))   : 'CURRENT_DATE';
		$end_date_compare   = $rec[$start] ? enquote1(sql_escape_string($rec[$start])) : 'CURRENT_DATE';

		$new_filter['FIELD<=:'.$start] = $start_date_compare;
		$new_filter[]                  = array('FIELD>=:'.$end=>$end_date_compare,
								   'NULL:'.$end=>'');

		$order = orr($order,$start.' DESC');

	} else {
		$order = orr($order,$def['id_field'].' DESC'); //default to pull most recent record
	}
	$filter = array_merge($new_filter,$filter);
	return call_user_func($def['fn']['get'],$filter,$order,'1',$def);
}

function valid_generic($rec,&$def,&$mesg,$action,$rec_last=array())
{
      global $engine_ignore_fields;
      $VALID=true;
      $fields=$def['fields'];
	//manual record-wide validity check
	if ($val = $def['valid_record']) {
		// field can have multiple tests and multiple messages to display
		foreach ($val as $test => $msg)  
		{
			if (!eval( "return $test;" ))
			{
			      $mesg .= empty($msg) 
					? oline("Record has invalid data")
					: oline($msg);
			      $VALID=false;
			}
		}
	}
	if ($def['multi_records']) { //horrid 'generic' multi-record hack
		call_user_func($def['multi']['valid_fn'],$action,$rec,&$def,&$mesg,&$VALID);  
	}
	foreach ($rec as $key=>$value) {
	    $type=$fields[$key]['data_type'];
	    $label=$fields[$key]['label_'.$action];
	    $pr=$fields[$key];
	    $valid=true;
	    if ($type=='multi_rec') { //do nothing, but capture
		    continue;
	    } elseif ( (be_null(trim($value))) && (!$fields[$key]['null_ok']) ) {	
		    $mesg .= oline("Field $label cannot be blank.");
		    $valid=false;
	    } elseif ( ($type=='ssn') && (!ssn_of($value))  && (!be_null($value)) ) {
		  $mesg .= oline("Field $label has an invalid SSN.");
		  $valid=false;
	    } elseif ( ($type=='time' || substr($type,0,5)=='time_') && (!timeof($value)) && (!be_null($value)) ) {
		  $mesg .= oline("Field $label has an invalid time.");
		  $valid=false;
	    } elseif ( ($type=='time_past') && (timeof($value,'SQL') > timeof('now','SQL'))  && (!be_null($value)) ) {
		  $mesg .= oline("Field $label cannot be in the future.");
		  $valid=false;
	    } elseif ( ($type=='time_future') && (timeof($value,'SQL') < timeof('now','SQL'))  && (!be_null($value)) ) {
		  $mesg .= oline("Field $label cannot be in the past.");
		  $valid=false;
	    } elseif ((substr($type,0,4)=='date') && (!dateof($value))  && (!be_null($value))) {
		  $mesg .= oline("Field $label has an invalid date.");
		  $valid=false;
	    } elseif ( ($type=='date_past') && (dateof($value,'SQL') > dateof('now','SQL'))  && (!be_null($value)) ) {
		  $mesg .= oline("Field $label cannot be in the future.");
		  $valid=false;
	    } elseif ( ($type=='date_future') && (dateof($value,'SQL') < dateof('now','SQL'))  && (!be_null($value)) ) {
		  $mesg .= oline("Field $label cannot be in the past.");
		  $valid=false;
	    } elseif ( (substr($type,0,9)=='timestamp') && (!is_timestamp($value,'SQL')) 
			   && !($pr['timestamp_allow_date'] && dateof($value)) //if defined, date-only values are valid
			   && (!be_null($value)) ) {
		    $mesg .= oline("Field $label has an invalid timestamp.");
		    $valid=false;
	    } elseif ( ($type=='timestamp_past') && (datetimeof(datetotimestamp($value),'SQL') > datetimeof('now','SQL'))  && (!be_null($value)) ) {
		  $mesg .= oline("Field $label cannot be in the future.");
		  $valid=false;
	    } elseif ( ($type=='timestamp_future') && (datetimeof(datetotimestamp($value),'SQL') < datetimeof('now','SQL'))  && (!be_null($value)) ) {
		  $mesg .= oline("Field $label cannot be in the past.");
		  $valid=false;
	    }	elseif ( ($type=='currency') && (!currency_of($value)) && (!be_null($value)) ) {
		  $mesg .= oline("Field $label must be a dollar amount.");
		  $valid=false;
	    } elseif ( ($type=='phone') && (!phone_of($value)) && (!be_null($value)) ) {
		    $mesg .= oline("Field $label contains an invalid phone number.")
			    . oline(indent(smaller('A valid phone number is of the form: ('.CG_DEFAULT_PHONE_AREA_CODE.') 123-4567')));
		    $valid=false;
	    } elseif ( ($type=='float') && (!is_numeric($value)) && (!be_null($value)) ) {
		  $mesg .= oline("Field $label must be a number.");
		  $valid=false;
	    } elseif ( ($type=='integer')
			   and (!is_integer_all($value))
			   and (!be_null($value))) {
		  $mesg .= ($value > CG_POSTGRESQL_MAX_INT)
			  ? oline("Field $label is out of range for integers (max: ".CG_POSTGRESQL_MAX_INT.")")
			  : oline("Field $label must be a whole number.");
		  $valid=false;
	    } elseif ( ($type=='interval')
			   and !is_interval($value) and !be_null($value)) {
		    $mesg .= oline("Field $label must be of the form x [years,months,weeks,days,hours,minutes, or seconds]");
		    $valid=false;
	    } elseif ( $type == 'array'
			   and count($value) > $pr['array_max_elements'] ) {

		    $mesg .= oline("Field $label has ".count($value)." elements while the maximum allowable values is ".$pr['array_max_elements'].".");
		    $valid = false;

	    } elseif ( $type == 'lookup_multi'
			   and !sql_lookup_value_exists($value,$pr['lookup']['table'],$pr['lookup']['value_field']) and !be_null($value)) {
		    $mesg .= oline("Field $lable has an unknown value");
		    $valid = false;
	    } 

	    //maximum length
	    if (!is_array($type) and strlen($value) > CG_MAXIMUM_STRING_LENGTH) {
		    $valid = false;
		    $mesg .= oline('Field '.$label.' has exceeded the maximum length ('.CG_MAXIMUM_STRING_LENGTH
					 .'). Contact your system administrator if this is valid data.');
	    }

	    //manual validity definitions
	    if ($val = $fields[$key]['valid']) {
		    $x=$value;  
		    $ox = $rec_last[$key];
		    // field can have multiple tests and multiple messages to display
		    foreach ($val as $test => $msg) {
			    if (!eval( "return $test;" )) {
				    $mesg .= empty($msg) 
					    ? oline("Field $label has an invalid value.")
					    : oline(str_replace('{$Y}',$label,$msg));
				    $valid=false;
			    }
		    }
	    }
	    $def['fields'][$key]['not_valid_flag']= $valid ? false : true;
	    $VALID = $valid ? $VALID : $valid;
      }
      return $VALID;
}

function confirm_generic($rec,$def,&$mesg,$action,$rec_last)
{
      $confirmed = true;
      $fields=$def['fields'];
	if ($conf = $def['confirm_record']) {
		foreach ($conf as $test => $msg) {
			if (!eval( "return $test;" )) {
			      $mesg .= empty($msg) 
					? oline("Record requires confirmation?")
					: oline($msg);
			      $confirmed = false;
			}
		}
	}
      foreach ($rec as $key=>$value) {
		if($confirm = $fields[$key]['confirm']) {  // just like valid
			$label = $fields[$key]['label_'.$action];
			$x = $value;
			$ox = $rec_last[$key];
			foreach($confirm as $test => $msg) {
				if(!eval("return $test;")) {
					$confirmed = false;
					$mesg .= empty($msg) 
						? oline("Please review field $label.")
						: oline(str_replace('{$Y}',$label,$msg));                
				}
			}
		}
      }
      return $confirmed;
}

function rec_changed_generic($rec,$rec_last,$def)
{
	// first, check for append only fields
	foreach ($rec as $key => $value) {

		if ($def['fields'][$key]['append_only'] && !be_null($rec[$key])) {

			return true;

		}

	}

	$rec = unset_system_fields($rec);
	$rec_last = unset_system_fields($rec_last);
      return !($rec==$rec_last);
}

function unset_system_fields($rec)
{

      unset($rec['changed_at'],$rec['changed_by'],$rec['added_at'],$rec['sys_log']);

	return $rec;

}

function unset_system_fields_all($rec)
{
	global $engine;

	foreach (array_keys($engine['system_fields']) as $key) {

		unset($rec[$key]);

	}

	return $rec;
}

function rec_collision_generic($rec,$rec_last,$def,$action,&$mesg)
{

	/*
	 * $rec --> the current 'live' edited version of the record
	 * $rec_last --> the initial record prior to edits (stored as session variable)
	 * $old_rec --> if no collision, this should be the same as $rec_last
	 */

	if ($action=='add') { return false; } //can't have collisions on adds

	$rec = unset_system_fields($rec);
	$rec_last = unset_system_fields($rec_last);

	$rec = php_to_sql_generic($rec,$def);
	$rec_last = php_to_sql_generic($rec_last,$def);

	$filter = array($def['id_field']=>$rec[$def['id_field']]);

	/*
	 * Using get_generic() since custom get functions tend to modify they record,
	 * which doesn't work for determining a record collision
	 */
	$old_rec = sql_fetch_assoc(get_generic($filter,'','',$def,$def['use_table_post_'.$action]));
	$old_rec = unset_system_fields($old_rec);

	if ($rec_last == $old_rec) {

		/*
		 * No collision
		 */

		return false;

	}

	/*
	 * Record collision
	 */

	$changed_by = $old_rec['changed_by'] ? ' ('.staff_link($old_rec['changed_by']).') ' : '';

	if ($rec == $old_rec) { //user's changes took - user probably hit reload

		$mesg .= oline('Your changes have already been submitted. Re-posting of changes blocked.');

	} else {

		$rev_hist = Revision_History::has_history($def['table_post']) 
			? 'See '.Revision_History::link_history($def['object'],$rec[$def['id_field']],'View Revision History').' for details.'
			: '';

		$mesg .= oline(bigger('WARNING: This record has been changed by another user '.$changed_by.'since your edit started. EDIT FAILED.'))
			. $rev_hist;

	}
	
	return $old_rec;
}

function post_generic($rec,$def,&$mesg,$filter='',$control=array())
{
      // if a filter is passed, it means we want to update an existing record
      // no filter means add a new record
      $fields=$def['fields'];
      $table=$def['table_post'];
      $id_field=$def['id_field'];
      $action = $filter ? 'edit' : 'add' ;

	//postgres array handling
	$rec = php_to_sql_generic($rec,$def);

      foreach ($rec as $key=>$value) {
	    //detach multiple records into a separate array for easier manipulation
	    $type=$fields[$key]['data_type'];
	    if ($type=='multi_rec') {
		  $multi_records[$key]=$value; 
		  unset($rec[$key]);
		  continue;
	    }

	    if ($action=='add') {

		  $ADD_FILTER[$key]=$value;	    //contsruct an add filter to verify that this isn't a duplicate

		  if ($key=='added_at' || $key=='changed_at' || $key=='deleted_at' || $key=='deleted_by') {

			unset($ADD_FILTER[$key]);
			if (in_array($key,array('added_at','changed_at'))) {
			      $ADD_FILTER['>=:'.$key]=minusHour('',$def['duplicate_posting_window']);
			}
		  } elseif ($fields[$key]['view_field_only'] || $fields[$key]['virtual_field'] ) {
			unset($ADD_FILTER[$key]);
		  } elseif (be_null($value)) {
			$ADD_FILTER["FIELD:BE_NULL($key)"]='true';
			unset($ADD_FILTER[$key]);
		  }

		  if (($condition = $fields[$key]['add_query_modify_condition']) && is_array($condition)) { //for tables with value-changing triggers
			  foreach ($condition as $e_code => $new_val) {
				  $x = $value;
				  $change_val = eval('return '.$e_code.';');
				  if ($change_val && $new_val=='ENGINE_UNSET_FIELD') {
					  unset($ADD_FILTER["FIELD:BE_NULL($key)"]); //in case of null conditions
					  unset($ADD_FILTER[$key]);
				  } else {
					  $ADD_FILTER[$key] = $change_val ? $new_val : $value;
				  }
			  }
		  }
	    }
	    if (be_null($value) && !is_null($fields[$key]['null_post_value'])) {
		    $rec[$key] = $fields[$key]['null_post_value'];
	    }
  	    if ($key=='changed_at') {
		  unset($rec[$key]);
		  $rec['FIELD:'.$key]='CURRENT_TIMESTAMP';
	    }
	    if ($key=='changed_by') {
		    $rec[$key] = $GLOBALS['UID'];
	    }
	    if (!$fields[$key]['post_'.$action]) {
		  unset($rec[$key]);
	    }
	    if ($fields[$key]['append_only'] && !be_null($control['rec_last'][$key])) {

		    $rec[$key] = $control['rec_last'][$key] . "\n" . $rec[$key];

	    }

	    
      }

      // FOR ADD, CHECK TO SEE IF DUPLICATE
      if ($action=='add') {
	    $duplicate = desc_query("SELECT * FROM $table",$ADD_FILTER);
	    if (sql_num_rows($duplicate) > 0 ) {
		  //DUPLICATE RECORD EXISTS
		  $singular=$def['singular'];
		  $mesg .= oline(bigger('RECORD NOT POSTED')) 
			. oline("This record has already been entered in the $singular table.");
		  return sql_to_php_generic(sql_fetch_assoc($duplicate),$def);
	    }
      }
      
      $result = desc_query( $filter
				    ? sql_update($table,$rec,$filter,sql_supports_returning() ? '*' : false)
				    : sql_insert($table,$rec,sql_supports_returning() ? '*' : false)
				    );

      if (!$result) {

		  $mesg .= oline("Your attempt to {$action} a record failed.");
		  log_error("Error in {$action}ing, using post_generic(). Here was the record: " . dump_array($rec));
		  return false;

	} elseif (sql_supports_returning()) {

		// fetch back the returned record
		$tmp_new_rec = sql_fetch_assoc($result);

		// just to be safe, the record is re-queried from the table using the primary key
		if ($id = $tmp_new_rec[$def['id_field']]) {

			$NEW_REC = sql_to_php_generic(sql_fetch_assoc(get_generic(array($def['id_field'] => $id),'','',$def)),$def);

		} else {

			// if for whatever reason this isn't available, we go with the returned record.
			$NEW_REC = $tmp_new_rec;

		}

	} else {

		// if insert/update returning is not supported, must attempt at a filter to retrieve the record
      
		// we're having a problem w/ nulls, so as a temporary
		// hack we'll set null values to be NULL or '' for query
		// This is a temporary hack and needs fixing!!

		$test_rec=array();

		foreach ($rec as $key=>$value) {

			if (be_null($value)) {

				$test_rec["FIELD:BE_NULL($key)"]='true';

			} elseif(substr($key,0,6) == 'FIELD:') {

				continue;// skip fields like added_at, changed_at

			} else {

				$test_rec[$key]=$value;

			}

			if (($condition = $fields[$key][$action.'_query_modify_condition']) && is_array($condition)) {

				foreach ($condition as $e_code => $new_val) {

					$x = $value;
					$change_val = eval('return '.$e_code.';');

					if ($change_val && $new_val=='ENGINE_UNSET_FIELD') {

						unset($test_rec[$key]);
						unset($test_rec["FIELD:BE_NULL($key)"]); //in case of null conditions

					} else {

						$test_rec[$key] = $change_val ? $new_val : $value;

					}

				}

			}

		}
	
		$new_rec = get_generic($test_rec,'','',$def,$def['use_table_post_'.$action]);
		$NEW_REC = sql_to_php_generic(sql_fetch_assoc($new_rec),$def);

	}

      if (isset($multi_records)) {

	    if (!(call_user_func($def['multi']['post_fn'],$multi_records,$NEW_REC,$def,$mesg))) {

		  $mesg .= oline("You attempted to {$action} multiple child records failed.");

	    }

      }

      if (!$NEW_REC) {

	    $mesg .= oline("The database accepted your $action, but I was unable to find the record. The $action will be aborted.");
	    log_error("The database accepted your $action, but I was unable to find the record.");
	    return false;

      }

	$singular = $def['singular'];
      $mesg .= oline("Your {$singular} was successfully {$action}ed.");

      return $NEW_REC;
}

function delete_generic($filter,$def,$delete_comment='')
{
	if ($def['stamp_deletes']) {
		$del_rec=$filter;
		$del_rec['is_deleted']=sql_true();
		$del_rec['deleted_by']=$GLOBALS['UID'];
		$del_rec['FIELD:deleted_at']='CURRENT_TIMESTAMP';
		$del_rec['deleted_comment']=$delete_comment;
		$res = desc_query(sql_update(orr($def['table_post'],$def['table']),$del_rec,$filter));
	} else {
		$res = desc_query(sql_delete(orr($def['table_post'],$def['table']),$filter));
	}
	return $res;
}

function engine_delete_filter($rec,$def)
{
	/*
	 * Generates a filter for list to use after a record is deleted
	 */

	foreach ($rec as $key => $val) {

		$pr = $def['fields'][$key];
		$type = $pr['data_type'];

		if (in_array($type,array(CG_MAIN_OBJECT_DB,'staff'))
		    && !$pr['virtual_field'] && !$pr['view_field_only']) {
			return array($key => $val);
		}
	}
	return false;
}

function object_merge_generic($def,$control)
{
	//this is a place-holder function for custom object mergining -- see path_tracking

	return $def;
}

function process_generic(&$sess,&$form,$def)
{
      if (!isset($form)) {
	    return false;
      }
      //The following initial loop was added to maintain key consistency between $form
      //and $sess variables. This was inspired by the fact that html forms don't pass
      //variables that aren't checked, or true. This was done quickly out of necessity, and there is
      //certainly a better way to obtain the desired result.
      if (isset($sess)) {
		foreach ($sess as $sess_key=>$sess_value) {
			$type=$def['fields'][$sess_key]['data_type'];
			if ($type=='multi_rec') {
				foreach ($sess_value as $sub_key=>$sub_value) {
					if(!array_key_exists($sub_key,$form[$sess_key]))
					{
						$form[$sess_key][$sub_key]='';
					}
				}
			} else {
				if (!array_key_exists($sess_key,$form)) {
					$form[$sess_key]='';
				}
			}
		}
      }
      foreach ($form as $form_key=>$form_value) {
		$fields = $def['fields'][$form_key];
		$type = $def['fields'][$form_key]['data_type'];
		
		if (!$def['fields'][$form_key]['allow_smart_chars']) { //no smart quotes
			$form_value=smart_char_destroy($form_value);
		}

		//FORCE_CASE IF SPECIFIED IN CONFIG FILE
		if ($fcase = $def['fields'][$form_key]['force_case']) {
			$form_value = force_case($form_value,$fcase);
		}
		// CAPTURE STAFF FIELDS...-1 IS REALLY NULL
		if ( $type=='staff' && $form_value=='-1' ) {

			// -1 = no staff on form, set to null for data record
			$form_value = ($form_value=='-1') ? '' : $form_value;
			$sess[$form_key] = get_magic_quotes_gpc() ? stripslashes($form_value) : $form_value;

		} elseif ( substr($type,0,9)=='timestamp' ) {

			if (isset($form[$form_key.'_date_']) && isset($form[$form_key.'_time_'])) {
				$tmp_date=dateof($form[$form_key.'_date_'],'SQL');
				$tmp_time=timeof($form[$form_key.'_time_'],'SQL');
				$sess[$form_key] = trim($tmp_date.' '.$tmp_time);
				unset($form[$form_key.'_date_']);
				unset($form[$form_key.'_time_']);
			} else {
				$sess[$form_key] = $form_value;
			}
		} elseif ( $type=='ssn' ) {
			if (!($sess[$form_key]=ssn_of($form_value))) {
				$sess[$form_key] = $form_value; //assign invalid ssn anyway, for validity check
			}
		} elseif ( $type=='currency' ) {
			$sess[$form_key] = currency_of($form_value,'sql'); //format for db
		} elseif ( $type == 'float' && (!be_null($form_value) && $round = $def['fields'][$form_key]['length_decimal_places'])) {
			$sess[$form_key] = round($form_value,$round);
		} elseif ($type=='phone' ) {
			if (!($sess[$form_key]=phone_of($form_value))) {
				$sess[$form_key] = $form_value;
			}
		} elseif ( substr($type,0,4)=='date' ) {
			if (!($sess[$form_key]=dateof($form_value,'SQL'))) {
				$sess[$form_key] = $form_value; //assign invalid date anyway, for validity check to catch
			}
		} elseif ( ($type=='boolean') ) {  //added for checkbox booleans
			if (sql_true($form_value)) { $form_value=sql_true(); }
			if (sql_false($form_value)) { $form_value=sql_false(); }
			$sess[$form_key] = $form_value;
		} elseif ($type=='multi_rec') { //cycle through and assign sub-records
			foreach ($form_value as $sub_key=>$sub_value) {
				if ( ($fields['multi_type']=='boolean') 
				     && ($sub_key==$fields['multi_field'])
				     && (sql_true($sub_value)) ) {
					$sub_value=sql_true();
					$sess[$form_key][$sub_key]=$sub_value;
				} else {
					$sess[$form_key][$sub_key] = get_magic_quotes_gpc() ? stripslashes($sub_value) : $sub_value;
				}
			}
		} elseif (in_array($type,array('lookup_multi','array','staff_list'))) {
			if (in_array($fields['lookup_format'],array('checkbox','checkbox_v')) and !be_null($form_value)) {
				$form_value = $form[$form_key] = array_keys($form_value);
			}
			if ($type == 'staff_list') {
				$form_value = array_unique($form_value);
				$form_value = array_diff($form_value,array(-1)); //strip -1
			}
			$sess[$form_key] = dewebify_array(array_filter(orr($form_value,array())));
		} else {

			$sess[$form_key] = get_magic_quotes_gpc() ? stripslashes($form_value) : $form_value;

		}
      }
}

function title_generic($action,$rec,$def)
{
      $a=orr($def['title_' . $action],$def['title']);
      $object=$def['object'];
      $b=$def['title_format'];
      $x = $a
	    // first figure out title text
	    ? eval("return $a;")
	    : ucwords($action). 'ing ' . $def['singular'] 
	    . ($rec[$def['id_field']] ? " #{$rec[$def['id_field']]}": '');
	if (array_key_exists(CG_MAIN_OBJECT_DB.'_id',$rec) && !strstr($x,' for ')
	    && !stristr($x,'Adding a new '.CG_MAIN_OBJECT.' record.')) {
		$x .= ' for '.client_link($rec[CG_MAIN_OBJECT_DB.'_id']);
	}

      // then determine format
      return oline($b
			 ? eval("return $b;")
			 : bigger(bold($x)))
		. $required_note
		. (sql_true($rec['is_deleted'])
		   ? $GLOBALS['NL'] . italic(bold(red('(this record has been marked deleted)')))
		   : '');
}

function engine_process_quicksearch(&$step,&$rec,$control)
{
	$_SESSION['engineQuickSearchField']=orr($_REQUEST['engineQuickSearchField'],
									  $_SESSION['engineQuickSearchField']);
	if ($client = $_REQUEST['client_select']) {
		$field=$_SESSION['engineQuickSearchField'];
		$rec[$field]=$client;
		$step = 'continued';
		$_SESSION['engineQuickSearchField']=null;
	}

	global $CG_QUICK_SEARCH_CONTROL_ARRAY;     //this is a kludge to pass the control array in the old qs form code
	$CG_QUICK_SEARCH_CONTROL_ARRAY = $control; //once list is used for qs, this will be cleaner...

	return process_quick_search(true,false,true,true); //exit the script, don't auto-forward, use old form for now, and don't directly output
}

function add_another_set_rec_init($rec,$def,$rec_init,$session_identifier)
{
	$init=array();
	if ($_REQUEST['engineAddAndRemember']) {
		foreach ($rec as $key=>$val) {
			if ($def['fields'][$key]['add_another_remember']) {
			   $init[$key]=$val;
			}
		}
	} else {
	  $key=CG_MAIN_OBJECT_DB.'_id';
	  if (array_key_exists($key,$rec)) {
	     $init[$key]=$rec[$key];
	  }
	}
	$_SESSION['CONTROL'.$session_identifier]['rec_init'] = $init;
}

function update_engine_array($use_auth=true,$engine_array=null,$tables=null) {

	global $CG_ENGINE_TABLES,$engine,$off;

	$engine_array = orr($engine_array,CG_ENGINE_CONFIG_ARRAY);
	$tables = orr($tables,$CG_ENGINE_TABLES);
	$engine = null; //objects w/o config files were not configuring properly w/o this

	$auth = $use_auth ? has_perm('admin','RW') : true;
	if (!$auth) {
		outline(red('You don\'t have permission to update the engine array.'));
		exit;
	}

	outline(bold('Updating and storing in Engine Array: '.$engine_array));
	//GENERATE ENGINE ARRAY FROM CONFIG FILES
	require ENGINE_CONFIG_FILE_DIRECTORY.'/config_engine.php';

	foreach ($tables as $tmp)	{
		if (is_readable($off . ENGINE_CONFIG_FILE_DIRECTORY . '/'.CG_MAIN_OBJECT_DB.'/config_'.$tmp.'.php')) {

			include $off . ENGINE_CONFIG_FILE_DIRECTORY . '/'.CG_MAIN_OBJECT_DB.'/config_'.$tmp.'.php';

		} elseif (is_readable($off . ENGINE_CONFIG_FILE_DIRECTORY.'/config_'.$tmp.'.php')) {

			include $off . ENGINE_CONFIG_FILE_DIRECTORY . '/config_'.$tmp.'.php';

		} else {

			outline('No config file found for '.bold($tmp).'. Will configure '.$tmp.' using engine defaults.');

		}
	}
	
	foreach ($tables as $tmp)	{
		$engine[$tmp]=config_object($tmp);
		outline ('Configuring '.bold($tmp));
	}

	$serialized_engine=serialize($engine);
	
	outline(bold('UPDATING CONFIGURATION ARRAY IN DB'));
	$exists=desc_query('SELECT * FROM '.CG_ENGINE_CONFIG_TABLE,array('val_name'=>$engine_array));
	if (sql_num_rows($exists)<1) {
		$res=desc_query(sql_insert(CG_ENGINE_CONFIG_TABLE,array('val_name'=>$engine_array,
											  'FIELD:changed_at'=>'CURRENT_TIMESTAMP',
											  'value'=>$serialized_engine)));
	} else {
		$res=desc_query(sql_update(CG_ENGINE_CONFIG_TABLE,array('value'=>$serialized_engine,
											  'FIELD:changed_at'=>'CURRENT_TIMESTAMP'),
						   array('val_name'=>$engine_array)));
	}
	if (CG_SESSION_ENGINE_ARRAY) {
		$_SESSION['STATIC_ENGINE_ARRAY']=null; //array will be pulled on fresh on next page load.
	}

	return $res;
}

function config_any_table_perm($def)
{
	//modify a config array to mimic super user on given table
	global $engine;
	foreach (array_keys($engine['actions']) as $action) {
		$def['allow_'.$action] = true;
		$def['perm_'.$action] = 'any_table';
	}

	$def['add_link_always'] = orrn($def['add_link_always'],true);

	return $def;
}

function is_protected_generic($object,$id=null)
{ //determine whether a record is protected
	global $engine;
	$def = $engine[$object];
	if (isset($def['fields']['is_protected_id']) && !be_null($id)) {
		//this object type has records of protected nature
		//the record must now be fetched to determine status
		$filter = array($def['id_field']=>$id);
		$rec = sql_fetch_assoc(get_generic($filter,'','',$def));
		if (sql_true($rec['is_protected_id'])) {
			//record is protected
			return true;
		}
	}
	//record/object is not protected
	return false;
}

function add_staff_alert_form_generic($def,$rec,$control)
{
	if ($control['action'] == 'add') {
		$i = 0;
		foreach ($control['staff_alerts'] as $id) {
			$js_id = 'addStaffAlert'.$i;
			$remove_link = hlink($_SERVER['PHP_SELF'].'?staff_alert_remove[]='.$id,smaller('remove',2)
						   ,'',' onclick="javascript:document.getElementById(\''.$js_id.'\').innerHTML=\'\';'
 						   . ' document.getElementById(\'addStaffAlertVar'.$i.'\').name=\'staff_alert_remove[]\'; return false;"');
			$alerts .= hiddenvar('staff_alerts[]',$id,' id="addStaffAlertVar'.$i.'"') . span(oline($remove_link.' '.staff_link($id)),' id="'.$js_id.'"');
			$i++;
		}
		global $CG_HEAD_TAG;
		$CG_HEAD_TAG .= Java_Engine::get_js('     var currentStaffAlertID = \''.$i.'\';

     function addStaffAlert() {
          currentStaffAlertID ++;
          var cont = document.getElementById(\'addStaffAlertContainer\');
          var staffList = document.getElementById(\'addStaffAlertList\');
          var staffID = staffList.options[staffList.selectedIndex].value;

          var staffName = staffList.options[staffList.selectedIndex].innerHTML;
          var staffLink = \'<a href="' . CG_STAFF_PAGE . '?id=\'+staffID+\'"  class="staffLink">\'+staffName+\'</a>\';

          var removeLink = \'<a href="javascript:void(0);" onclick="javascript:document.getElementById(\\\'addStaffAlert\'+currentStaffAlertID+\'\\\').innerHTML=\\\'\\\'; document.getElementById(\\\'addStaffAlertVar\'+currentStaffAlertID+\'\\\').name=\\\'staff_alert_remove[]\\\'"><font size="-2">remove</font></a> \';
          var staffNameFormatted = \'<span id="addStaffAlert\'+currentStaffAlertID+\'">\'+removeLink+\' \'+staffLink+\'<br /></span>\';


          cont.innerHTML+=\'<input type="hidden" name="staff_alerts[]" value="\'+staffID+\'"\'+ \' id="addStaffAlertVar\'+currentStaffAlertID + \'"/>\'+staffNameFormatted+\'\'; 

          return true;
     }
');

		$out = div($alerts,'addStaffAlertContainer')
			. pick_staff_to('staff_alerts[]', $active_only="Yes", $default=-1 ,$subset=false,$options=' id="addStaffAlertList"')
			. button('Add','','','','javascript:addStaffAlert(); return false;');
		return div(oline(bold('Staff Alerts')).$out,'','class="staff" style="border: solid 1px black; padding: 5px; margin: 10px; width: 22em;"');
	} elseif ($control['action'] != 'view') { return false; }

	$req = ' '.red('*');
	$alert = $_REQUEST['engineStaffAlert'];
	$hide = !be_null($alert) ? 'block' : 'none';
	$close = 'document.getElementById(\'engineStaffAlertForm\').style.display="none";blur();return false;';

	global $engine, $CG_AUTH;
	$adef = $engine['alert'];
// 	$adef['fields']['alert_subject']['null_ok'] = 
// 		$adef['fields']['alert_text']['null_ok'] = false;

	$alert['staff_id'] = $alert['staff_id']=='-1' ? '' : $alert['staff_id'];
	
	if (!be_null(array_filter($alert)) and !call_user_func($adef['fn']['valid'],$alert,&$adef,&$mesg,'add',$rec_last=array())) {
		$error = red($mesg);
	}
	//this doesn't work yet
// 	$multi = oline() . js_link(smaller('(multiple)'),'document.getElementById(\'engineStaffAlertFormStaff\').multiple="multiple";document.getElementById(\'engineStaffAlertFormStaff\').size=8;document.getElementById(\'engineStaffAlertFormStaff\').name=document.getElementById(\'engineStaffAlertFormStaff\').name+"[]"');
	$form = html_heading_3('Add a Staff Alert')
		. div(/*span(js_link('X',$close,'title="Close"')) 
			 . */ $error
				. formto('','',$CG_AUTH->get_onsubmit(''))
				. hiddenvar('engineStaffAlert[ref_id]',$rec[$def['id_field']])
				. hiddenvar('engineStaffAlert[ref_table]',$def['object'])
				. hiddenvar('control[action]',$control['action'])
				. hiddenvar('control[id]',$control['id'])
				. hiddenvar('control[object]',$control['object'])
				. table(rowrlcell('Staff' . $req . $multi,pick_staff_to('engineStaffAlert[staff_id]','HUMAN',$alert['staff_id'],false,' id="engineStaffAlertFormStaff"'))
// 			  . rowrlcell('Subject' . $req,formvartext('engineStaffAlert[alert_subject]',$alert['alert_subject']))
// 			  . rowrlcell('Text' . $req,formtextarea('engineStaffAlert[alert_text]',$alert['alert_text']))
					  . rowrlcell('Your Password' . $req,$CG_AUTH->get_password_field($auto_focus=false))
					  ,'','class=""')
			. button('Add Alert','','','','','class="engineButton"')
			. js_link('Cancel',$close,'class="linkButton"')
			. formend());
	return js_link('Add staff alert',
			   'document.getElementById(\'engineStaffAlertForm\').style.display="block";blur();return false;')
		. div($form,'engineStaffAlertForm',' style="display: '.$hide.';"');
}

function process_staff_alert_generic($def,$rec,&$control)
{
	if ($control['action'] == 'add') {
		$alerts = array_unique(array_merge(orr($_REQUEST['staff_alerts'],array()),orr($control['staff_alerts'],array())));
		$remove = orr($_REQUEST['staff_alert_remove'],array());
		foreach ($alerts as $key => $staff) {
			if ($staff < 1) {
				unset($alerts[$key]);
			} elseif (in_array($staff,$remove)) {
				unset($alerts[$key]);
			}
		}
		if ($control['step'] == 'new') {
			$alerts = array();
		}

		$control['staff_alerts'] = $alerts;
		return false;
	}

	if (!$alert = $_REQUEST['engineStaffAlert']) {
		return false;
	}

	global $UID, $CG_AUTH;
	$adef = get_def('alert');

	//check password
	if (!$CG_AUTH->reconfirm_password()) {
		return 'Incorrect password for '.staff_link($UID);
	}

	// require text and subject
//  	$adef['fields']['alert_subject']['null_ok'] = 
//  		$adef['fields']['alert_text']['null_ok'] = false;

	$alert['staff_id'] = $alert['staff_id']=='-1' ? '' : $alert['staff_id'];
	$alert['changed_by'] = $alert['added_by'] = $UID;
	$alert['sys_log'] = 'Alert added through CHASERS interface.';

	$alert['alert_subject'] = substr($def['singular'] .' (id '.$control['id'].') has been flagged for your attention',0,90);
	$alert['alert_text'] = staff_name($UID) . ' has flagged '.aan($def['singular']).' '.$def['singular'].' (id '.$control['id'] . ') for your attention';

	if ($alert['ref_id'] != $rec[$def['id_field']] 
	    or $alert['ref_table'] != $def['object']) {
		return 'Error: ID or OBJECT mismatch. Couldn\'t add alert';
	}

	if (!call_user_func($adef['fn']['valid'],$alert,&$adef,&$mesg,'add',$rec_last=array())) {
		return 'Couldn\'t add alert. Validity Problems: ' . oline() . $mesg;
	}

	//verify alert doesn't exist
	$res = get_generic($alert,'','','alert');

	if (sql_num_rows($res) > 0) {
		$_REQUEST['engineStaffAlert'] = null;
		return 'Error: Alert already exists for this staff and id';
	}

	//post alert
	$n_alert = post_generic($alert,$adef,&$mesg,$filter='');
	if (!$n_alert) {
		return 'Failed to post alert: '.$mesg;
	}

	$_REQUEST['engineStaffAlert'] = null;
	$query_string = 'control[object]='.$control['object']
		. '&control[action]='.$control['action']
		. '&control[id]='.$control['id']
		. '&control[message]='.urlencode('Posted alert for '.staff_link($n_alert['staff_id']));
	header('Location: display.php?'.$query_string);
	exit;
}

function get_table_switch_object_id($id,$def,$key='')
{
	/*
	 * returns an array(id,object)
	 */

	$key = orr($key,$def['id_field']);
	$identifier = $def['fields'][$key]['table_switch']['identifier'];

	return explode($identifier,$id);
}

function get_client_refs_generic($rec,$action,$def)
{
	switch ($action) {
	case 'view':
		$id    = $rec[$def['id_field']];
		$table = $def['table'];
		
		//------ extract table and id from joint views -------//
		if ($def['fields'][$def['id_field']]['data_type']=='table_switch' ) {
			list($id,$table) = get_table_switch_object_id($id,$def);
		}
		if (!is_numeric($id)) { return false; }

		$filter = array('ref_table' => strtoupper($table), //fixme: testing
				    'ref_id'    => $id);
		$res = get_generic($filter,'added_at DESC','','client_ref');
		if (sql_num_rows($res) < 1) { return false; }

		$clients = array();
		while ($a = sql_fetch_assoc($res)) {
			$client = $a[CG_MAIN_OBJECT_DB.'_id'];
			$clients[] = div(client_photo($client,0.35) . smaller(client_link($client),2));
		}
		return div(implode("\n",$clients),'',' style="white-space: nowrap;" class="'.CG_MAIN_OBJECT_DB.'"');
	}

	return false;
}

function get_staff_alerts_generic($rec,$action,$def,$control)
{
	switch ($action) {
	case 'add':
		if (!be_null($control['staff_alerts'])) {
			global $UID;
			$alerts = array();
			foreach ($control['staff_alerts'] as $sid) {
				$link = staff_link($sid);
				if ($UID === $sid) {
					$link = bigger(bold('<<'.$link.'>>'));
				}
				$alerts[] = $link;
			}
			return implode(oline(),$alerts);
		}
		return false;
	case 'edit':
		//here the results will be prepended with the queued staff alerts once supported
	case 'view':
		global $engine;
		$id = $rec[$def['id_field']];

		$table = $def['object']; //for now, must use object since for jail, table<>jail, but alerts use jail in ref_table

		//------ extract table and id from joint views -------//
		if ($def['fields'][$def['id_field']]['data_type']=='table_switch' ) {
			list($id,$table) = get_table_switch_object_id($id,$def);
		}

		if (!is_numeric($id)) { return false; }

		$filter = array('ref_table'=>$table,
				    'ref_id'=>$id);
		$res = get_generic($filter,'added_at DESC','','alert_consolidated');
		if (sql_num_rows($res) < 1) {
			return false;
		}

		global $colors,$CG_HEAD_TAG;
		$CG_HEAD_TAG .= style('.hiddenAlertData { padding: 2px 0px 2px 35px; }');
		$alt = 'The following staff have received alerts on '.$def['id_field'].' '.$id;
		$formatted_refs = row(centercell(smaller(bold(alt('Staff Alerts',$alt))))
					    ,' class="staff"');

		static $i = 0;
		while ($a = sql_fetch_assoc($res)) {
			//3rd argument should be 'table-row', but blank works just as well for compliant browsers, 
			//and it also works for IE (while 'table-row' does not)
			$button = Java_Engine::toggle_id_display(smaller('+/- ',2),'hiddenAlertRow'.$i,'');

			$alert_text = webify($a['alert_text']);
			if (preg_match_all('/=====(.*)=====/',$alert_text,$matches)) {
				foreach ($matches[0] as $key => $m) {
					$alert_text = str_replace($m,bold($matches[1][$key]),$alert_text);
				}
			}
			$hidden_content = table(row(leftcell(smaller(bold('time: ').datetimeof($a['added_at'],'US'))))
							. row(leftcell(smaller(bold('by: ').smaller(staff_link($a['added_by'])))))
							. row(cell(smaller(bold(webify($a['alert_subject'])))))
							. row(cell(smaller($alert_text)))
							,'','cellspacing="0" cellpadding="0" class=""');
							    
			$hidden_row = row(cell($hidden_content,' class="hiddenAlertData"'),' id="hiddenAlertRow'.$i.'" style="display: none;"');
			$c = $c==2 ? 1 : 2; //color flipping
			$formatted_refs .= row(cell($button . smaller(staff_link($a['staff_id']))),' class="staffData'.$c.'"')
				. $hidden_row;
			$i++;
		}
		return table($formatted_refs,'',
				 ' class="" cellspacing="0" style="background-color: #efefef; border: 1px solid black;"');
	}
}

function is_safe_sql($sql_all,&$errors,$non_select = false)
{
	global $generic_sql_security_checks;

	/*
	 * Check Security First
	 */

	if (!is_array($sql_all)) {
		$sql_all = array($sql_all);
	}

	$security_passed = true;
	foreach ($sql_all as $sql) {
		if (preg_match('/^SELECT/i',$sql) or $non_select) {
		} else {
			$security_passed = false;
			$errors .= oline(alert_mark('Query must start with SELECT'));
		}
		
		$sql = sql_strip_comments($sql); // comments can't be used to trick the checks
		foreach ($generic_sql_security_checks as $s_check=>$message) {
			if (preg_match($s_check,$sql,$m)) {
				$errors .= oline(alert_mark($message));
				$security_passed = false;
			}
		}

		/*
		 * Check Permissions based on FROM and LEFT JOIN objects
		 */

		preg_match_all('/from\s+([a-z0-9_]+)\s+/i',$sql,$froms);
		preg_match_all('/join\s+([a-z0-9_]+)\s+/i',$sql,$ljs);
		if ($froms or $ljs) {

			$objects = array_unique(array_merge($froms[1],$ljs[1]));

			if (has_perm('open_query,any_table')) {
				//default to above security passed
			} else {
				foreach ($objects as $object) {
					$o_def = get_def($object);
					$view_perm = $o_def['perm_view'];
					$list_perm = $o_def['perm_list'];
					if (preg_match('/^(l_|tmp_)/',$object) 
					    // fixme: define lookup and tmp_ table permissions elsewhere (this grants all)					    
					    or (has_perm($view_perm) and has_perm($list_perm))) {
						//default to above security passed
					} else {
						$security_passed = false;
						$singular = orr($o_def['singular'],$object);
						$errors .= oline(alert_mark('You don\'t have permission for '.$singular.' records'));
					}
				}
			}
		} else {
			$errors .= oline(alert_mark('Couldn\'t determine FROM or JOIN table'));
			$security_passed = false;
		}
	}

	if ($errors) {
		log_error($errors,$silent=true);
	}

	return $security_passed;
}

function cancel_url_generic($rec,$def,$action,$control_array_variable)
{
	if ($cancel_url = $def['cancel_'.$action.'_url']) {
		return $cancel_url;
	} elseif ($action=='add') {

		$idnum = orr($rec[CG_MAIN_OBJECT_DB.'_id'],$rec['staff_id']);
		$cancel_url = (($idnum==$rec['staff_id']) ? CG_STAFF_PAGE : $client_page) . "?id=$idnum";
	} else {
		$cancel_url = $_SERVER['PHP_SELF'].'?'.$control_array_variable.'[action]=view'
			.'&'.$control_array_variable.'[object]='.$def['object']
			.'&'.$control_array_variable.'[id]='.$rec[$def['id_field']];
	}
	return $cancel_url;
}

function build_lookup_query($field_def,$action)
{
	$look=$field_def['lookup'];
	$look_table=$look['table'];
	//action-based table
	if ($action and isTableView($look_table.'_'.$action)) {
		$look_table = $look_table.'_'.$action;
	}
	$look_code=$look['value_field'];
	$show_value = $field_def['show_lookup_code_'.$action];
	$look_label=$look['label_field'];
	$tmp_order = orr($field_def['lookup_order'],'LABEL');
	switch ($tmp_order) {
	case 'TABLE_ORDER':
		$look_order = null;
		break;
	case 'CODE':
		$look_order = $look_code;
		break;
	case 'LABEL':
		$look_order = $look_label;
		break;
	default:
		$look_order = $tmp_order;
	}
	switch ($show_value) {
	case 'CODE':
		$look_label = $look_code;
		break;
	case 'BOTH':
		$look_label = $look_label.'||\' (\'||'.$look_code.'||\')\'';
		break;
	case 'PREPEND_CODE' :
		$look_label = $look_code.'||\' - \'||'.$look_label;
		break;
	case 'DESCRIPTION':
	default:
	}
	$filt = $look['filter'];
	return make_desc_query("SELECT $look_code AS value, $look_label AS label FROM $look_table",$filt,$look_order);
}

function engine_password_cycle($def,$action,$step)
{
	/*
	 * Returns true if password required this time around.
	 */

	$object = $def['object'];

	$re_enter_pwd = true;

	switch ($action) {
	case 'add':
		$check = ( ($step=='submit') or ($step=='confirm_pass'));
		break;
	case 'delete':
		$check = true;
		break;
	case 'view':
	case 'list':
		$re_enter_pwd = false; //passwords currently aren't used for these actions
	default:
	}

	if ( $check ) {
		if ( $def[$action.'_another'] ) {
			if (!isset($_SESSION['ENGINE_PASSWORD_CYCLER'.$object]) ) {
				$pwd_cycle = $_SESSION['ENGINE_PASSWORD_CYCLER'.$object] = 0;
			} else {
				$pwd_cycle = ($step=='confirm_pass') 
					? $_SESSION['ENGINE_PASSWORD_CYCLER'.$object]++
					: $_SESSION['ENGINE_PASSWORD_CYCLER'.$object];
				if ($pwd_cycle >= $def[$action.'_another_password_cycle']) {
					$pwd_cycle=$_SESSION['ENGINE_PASSWORD_CYCLER'.$object]=0;
				}
			}
			$re_enter_pwd = ($pwd_cycle==0) ? true : false;
		}
	}
	return $re_enter_pwd;
}

function engine_set_control_id($control)
{
	if ($id = $control['id']) { //id exists
		return $id;
	}

	$action = $control['action'];
	if ($action == 'list') { //fixme, better handling needed?
		return $action;
	}

	if (in_array($action,array('add','widget'))) {
		//generate unique id for this session
		$_SESSION['ENGINE_UNIQUE_ADD_ID']++;
		return $_SESSION['ENGINE_UNIQUE_ADD_ID'];
	}
}

function generate_session_identifier($object,$id)
{
	return '_'.strtoupper($object).'_'.strtoupper($id);
}

function auto_close_generic($def,$action,$id,$date)
{
	if (!$id) {
		return false;
	}

	$object = $def['object'];
	$fields = array_keys($def['fields']);

	if ($_SESSION['approved_auto_close_'.md5($object . $id)] !== true) {
		return oline('This record ('.$object.' '.$id.') hasn\'t been authorized for auto_close_generic()');
	}

	// check for end date
	$end   = orr($def['active_date_end_field'],$object.'_date_end');
	if (!in_array($end,$fields)) {
		return oline($object.': auto_close_generic() needs an end-date field. Contact your system administrator.');
	}

	if (!($close_date = dateof($date,'SQL'))) {
		return oline('Invalid closing date: '.$date);
	}

	// get existing record
	$filter = array($def['id_field']=>$id);
	$res = call_user_func($def['fn']['get'],$filter,'','',$def);
	if (!$res) {
		return oline('Couldn\'t find '.$object.' record '.$id);
	}
	$rec_last = $rec = sql_fetch_assoc($res);

	//set end date
	$rec[$end] = $close_date;

	//verify record
	if (!call_user_func($def['fn']['valid'],$rec,&$def,&$message,$action,$rec_last)) {
		return bold('This record couldn\'t be closed due to the following errors (it might need to be edited manually):')
			. div($message,'','class="indent"');
	} 

	$a      = call_user_func($def['fn']['post'],$rec,$def,&$message,$filter);

	$_SESSION['approved_auto_close_'.md5($object . $id)] = null; //unset authorization

	return $a ? oline($def['singular'].' record '.$id.' successfully edited.') : $message;
}

function link_engine_list_filter($object,$filter,$label,$options='')
{
	$control = array('object'=>$object,
			     'action'=>'list',
			     'list'=>array(
						 'filter'=>$filter)
			     );
	return link_engine($control,$label,'',$options);
}

function sql_to_php_generic($rec,$def)
{
	if (is_array($rec)) {
		foreach ($rec as $key=>$value) {
			if (in_array($def['fields'][$key]['data_type'],array('lookup_multi','array','staff_list')) and !be_null($value)) {
				$rec[$key] = sql_to_php_array($value);
			}
		}
	}
	return $rec;
}

function php_to_sql_generic($rec,$def)
{
	foreach ($rec as $key=>$value) {
		if (in_array($def['fields'][$key]['data_type'],array('lookup_multi','array','staff_list')) and !be_null($value)) {
			$rec[$key] = php_to_sql_array($value);
		}
	}
	return $rec;
}

function service_note_object_by_id($id)
{
	/*
	 * returns view in which service note is contained
	 */

	$res = get_generic(array('service_id'=>$id),'','','tbl_service');

	if (sql_num_rows($res)==1) {
		$a = sql_fetch_assoc($res);
		switch ($a['service_project_code']) {
		case 'CONNECT':
			return 'service_connections';
		default :
			return 'service_'.strtolower($a['service_project_code']);
		}
	}

	return false;
}

function hot_link_objects( $text, $type="" )
{
/*
	MAKE SURE that changes to get_log_references or
	hot_link_objects are made in both functions, so that
	whatever logs are hot_linked are also reflected
	as references.

*/
	$type = orr($type,array('bar','note','log','bug','dal','news'));
	if (is_array($type)) {

		if (is_table('tbl_service')) {
			$type[] = 'service';
		}

		foreach($type as $ty) {

			$text = hot_link_objects( $text, $ty );

		}
		return $text;
	}

	$engine_types = array('bar','note','client_note','dal','service','news');
	global $show_bugzilla_url;
	if (in_array($type,$engine_types)) {

		$noun="($type)";

	} elseif ( ($type=='bug')) {

		if ($show_bugzilla_url) {
			$noun = "bug(zilla)?";
		} else {
			return $text;
		}
	} else {
		$noun = "(log)";
	}

	while (1) {

		// 1--is it a continuation? (i.e., after "log 5, " comes "7"
		if ($continued && preg_match('/^(,?\s*([ #,+-]|or|and|&amp;|\\n|<br>|<br \/>)*[ ]*)?([0-9]{1,7})(([^0-9]|$).*)/is',$text,$matches)) {

			switch ($type) {
			case 'log':
				$t_link = log_link($matches[3],$matches[3]);
				break;
			case 'bug':
				$t_link = bug_link($matches[3],$matches[3]);
				break;
			case 'note':
				$type = 'client_note';
			default:
				// $type can be set to false when searching for service note type, thus
				// the original is preserved, and reset below
				$original_type = $type;
				if (preg_match('/^service_?/',$type)) {
					$type = service_note_object_by_id($matches[3]);
				}
				$t_link = $type 
					? link_engine(array('object'=>$type,'id'=>$matches[3]),$matches[3])
					: dead_link(alt($matches[3],'No '.$original_type.' record found for id '.$matches[3]));
				$type = $original_type;
			}
			$new_text .= $matches[1] . $t_link;
			$text = $matches[4];
			continue;

		} elseif (preg_match('/(.*?)(('.$noun.'s?)(\s|<br>|<br \/>)*(entry|no|number|#)?(\s|<br>|<br \/>)*([0-9]+))(.*)/is',$text,$matches)) {

			if ($type == 'note') {
				// notes are really client notes, so this does a 'redirect'
				$type = 'client_note';
			}
			if (preg_match('/^service_?/',$type)) {
				$type = 'service'; //this gets changed back again
			}

			if (in_array($type,$engine_types)) {

				$original_type = $type;
				if ($type == 'service') {
					$type = service_note_object_by_id($matches[8]);
				}

			      $new_text .= $matches[1] 
					. ($type ? link_engine(array("object"=>$type,"id"=>$matches[8]),$matches[2])
					   : dead_link(alt($matches[2],'No '.$original_type.' record found for id '.$matches[8])));

				$type = $original_type;

			} elseif ($type=="bug") {

				$new_text .= $matches[1] . bug_link($matches[8],$matches[2]);

			} else {

				$new_text .= $matches[1] . log_link($matches[8],$matches[2]);

			}

			$text = $matches[9];
			$continued = true;
			continue;
		} else {

			$new_text .= $text;
			return $new_text;

		}
	}
}

function engine_translate_singular($string)
{
	static $engine_objects;
	if (!$engine_objects) {
		global $engine;
		$engine_objects = array();
		foreach ($engine as $def) {
			if ($def['object']) {
				$engine_objects['/'.preg_quote($def['object']).'/i'] = $def['singular'];
			}
		}
		uksort($engine_objects,'strlen_cmp_reverse');
	}
	//str_ireplace would be a better choice, but didn't come around until php 5
	return preg_replace(array_keys($engine_objects),array_values($engine_objects),$string);
}

function jump_to_object_link($object,$parent=CG_MAIN_OBJECT_DB,$label='')
{
	$def = get_def($object);
	$pdef = get_def($parent);
	$label = orr($label,'Jump to '.$def['plural']);

	//find parent grouping
	if ($grouping = orr($pdef['child_records'][$object])) {
		$expand_group = 'showElement(\''.$grouping.'ChildList\');';
	}

	return seclink($object,$label,'onclick="javascript:'.$expand_group.'showElement(\''.$object.'ChildList\');"');
}

function make_staff_list_form($value,$key,$def,$control,$formvar)
{
	/*
	 * returns a form field that can be appened for multiple staff
	 */

	$i = 0;
	$value = orr($value,array());
	foreach ($value as $id) {
		$js_id = $key.'Staff'.$i;
		$remove_link = js_link(smaller('remove',2),
					     'document.getElementById(\''.$js_id.'\').innerHTML=\'\';'
					     . ' document.getElementById(\'add'.$key.'Var'.$i.'\').value=\'\'; return false;');
		$hidden .= hiddenvar($formvar.'['.$key.'][]',$id,' id="add'.$key.'Var'.$i.'"') . span(oline($remove_link.' '.staff_link($id)),' id="'.$js_id.'"');
		$i++;
	}

	global $CG_HEAD_TAG;
	$CG_HEAD_TAG .= Java_Engine::get_js('     var current'.$key.'id = \''.$i.'\';

     function addStaff'.$key.'() {
          current'.$key.'id ++;
          var cont = document.getElementById(\'add'.$key.'Container\');
          var staffList = document.getElementById(\'addStaff'.$key.'List\');
          var staffID = staffList.options[staffList.selectedIndex].value;

          var staffName = staffList.options[staffList.selectedIndex].innerHTML;
          var staffLink = \'<a href="' . CG_STAFF_PAGE . '?id=\'+staffID+\'"  class="staffLink">\'+staffName+\'</a>\';

          var removeLink = \'<a href="javascript:void(0);" onclick="javascript:document.getElementById(\\\'add'.$key.'\'+current'.$key.'id+\'\\\').innerHTML=\\\'\\\'; document.getElementById(\\\'add'.$key.'Var\'+current'.$key.'id+\'\\\').value=\\\'\\\'"><font size="-2">remove</font></a> \';
          var staffNameFormatted = \'<span id="add'.$key.'\'+current'.$key.'id+\'">\'+removeLink+\' \'+staffLink+\'<br /></span>\';


          cont.innerHTML+=\'<input type="hidden" name="'.$formvar.'['.$key.'][]" value="\'+staffID+\'"\'+ \' id="add'.$key.'Var\'+current'.$key.'id + \'"/>\'+staffNameFormatted+\'\'; 

          return true;
     }
');

		$out = div($hidden,'add'.$key.'Container')
			. pick_staff_to($formvar.'['.$key.'][]', $active_only='HUMAN', $default=-1 ,$subset=false,$options=' id="addStaff'.$key.'List"')
			. button('Add','','','','javascript:addStaff'.$key.'(); return false;');
		return div($out,'','class="staff" style="border: solid 1px black; padding: 5px; margin: 10px; width: 22em;"');

	return $key;
}

function grab_append_only_fields($rec,$def)
{

	/*
	 * Blanks out the working version for fields defined as append only (ie, sys_log)
	 */

	foreach ($rec as $key => $value) {

		if ($def['fields'][$key]['append_only']) {

			$rec[$key] = '';

		}

	}

	return $rec;
}

?>
Return current item: CHASERS