ODB ----+ * | * Common App DB -> DB ---+--------+-------+ * | | | * Individual tables -> Person Address State * * ******************************************************************************/ require_once('creole/Creole.php'); class ODB { // Static (globalish) vars static public $in_transaction = false; static public $singleton = false; // Class vars public $conn; public $table; public $plural; // so we can find ourselves in others' caches public $id_name = "id"; public $columns = array(); public $has_a = array(); public $has_many = array(); public $has_many_many = array(); public $col_alias = array(); // Instance vars public $currow = array(); // Holds the current row in memory public $cur_result; // Keep a query result handle for looping public $id; public $close_transaction = false; public $attribute = array(); // User-defined extra vars public $has_many_cache = array(); public $global_cache = false; public $viewType = 'ODBView'; public $view; public $searchViewType = 'ODBSearchView'; public $searchView; static function GetNew($class, $id) { if(eval("return {$class}::\$singleton;")) { if(isset($_SESSION['odb'][$class][$id])) { #error_log("Using cached version of $class - $id"); return $_SESSION['odb'][$class][$id]; } else { error_log("Caching new instance of $class - $id"); $obj = new $class; $obj->do_get($id); $_SESSION['odb'][$class][$id] = $obj; return $obj; } } else { error_log("Caching not enabled for $class ($id)"); $obj = new $class; $obj->do_get($id); return $obj; } } function autoLoad($infer_relationships = true) { $table = get_class($this); $this->table = $table; $dbinfo = $this->conn->getDatabaseInfo(); $tableInfo = $dbinfo->getTable($this->table); $this->columns = $tableInfo->getColumns(); $this->id_name = $tableInfo->getPrimaryKey()->getName(); foreach($this->columns as $key => $col) { if(get_class($col) == 'ColumnInfo') { $this->columns[$key] = $col->name; } } error_log("Key: $this->id_name"); error_log("Columns: " . var_export($this->columns, true)); // Unset the primary ID unset($this->columns[array_search($this->id_name, $this->columns)]); // TODO: This only gets has_a relationships, we want it to also do has_many // relationships too if($infer_relationships) { foreach($this->columns as $col) { $colname = $col; error_log("Looking for relationship in $table @ $colname"); $parts = explode('_', $colname); if(count($parts) > 1) { if(count($parts) == 3) { $alias = $parts[0]; $table = $parts[1]; } else { $table = $parts[0]; $alias = $table; } if(class_exists($table)) { # Remove it from the column list unset($this->columns[$colname]); $this->has_a[$alias] = array( 'table' => $table, 'key' => $table . '_id' ); } } } } } function clear() { $this->currow = array(); unset($this->cur_result); unset($this->id); } function attr($attr, $val = NULL) { if(isset($val)) { $this->attr[$attr] = $val; } return $this->attr[$attr]; } function dirty_row($id = NULL, $val = NULL) { if(!isset($id)) $id = $this->id; if(isset($val)) { $_SESSION['dirty_row'][$this->table][$id] = $val; } if(isset($_SESSION['dirty_row'])) return $_SESSION['dirty_row'][$this->table][$id]; return NULL; } function clear_dirty_row($id = NULL) { if(!isset($id)) $id = $this->id; unset($_SESSION['dirty_row'][$this->table][$id]); } function is_dirty($id = NULL) { if(!isset($id)) $id = $this->id; if(isset($_SESSION['dirty_row'][$this->table])) return array_key_exists($id, $_SESSION['dirty_row'][$this->table]); return false; } // We'll just sleep with everything function __sleep() { $members = array_keys(get_class_vars(get_class($this))); $members = array_diff($members, array("in_transaction","singleton")); $m2 = array(); foreach($members as $m) { if(isset($this->$m)) array_push($m2, $m); } $members = $m2; return $members; } function begin_transaction() { if(self::$in_transaction) { $this->close_transaction = false; } else { $this->close_transaction = true; $sql = "BEGIN;"; $stm = $this->conn->prepareStatement($sql); $stm->executeUpdate(); } } function commit_transaction() { if(self::$in_transaction && $this->close_transaction) { $this->close_transaction = false; self::$in_transaction = false; $sql = "COMMIT;"; $stm = $this->conn->prepareStatement($sql); $stm->executeUpdate(); } } function rollback_transaction() { if(self::$in_transaction) { $this->close_transaction = false; self::$in_transaction = false; $sql = "ROLLBACK;"; $stm = $this->conn->prepareStatement($sql); $stm->executeUpdate(); } } function getNonAlias($col) { if(isset($this->col_alias[$col])) $col = $this->col_alias[$col]; return $col; } // TODO: Do some checks to see if this is a valid column function get($col) { if(isset($this->col_alias[$col])) $col = $this->col_alias[$col]; if(empty($this->currow) || (!array_key_exists($col, $this->currow))) { //error_log("Column $col not found in table $this->table"); return NULL; } return $this->currow[$col]; } // TODO: Do some checks to see if this is a valid column function set($col, $val = NULL) { if(is_array($col)) { foreach($col as $colname => $colval) { $this->set($colname, $colval); } return 1; } else { if(isset($this->col_alias[$col])) $col = $this->col_alias[$col]; if(isset($this->currow[$col]) && $this->currow[$col] == $val) { return $val; } $this->currow[$col] = $val; if(isset($this->id)) { $this->dirty_row($this->id, $this->currow); } return $val; } } function get_or_set($col, $val = NULL) { if(!isset($val)) { return $this->get($col); } else { return $this->set($col, $val); } } function update_if_dirty() { if($this->is_dirty()) { error_log("Updating table " . $this->table . ", dirty record #" . $this->id); $this->update(); } } // Put our current values into the database, using either INSERT or UPDATE function update() { if(isset($this->id)) { // We have an ID, so we should update return $this->do_update(); } else { // Welp... no ID found, so lets do an insert return $this->do_insert(); } } function do_update() { // Build and execute our UPDATE query $update_list = preg_replace('/^(.*)$/','$1 = ?', array_keys($this->currow)); $update_list = implode(', ', $update_list); $sql = "UPDATE $this->table SET $update_list where $this->id_name = ?"; $stm = $this->conn->prepareStatement($sql); $stm->executeUpdate(array_merge(array_values($this->currow), array($this->id))); // We are no longer dirty! $this->clear_dirty_row(); // And then re-fetch ourselves for any other values we might've gotten // TODO: Make this optional //$this->do_get($this->id); // And we return the ID to indicate success return $this->id; } function do_insert() { // Build and execute our INSERT query $field_list = preg_replace('/^(.*)$/','$1', array_keys($this->currow)); $field_list = implode(', ', $field_list); $qmark_list = preg_replace('/^(.*)$/','?', array_keys($this->currow)); $qmark_list = implode(', ', $qmark_list); $sql = "INSERT INTO $this->table ($field_list) VALUES ($qmark_list)"; $stm = $this->conn->prepareStatement($sql); $stm->executeUpdate(array_values($this->currow)); // get the generated ID $id = $this->conn->getIdGenerator()->getId(); // And then re-fetch ourselves for any other values we might've gotten $this->clear_dirty_row(); $this->do_get($id); // And we return the ID return $id; } // Get the given ID, possibly from our stored (dirty) version function do_get($id) { if($this->is_dirty($id)) { $this->id = $id; $this->currow = $this->dirty_row($id); error_log("Found in dirty store!"); } else { error_log("I am a " . get_class($this)); error_log("Getting copy from DB for $this->table:$id"); return $this->do_nondirty_get($id); } } function trim_currow() { foreach($this->currow as $name => $val) { $this->currow[$name] = trim($val); } } // Get the ID, but definately NOT from the dirty version function do_nondirty_get($id) { $sql = "SELECT * FROM $this->table WHERE $this->id_name = ?"; $stm = $this->conn->prepareStatement($sql); $result = $stm->executeQuery(array($id)); if($result->getRecordCount() < 1) { error_log("Nothing found during do_get. SQL: $sql - ID: $id"); // Not found } else if ($result->getRecordCount() > 1) { // Too much found error_log("TOO MUCH found during do_get. SQL: $sql - ID: $id"); } else if ($result->getRecordCount() == 1) { $result->next(); $this->currow = $result->getRow(); //$this->trim_currow(); //if(!isset($this->currow[$this->id_name])) { // print("
Table: $this->table, ID: $this->id_name, SQL:$sql, ID: $id");
// var_dump($this->currow);
//}
$this->id = $this->currow[$this->id_name];
unset($this->currow[$this->id_name]);
return $this->id;
}
}
// TODO: This does an immediate delete... should it do a pending delete and
// then actually do the delete when an update() is issued? Probably. Oh well.
function delete() {
if(isset($this->id)) {
$sql = "DELETE FROM $this->table WHERE $this->id_name = ?";
$stm = $this->conn->prepareStatement($sql);
$result = $stm->executeUpdate(array($this->id));
$this->clear_dirty_row($this->id);
# Remove from session obj cache
unset($_SESSION['odb'][get_class($this)][$this->id]);
# Look for ourselves in has-many relationships
error_log("delete: looking for has_a stuff");
foreach($this->has_a as $holder) {
error_log(" trying {$holder['table']}");
if(isset($holder['obj'])) {
error_log(" I do have an obj for them...");
$obj = $holder['obj'];
if(isset($obj->has_many_cache[$this->plural])) {
error_log(" Looking to delete myself from " . get_class($obj));
$offset = array_search($this, $obj->has_many_cache[$this->plural]);
if($offset)
error_log(" Deleting from " . get_class($obj));
unset($obj->has_many_cache[$this->plural][$offset]);
}
}
}
unset($this->id);
} else {
// TODO: Throw an exception
}
}
// TODO: Implement other search types. Lets say the default is 'AND'
// Also consider other ways of having a more powerful search
// This sets up an iteration, so if you just want the first one remember to check $obj->next()
// TODO: This currently doesn't pay attention to non-inserted dirty rows
function search($extra = "", $type = 'AND', $COMPAIR = '=') {
$where_list = preg_replace('/^(.*)$/',"\$1 $COMPAIR ?", array_keys($this->currow));
$where_list = implode(" $type ", $where_list);
if($where_list == '') {
$sql = "SELECT * FROM $this->table $extra";
} else {
$sql = "SELECT * FROM $this->table WHERE $where_list $extra";
}
$stm = $this->conn->prepareStatement($sql);
$result = $stm->executeQuery(array_values($this->currow));
if($result->getRecordCount() < 1) {
// Not found
#error_log("No records found in search. SQL: $sql; params: ("
# . join(',',$this->currow) . ")");
} else {
$this->cur_result = $result;
}
}
function getSqlLink($t, $otherWay = false) {
$table = get_class($t);
foreach($this->has_a as $r => $v) {
if($v['table'] == $table) {
$k = $this->getNonAlias($v['key']);
return "{$this->table}.{$k} = {$t->table}.{$t->id_name}";
}
}
foreach($this->has_many as $r => $v) {
if($v['table'] == $table) {
$k = $t->getNonAlias($v['key']);
return "{$this->table}.{$this->id_name} = {$t->table}.{$k}";
}
}
if(!$otherWay) {
return $t->getSqlLink($this, true);
}
return NULL;
}
# $objs structure:
# $objs = array(
# array($timesheetRow, 'Timesheet'),
# array($project, 'TimesheetRow'),
# array($client, 'Project'),
# array($employee, 'Timesheet'),
# array($user, 'Employee'),
function stupidSearchSets($connections, $use_or = false) {
$params = array();
$sql = "
SELECT *
FROM $this->table
WHERE {$this->table}.{$this->id_name} in (
SELECT {$this->table}.{$this->id_name}
FROM $this->table
";
$where_sql = ' WHERE 1 = 1 ';
$combine = 'AND';
if($use_or) {
$where_sql = ' WHERE 1=0 ';
$combine = 'OR';
}
# Get baseline where restrictions
if(!empty($this->currow)) {
foreach($this->currow as $col => $val) {
if($val != '') {
//error_log(" " . $obj->table . ".$col = ? ($val)");
if(preg_match('/^[\w\s]+$/', $val)) {
$val = preg_replace('/\s+/', '%', $val);
$where_sql .= " $combine {$this->table}.{$col} like ? \n";
$params[] = "%$val%";
} else {
$where_sql .= " $combine {$this->table}.{$col} = ? \n";
$params[] = $val;
}
}
}
}
foreach($connections as $c) {
list($obj, $link) = $c;
//error_log("Obj: " . $obj->table . " - $link");
$t = new $link;
$sql_link = $obj->getSqlLink($t);
$sql .= " LEFT JOIN $obj->table ON $sql_link \n";
foreach($obj->currow as $col => $val) {
if($val != '') {
//error_log(" " . $obj->table . ".$col = ? ($val)");
if(preg_match('/^[\w\s]+$/', $val)) {
$val = preg_replace('/\s+/', '%', $val);
$where_sql .= " $combine {$obj->table}.{$col} like ? \n";
$params[] = "%$val%";
} else {
$where_sql .= " $combine {$obj->table}.{$col} = ? \n";
$params[] = $val;
}
}
}
}
$sql .= " $where_sql )";
#error_log("SQL:\n$sql\n");
#error_log("Params: " . var_export($params, true));
// Lets DO IT!!! WOOOOHOOOOOO!
error_log("Start Query");
$stm = $this->conn->prepareStatement($sql);
$result = $stm->executeQuery($params);
error_log("Query Done");
if($result->getRecordCount() < 1) {
// Not found
#error_log("No records found in search. SQL: $sql; params: ("
# . join(',',$this->currow) . ")");
} else {
$this->cur_result = $result;
}
}
function getRecordCount() {
if(isset($this->cur_result)) {
return $this->cur_result->getRecordCount();
}
return -1;
}
function next() {
if(isset($this->cur_result)) {
$state = $this->cur_result->next();
$this->currow = $this->cur_result->getRow();
if(!empty($this->id_name)) {
$this->id = $this->currow[$this->id_name];
unset($this->currow[$this->id_name]);
} else {
return $state;
}
# If we have a non-updated version of this, use the local one
if($this->is_dirty()) {
$this->currow = $this->dirty_row($this->id);
}
return $this->id;
} else {
// TODO: Throw an exception or something
}
}
function fetch_all() {
$all = array();
while($this->next()) {
$all[] = clone($this);
}
return $all;
}
function fetch_all_has_many($method, $args = NULL) {
if(isset($this->has_many[$method]['cache'])
&& $this->has_many[$method]['cache']) {
if(isset($this->has_many_cache[$method]))
return $this->has_many_cache[$method];
$many = $this->search_has_many($method, $args);
// Prepair for reverse-lookups
$cname = get_class($this);
$cname = substr_replace($cname, strtolower($cname{0}), 0, 1);
if(isset($many->has_a[$cname])) {
$many->has_a[$cname]['obj'] = $this;
}
$all = $many->fetch_all();
$this->has_many_cache[$method] = $all;
return $all;
} else {
$many = $this->search_has_many($method, $args);
$all = $many->fetch_all();
return $all;
}
}
// Go through all the dirty rows for this table and look for exact matches
function fetch_dirty() {
$dirty = array();
if(isset($_SESSION['dirty_row'][$this->table])) {
foreach($_SESSION['dirty_row'][$this->table] as $id => $row) {
$keeper = true;
foreach($this->currow as $col => $val) {
if($row[$col] != $val) {
$keeper = false;
break;
}
}
if($keeper)
$dirty[] = $id;
}
}
return $dirty;
}
// Nice little wrapper to get the current ID
function id($new_id = null) {
// You probably shouldn't set the ID...
if(isset($new_id)) {
$this->id = $new_id;
}
return $this->id;
}
function get_or_set_has_a($col, $other_obj = NULL) {
$key = $this->has_a[$col]['key'];
$obj = $this->has_a[$col]['table'];
if(isset($other_obj)) {
if(is_string($other_obj)) {
$this->set($key, $other_obj);
return $other_obj;
}
// Add ourselves to the has_a cache if need be
if(isset($other_obj->has_many[$this->plural]['cache'])
&& $other_obj->has_many[$this->plural]['cache']) {
$other_obj->has_many_cache[$this->plural][] = $this;
$this->has_a[$col]['obj'] = $other_obj;
}
$val = $other_obj->id();
$this->set($key, $val);
return $val;
} else {
// Check to see if we have this cached as a holder
if(isset($this->has_a[$col]['obj']))
return $this->has_a[$col]['obj'];
// Figure out what key we are looking for
$val = $this->get($key);
$thing = ODB::GetNew($obj, $val);
return $thing;
}
}
function add_has_many($table, $obj) {
#...
}
function search_has_many($table, $search = NULL) {
$many = $this->has_many[$table];
$table = $many['table'];
$key = $many['key'];
$obj = new $table;
$obj->set($key, $this->id);
call_user_func_array(array($obj, 'search'), $search);
return $obj;
}
function remove_has_many($table, $obj) {
#...
}
function add_has_many_many($table, $obj) {
#...
}
function search_has_many_many($table, $search = NULL) {
#...
#$this->has_many_many = array(
# 'groups' => array(
# 'table' => 'Group',
# 'table_key' => 'group_id',
# 'connector' => 'intranet_user_group',
# 'key' => 'user_id')
#);
}
function remove_has_many_many($table, $obj) {
#...
}
// The real magic
// First we check to see if this is a joined column, if so we do a lookup
// Then we do a get/set of the column they gave us
function __call($method, $args) {
$arg_zero = isset($args[0]) ? $args[0] : NULL;
// See if this is one of our columns
if(in_array($method, $this->columns)) {
return $this->get_or_set($method, $arg_zero);
}
// Check for has-a relationship get/set
// This will return a single instance of the other object
if(in_array($method, array_keys($this->has_a))) {
return $this->get_or_set_has_a($method, $arg_zero);
}
// Check for has-many relationship get (array)
// This will return a single instance of the other object
if(in_array($method, array_keys($this->has_many))) {
return $this->fetch_all_has_many($method, $args);
}
if(substr($method, 0, 7) == 'search_') {
$method = substr($method, 7);
// Check for has-many relationship search
// $addr_list = $person->search_addresses( ... )
// --> $addr_list = $address->search(array('person_id' => $person->id(), ...));
if(in_array($method, array_keys($this->has_many))) {
return $this->search_has_many($method, $args);
}
// Check for many-to-many relationship search
// $addr_list = $person->search_addresses( ... )
// --> $addr_list = $address->search(array('person_id' => $person->id(), ...));
if(in_array($method, array_keys($this->has_many_many))) {
return $this->search_has_many_many($method, $args);
}
}
// Check for a has-many relationship addition
if(in_array('add_to_' . $method, array_keys($this->has_many))) {
return $this->add_has_many($method, $args);
}
// Check for a has-many relationship removal
if(in_array('remove_from_' . $method, array_keys($this->has_many))) {
return $this->remove_has_many($method, $args);
}
// Check for many-to-many relationship addition
if(in_array('add_to_' . $method, array_keys($this->has_many_many))) {
return $this->add_has_many_many($method, $args);
}
// Check for a many-to-many relationship removal
if(in_array('remove_from_' . $method, array_keys($this->has_many_many))) {
return $this->remove_has_many_many($method, $args);
}
// Check to see if this is aliased as something else
if(preg_grep("/(add_to_|remove_from_|search_)?$method/",
array_keys($this->col_alias))) {
if(isset($this->col_alias[$method]))
return $this->__call($this->col_alias[$method], $args);
}
throw new Exception("Unknown Method $method!");
}
/*******************************
** Database-aware HTML tools **
*******************************/
function html_name($col) {
if(isset($this->col_alias[$col]))
$col = $this->col_alias[$col];
return (get_class($this) .'['. $this->id .']['. $col . ']');
}
// An input form field
function html_input($col, $extra_html = NULL) {
if(isset($this->col_alias[$col]))
$col = $this->col_alias[$col];
$html_name = $this->html_name($col);
$val = $this->get($col);
return "";
}
// An textarea form field
function html_textarea($col, $extra_html = NULL) {
if(isset($this->col_alias[$col]))
$col = $this->col_alias[$col];
$html_name = $this->html_name($col);
$val = $this->get($col);
return "";
}
function html_dropdown($arg) {
$col = $arg['col'];
$show_col = $arg['show'][0];
$extra_html = isset($arg['extra_html']) ? $arg['extra_html'] : NULL;
if(isset($this->col_alias[$col]))
$col = $this->col_alias[$col];
$html_name = $this->html_name($col);
$obj = $this->has_a[$col]['table'];
$key = $this->has_a[$col]['key'];
$thing = new $obj;
$thing->search();
$cur_id = $this->$col()->id();
$html = "";
return $html;
}
function html_query_val($query, $col) {
if(isset($query[get_class($this)][$this->id][$col])) {
return $query[get_class($this)][$this->id][$col];
} else {
return NULL;
}
}
// Update the current row based on the most recent GET/POST
// By default update all fields, but if we are given a list just update those
function html_update($query, $col_list = NULL) {
$found_update = false;
if(!isset($col_list)) {
$col_list = $this->columns;
}
foreach($col_list as $col) {
if(isset($this->col_alias[$col]))
$col = $this->col_alias[$col];
$newval = $this->html_query_val($query, $col);
if($newval != $this->$col()) {
$this->$col($newval);
$found_update = true;
}
}
return $found_update;
}
function html_search_update($query, $col_list = NULL) {
$found_update = false;
if(!isset($col_list)) {
$col_list = $this->columns;
}
foreach($col_list as $col) {
$col = $this->getNonAlias($col);
$newval = isset($query[get_class($this)]['search'][$col])
? $query[get_class($this)]['search'][$col] : NULL;
$fieldName = get_class($this) . "[search][$col]";
error_log("$fieldName -- val: $newval");
if($newval != $this->$col()) {
$this->$col($newval);
$found_update = true;
}
}
return $found_update;
}
function html_search_text($col, $label) {
$col = $this->getNonAlias($col);
$fieldName = get_class($this) . "[search][$col]";
$val = $this->get($col);
$out = "
$label
";
return $out;
}
function view() {
if(isset($this->view)) return $this->view;
$viewType = $this->viewType;
$this->view = new $viewType($this);
return $this->view;
}
function searchView() {
if(isset($this->searchView)) return $this->searchView;
$viewType = $this->searchViewType;
$this->searchView = new $viewType($this);
return $this->searchView;
}
} // End of odb class
function lcfirst($s) {
return strtolower($s{0}) . substr($s,1);
}
class ODBView {
public $data; // The ODB object to whom we belong
function __construct($owner) {
$this->data = $owner;
}
function html_name($col) {
return (get_class($this->data) .'['. $this->data->id .']['. $col . ']');
}
// An input form field
function text($col, $extra_html = NULL) {
$html_name = $this->html_name($col);
$val = $this->data->get($col);
return "";
}
// An textarea form field
function textarea($col, $extra_html = NULL) {
$html_name = $this->html_name($col);
$val = $this->data->get($col);
return "";
}
/* Disabled until I fix it up a bit
function dropdown($arg) {
$col = $arg['col'];
$show_col = $arg['show'][0];
$extra_html = isset($arg['extra_html']) ? $arg['extra_html'] : NULL;
if(isset($this->data->col_alias[$col]))
$col = $this->col_alias[$col];
$html_name = $this->html_name($col);
$obj = $this->has_a[$col]['table'];
$key = $this->has_a[$col]['key'];
$thing = new $obj;
$thing->search();
$cur_id = $this->$col()->id();
$html = "";
return $html;
}
*/
function query_val($query, $col) {
if(isset($query[get_class($this)][$this->id][$col])) {
return $query[get_class($this)][$this->id][$col];
} else {
return NULL;
}
}
// Update the current row based on the most recent GET/POST
// By default update all fields, but if we are given a list just update those
function update($query, $col_list = NULL) {
$found_update = false;
if(!isset($col_list)) {
$col_list = $this->data->columns;
}
foreach($col_list as $col) {
$newval = $this->query_val($query, $col);
if($newval != $this->data->get($col)) {
$this->$col($newval);
$found_update = true;
}
}
return $found_update;
}
}
class ODBSearchView {
public $data; // The ODB object to whom we belong
function __construct($owner) {
$this->data = $owner;
}
function html_name($col) {
$fieldName = get_class($this->data) . "[search][$col]";
return $fieldName;
}
function update($query, $col_list = NULL) {
$found_update = false;
if(!isset($col_list)) {
$col_list = $this->columns;
}
foreach($col_list as $col) {
$newval = isset($query[get_class($this->data)]['search'][$col])
? $query[get_class($this->data)]['search'][$col] : NULL;
if($newval == '__ANY') $newval = NULL;
error_log("Updating " . get_class($this->data) . " - $col - $newval");
if($newval != $this->data->get($col)) {
$this->data->$col($newval);
$found_update = true;
}
}
return $found_update;
}
function text($col, $label = NULL) {
$fieldName = $this->html_name($col);
$val = $this->data->get($col);
$out = "
$label
";
return $out;
}
// This does a drop-down on a has-a link
function lookup($col, $label = NULL) {
$col = $this->data->getNonAlias($col);
$fieldName = $this->html_name($col);
$key = $this->data->has_a[$col]['key'];
$obj = $this->data->has_a[$col]['table'];
error_log("Looking for $key in $obj");
$val = $this->data->get($key);
//$col()->id();
$option = new $obj;
$option->search();
$out = "
$label
\n";
return $out;
}
}
?>