Xataface 2.0
Xataface Application Framework
Dataface/Relationship.php
Go to the documentation of this file.
00001 <?php
00002 /*-------------------------------------------------------------------------------
00003  * Xataface Web Application Framework
00004  * Copyright (C) 2005-2008 Web Lite Solutions Corp (shannah@sfu.ca)
00005  * 
00006  * This program is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU General Public License
00008  * as published by the Free Software Foundation; either version 2
00009  * of the License, or (at your option) any later version.
00010  * 
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  * 
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, write to the Free Software
00018  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00019  *-------------------------------------------------------------------------------
00020  */
00021  
00038 class Dataface_Relationship {
00039 
00040         /*
00041          * The name of the relationship.
00042          */
00043         var $_name;
00044         
00045         /*
00046          * Reference to the source table of the relationship.
00047          */
00048         var $_sourceTable;
00049         
00050         /*
00051          * Arrayh of references to the destination tables of the relationship.
00052          */
00053         var $_destinationTables;
00054         
00055         /*
00056          * An associative array mapping field names to tables.
00057          */
00058         var $_fieldTableLookup;
00059         
00060         /*
00061          * A Descriptor array to describe the relationship.
00062          */
00063         var $_schema;
00064         
00065         /*
00066          * The key fields of the relationship.
00067          */
00068         var $_keys;
00069         
00070         
00077         var $_sql_generated = array();
00078         
00079         var $_permissions = array();
00080         
00081         var $app;
00082         
00087         var $_cache=array();
00088         
00089         var $addNew;
00090         var $addExisting;
00091         
00092         
00093         
00104         function Dataface_Relationship($tablename, $relationshipName, &$values){
00105                 $this->app =& Dataface_Application::getInstance();
00106                 $this->_name = $relationshipName;
00107                 $this->_sourceTable =& Dataface_Table::loadTable($tablename);
00108                 $this->_schema = array();
00109                 $res = $this->_init($values);
00110                 if ( PEAR::isError($res) ){
00111                         throw new Exception($res->getMessage());
00112                 }
00113                 
00114                 if ( !isset($this->_schema['permissions']) ){
00115                         $app =& Dataface_Application::getInstance();
00116                         $this->_schema['permissions'] = Dataface_PermissionsTool::getRolePermissions($app->_conf['default_relationship_role']);
00117                 }
00118                 $this->_permissions =& $this->_schema['permissions'];
00119                 
00120         }
00121         
00131         function &fields($includeAll=false, $includeTransient=false){
00132                 if ( !$includeAll ){
00133                         
00134                         return $this->_schema['columns'];
00135                 } else {
00136                         if ( !isset($this->_cache[__FUNCTION__][intval($includeAll)][intval($includeTransient)]) ){
00137                                 $tables =& $this->getDestinationTables();
00138                                 $out = array();
00139                                 $used_names = array();
00140                                 foreach ( array_keys($tables) as $i ){
00141                                         foreach ( $tables[$i]->fields(false,true, $includeTransient) as $fld ){
00142                                                 if ( @$fld['grafted'] and @$used_names[$fld['Field']] ) continue;
00143                                                         // We don't want grafted fields overwriting valid fields in
00144                                                         // other tables.
00145                                                 $out[] = $tables[$i]->tablename.'.'.$fld['Field'];
00146                                                 $used_names[ $fld['Field'] ] = 1;
00147                                         }
00148                                 }
00149                                 $this->_cache[__FUNCTION__][intval($includeAll)][intval($includeTransient)] = $out;
00150                         }
00151                         return $this->_cache[__FUNCTION__][intval($includeAll)][intval($includeTransient)];
00152                 }
00153         }
00154         
00178         function hasField($fieldname, $checkAll=false, $includeTransient=false){
00179                 if ( strpos($fieldname,'.') === false ){
00180                         if (in_array($fieldname, $this->_schema['short_columns'] ) ) return true;
00181                         if ( ($checkAll or $includeTransient) and preg_grep('/\.'.preg_quote($fieldname, '/').'$/', $this->fields($checkAll, $includeTransient))){
00182                                 return true;
00183                         }
00184                         return false;
00185                 } else {
00186                         return in_array( $fieldname, $this->fields($checkAll, $includeTransient) );
00187                 }
00188         }
00189         
00190         
00191         
00202         function _init(&$rel_values){
00203                 $r =& $this->_schema;
00204                 /*
00205                  * First we will check the array for parameters.  Parameters might include
00206                  * default values for new records in the relationship - or for existing records
00207                  * in the relationship.
00208                  */
00209                 foreach ($rel_values as $key=>$value){
00210                         if ( strpos($key,":") !== false ){
00211                                 $path = explode(":", $key);
00212                                 $len = count($path);
00213                                 
00214                                 $val =& $r;
00215                                 
00216                                 for ($i=0; $i<$len; $i++ ){
00217                                         //if (!isset($val[$path[$i]]) ){
00218                                                 if ( $i == $len -1 ) $val[$path[$i]] = $value;
00219                                                 else {
00220                                                         if ( !isset($val[$path[$i]]) ) {
00221                                                                 $val[$path[$i]] = array();
00222                                                         }
00223                                                         $valTemp =& $val;
00224                                                         unset($val);
00225                                                         $val =& $valTemp[$path[$i]];
00226                                                         unset($valTemp);
00227                                                 }
00228                                         //}
00229                                 }               
00230                         
00231                         }
00232                 }
00233                 
00234                 if ( array_key_exists( '__sql__', $rel_values ) ){
00235                         // The relationship was defined using an SQL statement
00236                         $r['sql'] = $rel_values['__sql__'];
00237                         $matches = array();
00238                         /* MOD START 051021 - shannah@sfu.ca - Using PEAR SQL parser package instead of regexes. */
00239                         $parser = new SQL_Parser();
00240                         $struct = $parser->parse($r['sql']);
00241                         if ( PEAR::isError($struct) ){
00242                                 error_log($struct->toString()."\n".implode("\n", $struct->getBacktrace()));
00243                                 throw new Exception("Failed to parse relationship SQ.  See error log for details.", E_USER_ERROR);
00244                                 
00245                         }
00246                         $parser_wrapper = new SQL_Parser_wrapper($struct);
00247                         $parser_wrapper->fixColumns();
00248                         $r['parsed_sql'] =& $struct;
00249                         $r['tables'] = $struct['table_names'];
00250                         $r['columns'] = $struct['column_names'];
00251                         foreach ($struct['columns'] as $colstruct){
00252                                 if ( $colstruct['type'] == 'ident' and @$colstruct['alias'] ){
00253                                         $r['aliases'][$colstruct['value']] = $colstruct['alias'];
00254                                 }
00255                         }
00256                         $temp = array();
00257                         foreach ( $r['columns'] as $column ){
00258                                 $col = $parser_wrapper->resolveColumnName($column);
00259                                 if (preg_match('/\.$/', $col) ){
00260                                         $col = $col.'*';
00261                                 }
00262                                 $temp[] = $col;
00263                         }
00264                         $r['columns'] = $temp;
00265                         unset($struct);
00266                         unset($temp);
00267                         /* MOD END 051021 */
00268                         
00269 
00270                 } else {
00271                         // The relationship was not defined using SQL.  It merely defines match columns
00272                         // and select columns
00273                         
00274                         $select = '*';
00275                                 // Default selection to all columns
00276                                 
00277                         if ( array_key_exists( '__select__', $rel_values ) ){
00278                                 // __select__ should be comma-delimited list of column names.
00279                                 $select = $rel_values['__select__'];
00280                         }
00281                         
00282                         $tables = array();
00283                                 // stores list of table names involved in this relation
00284                                 
00285                         // Let's generate an SQL query based on the information given
00286                         //
00287                         
00288                         $from = 'from ';
00289                                 // from portion of generated sql query
00290                         $where = 'where ';
00291                                 // where portion of generated sql query
00292                                 
00293                         foreach ( $rel_values as $c1 => $c2 ){
00294                                 // Iterate through all of the match columns of the relationship
00295                                 
00296                                 if ( in_array( $c1, array('__sql__', '__select__', '__sort__','__domain__') ) ) continue;
00297                                         // special flags like sql, select, and sort are not column matchings.. we skip them.
00298                                 if ( strpos( $c1, ":" ) !== false ) continue;
00299                                         // This is a parameter so we ignore it.
00300                                         
00301                         
00302                                 
00303                                 // get the paths of the related columns
00304                                 // Match columns may be given as Table_name.Column_name dotted pairs... we need to separate
00305                                 // the tablenames from the column names.
00306                                 $p1 = explode( '.', $c1);
00307                                 $p2 = explode('.', $c2);
00308                                 
00309                                 if ( count( $p1 ) == 1 ){
00310                                         // Only column name is given.. we assume the tablename is the current table.
00311                                         array_unshift( $p1, $this->_sourceTable->tablename );
00312                                 }
00313                                 if ( count($p2) ==1 ){
00314                                         // Only the column name is given for rhs... assume current table name.
00315                                         array_unshift( $p2, $this->_sourceTable->tablename );
00316                                 }
00317                                 
00318                                 // add the tables to our table array... we omit the current table though.
00319                                 if ( !in_array( $p1[0], $tables ) && $p1[0] != $this->_sourceTable->tablename) $tables[] = $p1[0];
00320                                 if ( !in_array( $p2[0], $tables ) && $p2[0] != $this->_sourceTable->tablename) $tables[] = $p2[0];
00321                                 
00322                                 // Simplify references to current table to be replaced by variable value
00323                                 if( $p1[0] == $this->_sourceTable->tablename ){
00324                                         $lhs = "'\$$p1[1]'";
00325                                 } else {
00326                                         $lhs = "$p1[0].$p1[1]";
00327                                 }
00328                                 
00329                                 if ( $p2[0] == $this->_sourceTable->tablename ){
00330                                         if ( strpos($p2[1], '$')===0){
00331                                                 $var = '';
00332                                         } else {
00333                                                 $var = '$';
00334                                         }
00335                                         $rhs = "'".$var.$p2[1]."'";
00336                                 } else {
00337                                         $rhs = "$p2[0].$p2[1]";
00338                                 }
00339                                 
00340                                 // append condition to where clause
00341                                 $where .= strlen($where) > 6 ? ' and ' : '';
00342                                 $where .= "$lhs=$rhs";
00343                         }
00344                         
00345                         
00346                         
00347                         foreach ($tables as $table){
00348                                 $from .= $table.', ';
00349                         }
00350                         
00351                         $from = substr( $from, 0, strlen($from)-2);
00352                         
00353                         $r['sql'] = "select $select $from $where";
00354                         
00355                         
00356                         
00357                         /* MOD START 051021 - shannah@sfu.ca - Using PEAR SQL parser package instead of regexes. */
00358                         $parser = new SQL_Parser(null, 'MySQL');
00359                         
00360                         $struct =& $parser->parse($r['sql']);
00361                         $parser_wrapper = new SQL_Parser_wrapper($struct, 'MySQL');
00362                         $parser_wrapper->fixColumns();
00363                         $r['parsed_sql'] =& $struct;
00364                         $r['tables'] = $struct['table_names'];
00365                         $r['columns'] = $struct['column_names'];
00366                         $temp = array();
00367                         foreach ( $r['columns'] as $column ){
00368                                 $col = $parser_wrapper->resolveColumnName($column);
00369                                 if (preg_match('/\.$/', $col) ){
00370                                         $col = $col.'*';
00371                                 }
00372                                 $temp[] = $col;
00373                         }
00374                         $r['columns'] = $temp;
00375                         unset($struct);
00376                         
00377                 }
00378                 
00379                 $res = $this->_normalizeColumns();
00380                 if ( PEAR::isError($res) ) return $res;
00381                 $r['short_columns'] = array();
00382                 foreach ($r['columns'] as $col ){
00383                         list($table,$col) = explode('.', $col);
00384                         $r['short_columns'][] = $col;
00385                 }
00386         
00387         }
00388         
00393         function _normalizeColumns(){
00394         
00395                 $rel =& $this->_schema;
00396                 
00397                 
00398                 $tables =& $rel['tables'];
00399                 $selected_tables = array();
00400                 $rel['selected_tables'] =& $selected_tables;
00401                         // contains a list of the tables that actually have values returned in the select statement
00402                 
00403                 $len = sizeof($rel['columns']);
00404                 for ($i=0; $i<sizeof($rel['columns']); $i++){
00405                         $matches = array();
00406                         
00407                         // Case 1: This column has a wildcard.  eg: Profiles.* or just simply *
00408                         if ( preg_match('/^(\w+\.){0,1}\*$/', $rel['columns'][$i], $matches) ){
00409                                 // we are returning all columns from a particular table
00410                                 if ( isset( $matches[1]) ){
00411                                         $table = $matches[1];
00412                                         
00413                                         $temp_tables = array();
00414                                         $temp_tables[] = substr($table, 0, strlen($table)-1);
00415                                 } else {
00416                                         $temp_tables = $tables;
00417                                 }
00418                                 $temp_columns = array();
00419                                 
00420                                 // go through each table requested, and extract its columns
00421                                 foreach ($temp_tables as $table){
00422                                         
00423                                         $table_table =& Dataface_Table::loadTable($table, $this->_sourceTable->db);
00424                                         if ( PEAR::isError($table_table) ){
00425                                                 $table_table->addUserInfo("Failed to load table for table '$table'");
00426                                                 return $table_shema;
00427                                         }
00428                                         
00429                                         $fields = array_keys($table_table->fields());
00430                                         for ($j=0; $j<count($fields); $j++){
00431                                                 $fields[$j] = $table.'.'.$fields[$j];   
00432                                         }
00433                                         
00434                                         $temp_columns = array_merge($temp_columns, $fields);
00435                                         if ( !in_array( $table, $selected_tables ) ){
00436                                                 $selected_tables[] = $table;
00437                                         }
00438                                 }
00439                                 
00440                                 // We need to add all of the columns that we found to the persistent columns list for this relationship.
00441                                 // But we need to remove the entry with the '*' because it is meaningless from here on out.
00442                                 // Case A: We are at the first element
00443                                 if ( $i==0 ){
00444                                         $rel['columns'] = array_merge( $temp_columns, array_slice( $rel['columns'], 1, $len-1) );
00445                                         
00446                                 // Case B: We are at the last element
00447                                 } else if ( $i==$len-1 ){
00448                                         $rel['columns'] = array_merge( $rel['columns'], $temp_columns );
00449                                         $len = sizeof($rel['columns']);
00450                                         $i = $len-1;
00451                                                 // increment the counter so that we don't repeat all of the ones we just created.
00452                                                 
00453                                 // Case C: We are somewhere in the middle of the columns list
00454                                 } else {
00455                                         
00456                                         $rel['columns'] = array_merge( array_slice( $rel['columns'], 0, $i),
00457                                                                                                 $temp_columns,
00458                                                                                                 array_slice( $rel['columns'], $i+1, $len-$i-1) );
00459                                         $len = sizeof($rel['columns']);
00460                                         $i = $i + sizeof($temp_columns) -1;
00461                                                 // increment the counter so that we don't repeat all of the ones we just created.
00462                                 }
00463                                 unset($table_table);
00464                                         // to keep us from doing damage
00465                                         
00466                         
00467                         // Case 2: This is a fully qualified column address.    
00468                         } else if ( preg_match('/^(\w+)\.(\w+)$/', $rel['columns'][$i], $matches) ) {
00469                                 
00470                                 $table = $matches[1];
00471                                 $column = $matches[2];
00472                                 $table_table =& Dataface_Table::loadTable($table, $this->_sourceTable->db);
00473                                 if ( PEAR::isError($table_table) ){
00474                                         $table_table->addUserInfo("Failed to load table for table '$table'");
00475                                         error_log($table_table->toString()."\n".implode("\n", $table_table->getBacktrace()));
00476                                         throw new Exception("Failed to validate column ".$rel['columns'][$i].". See error log for details.", E_USER_ERROR);
00477                                         
00478                                 }
00479 
00480                                 $selected_tables[] = $table;
00481                                 // this column is ok and already absolute.
00482                                 
00483                         // Case 3: This column is specified by only a column name - needs to be made absolute.
00484                         } else {
00485                                 // it is just a single column declaration
00486                                 $name = Dataface_Table::absoluteFieldName($rel['columns'][$i], $tables, $this->_sourceTable->db);
00487                                 if ( PEAR::isError($name) ){
00488                                         $name->addUserInfo("Failed get absolute field name for '".$rel['columns'][$i]."'");
00489                                         
00490                                         return $name;
00491                                 }
00492                                 $rel['columns'][$i] = $name;
00493                                 
00494                                 $matches = array();
00495                                 if ( preg_match('/(\w+)\.(\w+)/', $name, $matches)){
00496                                         $selected_tables[] = $matches[1];
00497                                 } else {
00498                                         PEAR::raiseError(Dataface_SCHEMA_PARSE_ERROR,null,null,null,"Error parsing table name from '$name' ");
00499                                 }
00500                         }
00501                 }
00502                 
00503                 $this->_schema['selected_tables'] = array_unique($this->_schema['selected_tables']);
00504 
00505                         
00506                                 
00507                                 
00508                                 
00509         
00510         }
00511         
00512         function getName(){
00513                 return $this->_name;
00514         }
00515         
00529         function getSQL($getBlobs=false, $where=0, $sort=0){
00530                 $start = microtime_float();
00531                 import('SQL/Compiler.php');
00532                 import( 'SQL/Parser/wrapper.php');
00533                 $loadParserTime = microtime_float() - $start;
00534                 if ( isset($this->_sql_generated[$where][$sort]) and $this->_sql_generated[$where][$sort] ){
00535                         /*
00536                          * The SQL has already been generated and stored.  We can just return it.
00537                          */
00538                         if ( $getBlobs ){
00539                                 // We will be returning blob columns as well
00540                                 return $this->_schema['sql_with_blobs'][$where][$sort];
00541                         } else {
00542                                 // We will NOT be returning BLOB columns
00543                                 return $this->_schema['sql_without_blobs'][$where][$sort];
00544                         }
00545                 } else {
00546                         /*
00547                          * The SQL has not been generated yet.  We will generate it.
00548                          */
00549                         $this->_sql_generated[$where][$sort] = true;
00550                         if ( !isset( $this->_schema['sql_without_blobs'] ) ){
00551                                 $this->_schema['sql_without_blobs'] = array();
00552                         }
00553                         if ( !isset($this->_schema['sql_with_blobs']) ){
00554                                 $this->_schema['sql_with_blobs'] = array();
00555                         }
00556                         
00557                         if ( defined('DATAFACE_USE_CACHE') and DATAFACE_USE_CACHE ){
00558                                 $cache_key_blobs = 'tables/'.$this->_sourceTable->tablename.'/relationships/'.$this->_name.'/sql/withblobs';
00559                                 $cache_key_noblobs = 'tables/'.$this->_sourceTable->tablename.'/relationships/'.$this->_name.'/sql/withoutblobs';
00560                                 // we are using the APC cache
00561                                 import( 'Dataface/Cache.php');
00562                                 $cache =& Dataface_Cache::getInstance();
00563                                 $this->_schema['sql_with_blobs'] = $cache->get($cache_key_blobs);
00564                                 $this->_schema['sql_without_blobs'] = $cache->get($cache_key_noblobs);
00565                         
00566                         }
00567 
00568 
00569                         
00570                         if ( !isset($this->_schema['sql_without_blobs'][$where][$sort]) or !isset($this->_schema['sql_with_blobs'][$where][$sort])){
00571                                 //if ( !$this->_schema['sql_without_blobs'][$where][$sort] ) $this->_schema['sql_without_blobs'] = array();
00572                                 //if ( !$this->_schema['sql_with_blobs'] ) $this->_schema['sql_with_blobs'] = array();
00573                                 
00574                         
00575                                 $parsed = unserialize(serialize($this->_schema['parsed_sql']));
00576                                 $parsed['column_names'] = array();
00577                                 $parsed['column_aliases'] = array();
00578                                 $parsed['columns'] = array();
00579                                 $wrapper = new SQL_Parser_wrapper($parsed, 'MySQL');
00580                                 $blobCols = array();
00581                                 
00582                                 $tableAliases = array();
00583                                 // For tables that have custom SQL defined we sub in its SQL
00584                                 // here.
00585                                 foreach ( array_keys($parsed['tables']) as $tkey ){
00586                                         if ( $parsed['tables'][$tkey]['type'] == 'ident' ){
00587                                                 $table =& Dataface_Table::loadTable($parsed['tables'][$tkey]['value']);
00588                                                 $proxyView = $table->getProxyView();
00589                                                 $tsql = $table->sql();
00590                                                 if ( isset($tsql) and !$proxyView){
00591                                                         $parsed['tables'][$tkey]['type'] =  'compiled_subselect';
00592                                                         $parsed['tables'][$tkey]['value'] = $tsql;
00593                                                         if ( !$parsed['tables'][$tkey]['alias'] ) $parsed['tables'][$tkey]['alias'] = $table->tablename;
00594                                                 } else if ( $proxyView ){
00595                                                         $parsed['tables'][$tkey]['value'] = $proxyView;
00596                                                         if ( !$parsed['tables'][$tkey]['alias'] ) $parsed['tables'][$tkey]['alias'] = $table->tablename;
00597                                                 }
00598                                                 $tableAliases[$table->tablename] = $parsed['tables'][$tkey]['alias'];
00599                                                 unset($table);
00600                                                 unset($tsql);
00601                                         }
00602                                 }
00603                                 $done = array();
00604                                 $dups = array();
00605                                 foreach ( $this->fields(true)  as $colname){
00606                                         // We go through each column in the query and add meta columns for length.
00607                                         
00608                                         //$table =& Dataface_Table::getTableTableForField($colname);
00609                                         list($tablename, $col) = explode('.',$colname);
00610                                         if ( $tablename != $this->getDomainTable() and Dataface_Table::loadTable($this->getDomainTable())->hasField($col) ){
00611                                                 // If this is a duplicate field we take the domain table value.
00612                                                 $dups[$col] = $this->getDomainTable();
00613                                                 continue;
00614                                         }
00615                                         if ( isset($done[$col]) ) $dups[$col] = $tablename;
00616                                         $done[$col] = true;
00617                                         
00618                                         $table =& Dataface_Table::loadTable($tablename);
00619                                         $alias = $wrapper->getTableAlias($tablename);
00620                                         if ( !$alias ){
00621                                                 $alias = $tablename;
00622                                         }
00623                                         $colname = $alias.'.'.$col;
00624                                         
00625                                         if ( $table->isPassword($col) ){
00626                                                 unset($table);
00627                                                 continue;
00628                                         }
00629                                         
00630                                         if ( $table->isBlob($col) ){
00631                                                 $blobCols[] = $colname;
00632                                         }
00633                                         if ( @$tableAliases[$tablename] ){
00634                                                 $tableAlias = $tableAliases[$tablename];
00635                                         } else {
00636                                                 $tableAlias = $tablename;
00637                                         }
00638 
00639                                         if ( in_array(strtolower($table->getType($col)), array('timestamp','datetime')) ){
00640                                                 if ( $tableAlias ) {
00641                                                         $colFull = '`'.$tableAlias.'`.`'.$col.'`';
00642                                                         //echo "Full";
00643                                                 }
00644                                                 else {
00645                                                         $colCull = '`'.$col.'`';
00646                                                         
00647                                                 }
00648                                                 $parsed['columns'][] = array('type'=>'compiled_func', 'table'=>null, 'value'=>"convert_tz(".$colFull.",'SYSTEM','".addslashes(df_utc_offset())."')", 'alias'=>$col);
00649                                         } else {
00650                                                 
00651                                                 $parsed['columns'][] = array('type'=>'ident', 'table'=>$tableAlias, 'value'=>$col, 'alias'=>null);
00652                                         }
00653                                         //$wrapper->addMetaDataColumn($colname);
00654                                         // Note:  Removed *length* metadata columns for now.. not hard to add
00655                                         // back.  Will wait to see if anyone screams!
00656                                         // Steve Hannah 071229
00657                                         unset($table);
00658                                         
00659                                 }
00660 
00661                                 
00662                                 if ( $where !== 0 ){
00663                                         $whereClause = $where;
00664                                         // Avoid ambiguous column error.  Any duplicate columns need to be specified.
00665                                         foreach ( $dups as $dcolname=>$dtablename ){
00666                                                 $whereClause = preg_replace('/([^.]|^) *`'.preg_quote($dcolname).'`/','$1 `'.$dtablename.'`.`'.$dcolname.'`', $whereClause);
00667                                         }
00668                                         $wrapper->addWhereClause($whereClause);
00669                                 } 
00670                                 if ( $sort !==0){
00671                                         $sortClause = $sort;
00672                                         foreach ( $dups as $dcolname=>$dtablename ){
00673                                                 $sortClause = preg_replace('/([^.]|^) *`'.preg_quote($dcolname).'`/','$1 `'.$dtablename.'`.`'.$dcolname.'`', $sortClause);
00674                                         }
00675                                         $wrapper->setSortClause($sortClause);
00676                                 }
00677                                 
00678                                 //$compiler = new SQL_Compiler(null, 'mysql');
00679                                 $compiler =& SQL_Compiler::newInstance('mysql');
00680                                 $compiler->version = 2;
00681                                 $this->_schema['sql_with_blobs'][$where][$sort] = $compiler->compile($parsed);
00682                                 
00683                                 
00684                                 foreach ($blobCols as $blobCol){
00685                                         $wrapper->removeColumn($blobCol);
00686                                 }
00687                                 $this->_schema['sql_without_blobs'][$where][$sort] = $compiler->compile($parsed);
00688 
00689                                 if ( defined('DATAFACE_USE_CACHE') and DATAFACE_USE_CACHE){
00690                                         $cache->set($cache_key_blobs, $this->_schema['sql_with_blobs']);
00691                                         $cache->set($cache_key_noblobs, $this->_schema['sql_without_blobs']);
00692                                 
00693                                 }
00694                                 
00695 
00696                         }
00697                         
00698                         /*
00699                          * Now that the SQL is generated we can call ourselves and the first
00700                          * case will now be initiated (ie: the generated sql will be returned).
00701                          */
00702                          $timeToGenerate = microtime_float()-$start;
00703                          if ( DATAFACE_DEBUG ){
00704                                 $this->app->addDebugInfo("Time to generate sql for relationship {$this->name} : $timeToGenerate");
00705                         }
00706                         return $this->getSQL($getBlobs, $where, $sort);
00707                 }
00708                 
00709                 
00710                 
00711                 
00712         }
00713         
00714         
00724         function getLabel(){
00725                 $action = $this->_sourceTable->getRelationshipsAsActions(array(), $this->_name);
00726                 return $action['label'];
00727         }
00728         
00738         function getSingularLabel(){
00739                 $action = $this->_sourceTable->getRelationshipsAsActions(array(), $this->_name);
00740                 if ( !isset($action['singular_label']) ){
00741                 
00742                         $label = $this->getLabel();
00743                         $action['singular_label'] = df_singularize($label);
00744                         
00745                 
00746                 }
00747                 
00748                 return $action['singular_label'];
00749                 
00750                 
00751                 
00752         }
00753         
00754         
00761         function supportsAddNew(){
00762                 if ( !isset($this->addNew) ){
00763                         $this->addNew = !( isset( $this->_schema['actions']['addnew'] ) and !$this->_schema['actions']['addnew'] );
00764                 }
00765                 return $this->addNew;
00766         }
00767         
00774         function supportsAddExisting(){
00775                 if ( !isset($this->addExisting) ){
00776                         $this->addExisting=true;
00777                         $fkeys = $this->getForeignKeyValues();
00778                         if ( count($fkeys) == 1 ){
00779                                 $this->addExisting = false;
00780                                 // If the relationship only has a single destination table
00781                                 // then it probably won't support adding existing records
00782                                 // Unless the foreign key allows null values - then it is 
00783                                 // possible that records that aren't currently part of a 
00784                                 // relationship can be added.
00785                                 /*
00786                                 $table =& Dataface_Table::loadTable($this->getDomainTable());
00787                                 $keys = array_keys($fkeys[$this->getDomainTable()]);
00788                                 foreach ($keys as $key){
00789                                         $field =& $table->getField($key);
00790                                         if ( strtoupper($field['Null']) == 'YES' ){
00791                                                 $this->addExisting=true;
00792                                                 break;
00793                                         }
00794                                 }
00795                                 */
00796                         }
00797                         if ( isset( $this->_schema['actions']['addexisting'] ) and !$this->_schema['actions']['addexisting']  ){
00798                                 $this->addExisting = false;
00799                         }
00800                         else if ( isset( $this->_schema['actions']['addexisting'] ) and $this->_schema['actions']['addexisting']  ) {
00801                                 $this->addExisting = true;
00802                         }
00803                 }
00804                 return $this->addExisting;
00805         }
00806         
00813         function supportsRemove(){
00814                 if (isset( $this->_schema['actions']['remove'] ) and !$this->_schema['actions']['remove'] ) return false;
00815                 return true;
00816         
00817         }
00818         
00819         function showTabsForAddNew(){
00820                 return ( @$this->_schema['prefs']['addnew']['show_tabs'] !== '0' );
00821         }
00822         
00823         
00824         
00828         function &getSourceTable(){
00829                 return $this->_sourceTable;
00830         }
00831         
00835         function &keys(){
00836                 if ( !isset($this->_keys) ){
00837                         $this->_keys = array();
00838                         $destTables =& $this->getDestinationTables();
00839                         foreach ( array_keys($destTables) as $x ){
00840                                 $table =& $destTables[$x];
00841                                 $tkeys = array_keys($table->keys());
00842                                 foreach ($tkeys as $tkey){
00843                                         $this->_keys[$tkey] = $table->getField($tkey);
00844                                 }
00845                         }
00846                 }
00847                 return $this->_keys;    
00848         }
00849         
00854         function &getDestinationTables(){
00855                 if ( !isset( $this->_destinationTables ) ){
00856                         $this->_destinationTables = array();
00857                         $this->_fieldTableLookup = array();
00858                         $columns =& $this->_schema['columns'];
00859                         $tables = array();
00860                         foreach ($columns as $column){
00861                                 list($tablename, $fieldname) = explode('.', $column);
00862                                 //$table =& $this->_sourceTable->getTableTableForField($this->_name.'.'.$column);
00863                                 
00864                                 $table =& Dataface_Table::loadTable($tablename);
00865                                 $this->_fieldTableLookup[$fieldname] =& $table;
00866                                 $tables[] =& $table;
00867                                 unset($table);
00868                         }
00869                         //$this->_destinationTables = array_unique($tables);
00870                         // For some reason array_unique does not seem to work with references in PHP 4
00871                         $this->_destinationTables = array();
00872                         $found = array();
00873                         foreach ( array_keys($tables) as $tableIndex ){
00874                                 if ( @$found[$tables[$tableIndex]->tablename] ) continue;
00875                                 
00876                                 $found[$tables[$tableIndex]->tablename] = true;
00877                                 $this->_destinationTables[] =& $tables[$tableIndex];
00878                         }
00879                 }
00880                 
00881                 return $this->_destinationTables;
00882         }
00883         
00889         function &getTable($field=null){
00890                 if ( $field === null ) return $this->_sourceTable;
00891                 if ( strpos($field, '.') !== false ){
00892                         list($tablename, $field) = explode('.', $field);
00893                         return $table = Dataface_Table::loadTable($tablename);
00894                 }
00895                 $this->getDestinationTables();
00896                 if ( isset($this->_fieldTableLookup[$field]) ) return $this->_fieldTableLookup[$field];
00897                 else {
00898                         $fields = preg_grep('/\.'.preg_quote($field,'/').'$/', $this->fields(true, true));
00899                         if ( !$fields ){
00900                                 $null = null;
00901                                 return $null;
00902                         } else {
00903                                 list($tablename) = explode('.', reset($fields));
00904                                 $this->_fieldTableLookup[$field] =& Dataface_Table::loadTable($tablename);
00905                                 //echo $tablename;
00906                                 return $this->_fieldTableLookup[$field];
00907                         }
00908                         
00909                 }
00910         }
00911         
00916         function getTableAlias($tableName){
00917                 if ( !isset($this->_schema) || !isset($this->_schema['parsed_sql']) || !is_array($this->_schema['parsed_sql']['table_names']) ) return null;
00918                 $idx = array_search($tableName, $this->_schema['parsed_sql']['table_names']);
00919                 if ( $idx !== false ){
00920                         return $this->_schema['parsed_sql']['table_aliases'][$idx];
00921                 }
00922                 return null;
00923                 
00924         }
00925         
00926         
00934         function &getField($fieldname){
00935                 if ( !isset($this->_cache[__FUNCTION__][$fieldname]) ){
00936                         if ( strpos($fieldname, '.') !== false ){
00937                                 list($tablename, $sfieldname) = explode('.', $fieldname);
00938                                 $table =& Dataface_Table::loadTable($tablename);
00939                                 $field =& $table->getField($sfieldname);
00940                                 
00941                         } else {
00942                                 // Check the domain table first
00943                                 $domainTable = Dataface_Table::loadTable($this->getDomainTable());
00944                                 $f =& $domainTable->getField($fieldname);
00945                                 if ( !PEAR::isError($f) ) return $field =& $f;
00946                                 else {
00947                                         
00948                                         // Domain table doesn't have a field by this name
00949                                         $fields = preg_grep('/\.'.preg_quote($fieldname,'/').'$/', $this->fields(true));
00950                                 
00951                                         if ( count($fields) > 0 ){
00952                                                 $lfieldname = reset($fields);
00953                                                 
00954                                                 list($tablename, $sfieldname) = explode('.',$lfieldname);
00955                                                 $table =& Dataface_Table::loadTable($tablename);
00956                                                 $field =& $table->getField($sfieldname);
00957                                                 
00958                                         } else {
00959                                                 $field = null;
00960                                         }
00961                                 }
00962                         }
00963                         $this->_cache[__FUNCTION__][$fieldname] =& $field;
00964                 }
00965                 return $this->_cache[__FUNCTION__][$fieldname];
00966         }
00967         
00968         
00974         function getDomainSQL(){
00975                 
00976                 $relationship =& $this->_schema;
00977                         // obtain reference to the relationship in question
00978                 
00979                         
00980                 // The 'domain_sql' attribute of a relationship defines the SQL select statement that
00981                 // is used to obtain the set of candidates for a relationship.  This can be specified 
00982                 // in the ini file using the __domain__ attribute of a relationship, or it can be parsed
00983                 // from the existiing 'sql' attribute.
00984                 if ( !isset( $relationship['domain_sql'] ) ){
00985                         import( 'SQL/Compiler.php');
00986                                 // compiles SQL tree structure into query strings
00987                         import( 'SQL/Parser/wrapper.php');
00988                                 // utility methods for dealing with SQL structures
00989                         $compiler = new SQL_Compiler();
00990                                 // the compiler we will use to generate the eventual SQL
00991                         $parsed_sql = unserialize(serialize($relationship['parsed_sql']));
00992                                 // we make a deep copy of the existing 'parsed_sql' structure that was 
00993                                 // created in the "readRelationshipsIniFile" method.  We deep copy, because
00994                                 // some of the methods in SQL_Parser_wrapper work directly on the 
00995                                 // datastructure - but we want to leave it unchanged.
00996                         $wrapper = new SQL_Parser_wrapper($parsed_sql);
00997                                 // create a new wrapper to operate on the sql data structure.
00998                         $wrapper->removeWhereClausesWithTable( $this->_sourceTable->tablename);
00999                         $wrapper->removeJoinClausesWithTable( $this->_sourceTable->tablename);
01000                                 // We remove all Where and Join clauses that use columns from the current table.
01001                                 // This is because portions of the sql pertaining to the current table
01002                                 // likely represent specifications within the domain to mark that an 
01003                                 // element of the domain is related to the current table.
01004                         $wrapper->removeWhereClausesWithPattern( '/\$\w+/' );
01005                         $wrapper->removeJoinClausesWithPattern( '/\$\w+/' );
01006                                 // Similarly we need to remove any clauses containing variables which
01007                                 // get filled in by the current table.  The rationale is the same as
01008                                 // for removing clauses pertaining to the current table.
01009                         $fkVals = $this->getForeignKeyValues();
01010                                 // We obtain the foreign key values for this relationship because they
01011                                 // will help us to decide which columns in the remaining query are 
01012                                 // helpful for obtaining the domain.
01013                         $uselessTables = array();
01014                                 // will hold list of tables that we don't need
01015                         $fkTables = array_keys($fkVals);
01016                                 // list of tables that are involved in foreign key relationships in this
01017                                 // relationship.
01018                         foreach ($fkVals as $fkTable => $fkFields){
01019                                 $foundVal = 0;
01020                                 $foundLink = 0;
01021                                         // keep track of which tables actually have real values assigned.
01022                                 foreach ($fkFields as $fieldVal){
01023                                         //if ( !preg_match('/^__(\w+)_auto_increment__$/', $fieldVal) ){
01024                                         //      // A field with a value of the form __Tablename__auto_increment__ is a placeholder
01025                                         //      // for an auto generated id.  If the only values specified for a table are placeholders
01026                                         //      // then that table is pretty much useless as a domain query... it can be eliminated.
01027                                         //      $foundVal++;
01028                                         //      
01029                                         //      
01030                                         //}
01031                                         if ( is_scalar($fieldVal) and strpos($fieldVal, '$') === 0 ){
01032                                                 // This table is linked directly to the current table... hence it is only a join
01033                                                 // table.
01034                                                 $foundLink++;
01035                                         }
01036                                 }
01037                                 if ( $foundLink){
01038                                         // no real valus found.. mark table as useless.
01039                                         $uselessTables[] = $fkTable;
01040                                 }
01041                                 
01042                         }
01043                         
01044                         
01045                         
01046                         foreach ($uselessTables as $table_name){
01047                                 // Remove all useless tables from the query's where and join clauses.
01048                                 $wrapper->removeWhereClausesWithTable( $table_name );
01049                                 $wrapper->removeJoinClausesWithTable( $table_name );
01050                                 $wrapper->removeColumnsFromTable( $table_name );
01051                         }
01052                         
01053                         $domain_tables = array_diff($relationship['selected_tables'], $uselessTables);
01054                         if ( !$domain_tables ) $domain_tables = $relationship['selected_tables'];
01055                         
01056                         $table_ranks = array();
01057                         foreach ($this->_schema['columns'] as $col){
01058                                 list($tname) = explode('.',$col);
01059                                 if ( !isset($table_ranks[$tname]) ) $table_ranks[$tname] = 0;
01060                                 $table_ranks[$tname]++;
01061                         }
01062 
01063                         $high = null;
01064                         $high_score = 0;
01065                         foreach ( $domain_tables as $dt ){
01066                                 if ( $table_ranks[$dt] > $high_score ){
01067                                         $high = $dt;
01068                                         $high_score = $table_ranks[$dt];
01069                                 }
01070                         }
01071                         $domain_tables = array($high);
01072                                 
01073                         
01074                         if ( count($domain_tables) !== 1 ){
01075                                 return PEAR::raiseError("Error calculating domain tables for relationship '".$this->_name."'.  Selected tables are {".implode(',',$relationship['selected_tables'])."} and Useless tables are {".implode(',', $uselessTables)."}.",null,null,null,1);
01076                         }
01077                         $relationship['domain_table'] = array_pop($domain_tables);
01078                         
01079                         
01080                         $wrapper->packTables(/*$relationship['selected_tables']*/);
01081                                 // Previous steps have only eliminated useless tables with respect to query
01082                                 // parameters.  There may still be some tables listed in the query that don't
01083                                 // offer anything.  Notice that we pass the list of selected tables to this
01084                                 // method to indicate that tables whose columns are selected need to be there
01085                                 // and should be left intact.
01086                         $relationship['domain_sql'] = $compiler->compile($parsed_sql);
01087                 }
01088                 return $relationship['domain_sql'];
01089                 
01090         
01091         }
01092         
01100         function getDomainTable(){
01101                 if ( !isset($this->domainTable) ){
01102                         $res = $this->getDomainSQL();
01103                         if ( PEAR::isError($res) ){
01104                                 return $res;
01105                         }
01106                         $this->domainTable =  $this->_schema['domain_table'];
01107                 }
01108                 return $this->domainTable;
01109         }
01110         
01111         
01127         function getForeignKeyValues($values = null, $sql = null, $record = null){
01128                 if ( is_object($record) ) $record_id = $record->getId();
01129                 if ( !isset($values) and !isset($sql) and !isset($record) and isset($this->_cache[__FUNCTION__]) ){
01130                         return $this->_cache[__FUNCTION__];
01131                 }
01132                 if ( !isset($values) and !isset($sql) and is_object($record) and isset($this->_cache[__FUNCTION__.$record_id]) ){
01133                         return $this->_cache[__FUNCTION__.$record_id];
01134                 }
01135                 // Strategy:
01136                 // ----------
01137                 // 1. Label all fields involved in the foreign key so that fields that are equal have the
01138                 //    same label.  Eg: In the query:
01139                 //              select * 
01140                 //                      from Students 
01141                 //                      inner join Student_Courses 
01142                 //                              on Students.id = Student_Courses.studentid
01143                 //                      inner join Courses
01144                 //                              on Student_Courses.courseid = Courses.id
01145                 //              where
01146                 //                      Students.id = '$id'
01147                 //
01148                 //      In the above query Students.id and Student_Course.studentid would have the same label, and
01149                 //  Student_Courses.courseid and Courses.id would have the same label.
01150                 //  ie: Label(Students.id) = Label(Student_Courses.studentid) ^ Label(Student_Courses.courseid) = Label(Courses.id)
01151                 //
01152                 // 2. Assign values for each label.  All fields with a particular label have the same value.
01153                 //      In the above query, we would have:
01154                 //              Value(Label(Students.id)) = '$id'
01155                 //              **Note from above that Label(Students.id)=Label(Student_Courses.studentid) so their values are also equal.
01156                 //
01157                 // 3. For labels without a value find out if one of the fields assuming that label is an auto-increment field. If so
01158                 //        we assign the special value '__Tablename__auto_increment__' where "Tablename" is the name of the table  whose
01159                 //        field is to be auto incremented.
01160                 //
01161                 // 4. Collect the the values in a structure so that we can lookup the values of any particular field in any particular
01162                 //    table easily.  Return this structure.
01163                 $relationship =& $this->_schema;
01164                 
01165                 if ( $sql !== null ){
01166                         // An SQL query was specified as a parameter, we parse this and use the resulting data structure
01167                         // for the rest of the computations.
01168                         if ( is_string($sql) ) {
01169                                 $parser = new SQL_Parser(null,'MySQL');
01170                                 $sql = $parser->parse($sql);
01171                         }
01172                         $select =& $sql;
01173                 } else {
01174                         // We use the 'parsed_sql' entry in the relationship as the basis for our dissection.
01175                         $select =& $relationship['parsed_sql'];
01176                 }
01177                 
01178                 
01179                 // Build equivalence classes for column names.
01180                 $labels = array();
01181                 $vals = array();
01182                 $this->_makeEquivalenceLabels($labels, $vals, $select);
01183                 
01184                 // Fill in some default values
01185                 
01186                 if ( is_array($values) ){
01187                         foreach ($values as $field_name => $field_value ){
01188                                 
01189                                 if ( !$field_value ) continue;
01190                                         // we don't want empty and null values to act as defaults because they 
01191                                         //  tend to screw things up when we are adding related records.
01192                                         
01193                                 if ( isset( $labels[$field_name] ) ) $label = $labels[$field_name];
01194                                 else {
01195                                         $label = $field_name;
01196                                         $labels[$field_name] = $label;
01197                                 }
01198                                 
01199                 
01200                                 $vals[$label] = $field_value;
01201                         
01202                         }
01203                 }
01204                 
01205         
01206                 
01207                 // next we need to find 'circular links'.  Ie: There may be columns that are only specified to be equal to each other.  Most of the
01208                 // time this means that one of the fields is an auto increment field that will be automatically filled in.  We need to insert
01209                 // a special value (in this case), so that we know this is the case.
01210                 foreach ( $labels as $field_name=>$label ){
01211                         if ( !isset( $vals[$label] ) ){
01212                                 $field =& Dataface_Table::getTableField($field_name);
01213                                 $table_auto_increment = null;
01214                                 foreach ( $labels as $auto_field_name=>$auto_label ){
01215                                         if ( $auto_label == $label ){
01216                                                 $auto_field =& Dataface_Table::getTableField($auto_field_name);
01217                                                 if ( $auto_field['Extra'] == 'auto_increment' ){
01218                                                         list($table_auto_increment) = explode('.', $auto_field_name);
01219                                                         unset($auto_field);
01220                                                         break;
01221                                                 }
01222                                                 unset($auto_field);
01223                                         }
01224                                 }
01225                                 if ( isset($table_auto_increment) ){
01226                                         //list($table) = explode('.', $field_name);
01227                                         $vals[$label] = "__".$table_auto_increment."__auto_increment__";
01228                                 } else {
01229                                         $vals[$label] = new Dataface_Relationship_ForeignKey($this, $labels, $label);
01230                                 }
01231                                 unset($field);
01232                         }
01233                 }
01234                         
01235                 $table_cols = array();
01236                 foreach ( $labels as $field_name=>$label){
01237                         $fieldArr =& Dataface_Table::getTableField($field_name);
01238                         list( $table, $field ) = explode('.', $field_name);
01239                         if ( !$table ) continue;
01240                         if ( !isset( $table_cols[$table] ) ) $table_cols[$table] = array();
01241                         $table_cols[$table][$field] = ( is_scalar(@$vals[$label]) and $record !== null  and !preg_match('/(blob|binary)/', strtolower($fieldArr['Type'])) ) ? $record->parseString(@$vals[$label]) : @$vals[$label];
01242                         unset($fieldArr);
01243                 }
01244                 
01245                 // make sure that each table at least sets all of the mandatory fields.
01246                 foreach ( $table_cols as $table=>$cols ){
01247                         $tableObject =& Dataface_Table::loadTable($table);
01248                         foreach ( array_keys($tableObject->mandatoryFields()) as $key ){
01249                                 if ( !isset( $cols[$key] ) ){
01250                                         $this->errors[] = PEAR::raiseError(DATAFACE_TABLE_RELATED_RECORD_REQUIRED_FIELD_MISSING_ERROR, null,null,null, "Could not generate SQL to add new record to relationship '".$this->_name."' because not all of the required fields have values.  In particular, the field '$key' of table '$table' is missing but is a key of the table.");
01251                                 }
01252                         }
01253                         unset($tableObject);
01254                 }
01255                 
01256                 
01257                 if ( !isset($values) and !isset($sql) and !isset($record) ){
01258                         $this->_cache[__FUNCTION__] = $table_cols;
01259                 }
01260                 if ( !isset($values) and !isset($sql) and is_object($record) ){
01261                         $this->_cache[__FUNCTION__.$record_id] = $table_cols;
01262                 }
01263 
01264                 return $table_cols;
01265         
01266         }
01267         
01268         function isNullForeignKey(&$val){
01269                 return is_a($val, 'Dataface_Relationship_ForeignKey');
01270         }
01271         
01281         function getDistance($table){
01282                 $fkeys =& $this->getForeignKeyValues();
01283                 if ( !isset($fkeys[$table]) ) return 999;
01284                 foreach ( $fkeys[$table] as $key=>$value ){
01285                         if ( is_scalar($value) and strlen($value) > 0 and $value{0} == '$' ) return 1;
01286                         
01287                 }
01288                 return 2;
01289         }
01290         
01291         function getAddExistingFilters(){
01292                 $this->_addExistingFilters = null;
01293                 if ( !isset($this->_addExistingFilters) ){
01294                         $this->_addExistingFilters = array();
01295                         $fkeys = $this->getForeignKeyValues();
01296                         if ( count($fkeys) == 1 ){
01297                                 foreach ( $fkeys[$this->getDomainTable()] as $fname=>$fval){
01298                                         $this->_addExistingFilters[$fname] = '=';
01299                                 }
01300                         }
01301                 }
01302                 return $this->_addExistingFilters;
01303                         
01304         }
01305         
01314         function getAddableValues(&$record, $filter=array()){
01315                 $filter = array_merge($this->getAddExistingFilters(), $filter);
01316                 $t =& Dataface_Table::loadTable($this->getDomainTable());
01317                 $r =& $this->_schema;
01318                 $tkey_names = array_keys($t->keys());
01319                 if ( !is_a($record, 'Dataface_Record') ){
01320                         throw new Exception("Attempt to call getAddableValues() without providing a Dataface_Record as context.", E_USER_ERROR);
01321                         
01322                 }
01323                 if ( ( $res = $record->callDelegateFunction($this->_name.'__'.__FUNCTION__) ) !== null ){
01324                         return $res;
01325                 }
01326         
01327                 if ( isset( $this->_schema['vocabulary']['existing'] ) ){
01328                         // A custom vocabulary has been specified in the relationships.ini
01329                         // file for this relationship.
01330                         $options_temp = $t->getValuelist($r['vocabulary']['existing']);
01331                         $options = array();
01332                         foreach (array_keys($options_temp) as $optkey){
01333                                 if ( strpos($optkey, '=') === false ){
01334                                         $options[$tkey_names[0].'='.urlencode($optkey)] = $options_temp[$optkey];
01335                                 } else {
01336                                         $options[$optkey] = $options_temp[$optkey];
01337                                 }
01338                         }
01339                 } else {
01340                         // No custom vocabulary has been specified.  Let's do our best
01341                         // to figure out what the vocabulary should be.
01342                         //$fkeys = $this->getForeignKeyValues(null,null,$record);
01343                         $table = $this->getDomainTable();
01344                         if ( isset( $fkeys[$table] ) ){
01345                                 $query = $fkeys[$table];
01346                                 foreach ($query as $key=>$val){
01347                                         if ( $this->isNullForeignKey($val) or strpos($val,'$')===0 or $val == '__'.$table.'__auto_increment__'){
01348                                                 unset($query[$key]);
01349                                         }
01350                                 }
01351                         } else {
01352                                 $query = array();
01353                         }
01354                         $query = array_merge($filter, $query);
01355                         $qt = new Dataface_QueryTool($table, $this->_sourceTable->db, $query);
01356                         $options = $qt->getTitles(true,false,true/*Ignores 250 record limit*/);
01357                 }
01358                 
01359                 return $options;
01360         
01361         }
01362         
01363         
01364         
01374          function _makeEquivalenceLabels(&$labels, &$values, &$sql_data){
01375                 
01376                 $roots = array();
01377                 if ( isset( $sql_data['where_clause'] ) and is_array( $sql_data['where_clause'] )){
01378                         $roots[] =& $sql_data['where_clause'];
01379                 }
01380                 if ( isset( $sql_data['table_join_clause'] ) and is_array( $sql_data['table_join_clause']) ){
01381                         foreach ( $sql_data['table_join_clause'] as $clause ){
01382                                 if ( is_array($clause) ){
01383                                         $roots[] = $clause;
01384                                 }
01385                         }
01386                 }
01387                 $parser_wrapper = new SQL_Parser_wrapper($sql_data);
01388                 foreach ($roots as $root){
01389                         $this->_makeEquivalenceLabels_rec($labels, $values, $root, $parser_wrapper);
01390                 }
01391         
01392         
01393         }
01394         
01395         
01405         function _makeEquivalenceLabels_rec( &$labels, &$values, &$root, &$parser_wrapper){
01406                 
01407                 if ( isset( $root['op'] ) ){
01408                         if ( $root['op'] == '=' ){
01409                                 $label = '';
01410                                 $value = null;
01411                                 $fields = array();
01412                                 $existingLabels = 0;
01413                                 $oldLabel = null;
01414                                         // keep track of the number of existing labels.
01415                                 foreach ( array('arg_1','arg_2') as $arg ){
01416                                         switch ($root[$arg]['type']){
01417                                                 case 'ident':
01418                                                         $field_name = Dataface_Table::absoluteFieldName(
01419                                                                                                 $parser_wrapper->resolveColumnName($root[$arg]['value']), 
01420                                                                                                 $parser_wrapper->_data['table_names']
01421                                                         
01422                                                         );
01423                                                         if ( !is_string($field_name) ){
01424                                                                 echo "Field name is not a string.";
01425                                                                 echo get_class($field_name);
01426                                                                 if ( is_a($field_name, 'PEAR_Error') ) echo $field_name->toString();
01427                                                         }
01428                                                         $fields[] = $field_name;
01429                                                         
01430                                                         // If this column already has a label, then we use it as the common label
01431                                                         if ( isset( $labels[$field_name] ) ) {
01432                                                                 $existingLabels++;
01433                                                                 if ( $existingLabels > 1 ){
01434                                                                         // If the other column already had a label, then we keep track of it
01435                                                                         $oldLabel = $label;
01436                                                                 }
01437                                                                 $label = $labels[$field_name];
01438                                                         }
01439                                                                         
01440                                                         break;
01441                                                 
01442                                                 case 'text_val':
01443                                                 case 'int_val':
01444                                                 case 'real_val':
01445                                                         $value = $root[$arg]['value'];
01446                                                         break;
01447                                         }
01448                                                         
01449                                 }
01450                                 // Assert (count($fields) == 1 or count($fields) == 2)
01451                                 // Assert (count($fields) == 1 => $value !== null )
01452                                 // Assert (count($fields) == 2 => $value === null )
01453                                 
01454                                 $label = ( $label ? $label : $fields[0] );
01455                                         // Obtain the label for these columns.  If there are 2 columns, they must have the same label
01456                                 foreach ( $fields as $field ){
01457                                         if ( !isset( $labels[$field] ) ) $labels[$field] = $label;
01458                                 }
01459                                 
01460                                 // Now we have to change labels of all fields that contained the old label.
01461                                 if ( $oldLabel !== null ){
01462                                         foreach ( $labels as $field_name=>$field_label ) {
01463                                                 if ( $field_label == $oldLabel ){
01464                                                         $labels[$field_name] = $label;
01465                                                 }
01466                                         }
01467                                 }
01468                                 
01469                                 // Now we update the value for the label if there is a value.
01470                                 if ( $value !== null ){
01471                                         $values[$label] = $value;
01472                                 }
01473                         }
01474                 }
01475                 
01476                 foreach ( $root as $key=>$value ){
01477                         if ( is_array($value) ){
01478                                 $this->_makeEquivalenceLabels_rec($labels, $values, $value, $parser_wrapper);
01479                         }
01480                 }
01481         }
01482         
01483         
01504         function getOrderColumn(){
01505 
01506                 $order_col = ( ( isset( $this->_schema['metafields']['order']) and $this->_schema['metafields']['order'] ) ? $this->_schema['metafields']['order'] : null );
01507                 if ( !isset($order_col) ){
01508                         return PEAR::isError('Attempt to sort relationship "'.$this->_name.'" but no order column was defined.');
01509                 }
01510                 return $order_col;
01511         }
01512         
01513         
01524         function isParentRelationship(){
01525                 return ( isset( $this->_schema['meta']['class']) and strtolower($this->_schema['meta']['class']) == 'parent');
01526         }
01527         
01539         function isChildrenRelationship(){
01540                 return ( isset($this->_schema['meta']['class']) and strtolower($this->_schema['meta']['class']) == 'children');
01541         }
01542         
01543         
01555         function isOneToMany(){
01556                 if ( isset($this->_cache[__FUNCTION__]) ) return $this->_cache[__FUNCTION__];
01557                 $this->_cache[__FUNCTION__] = (count($this->getForeignKeyValues()) == 1);
01558                 return $this->_cache[__FUNCTION__];
01559         }
01560         
01570         function isManyToMany(){
01571                 return !$this->isOneToMany();
01572         }
01573                                 
01574                         
01575 
01576 }
01577 
01583 class Dataface_Relationship_ForeignKey {
01584         var $fields = array();
01585         var $relationship = null;
01586         
01592         function Dataface_Relationship_ForeignKey(&$relationship, $labels, $label){
01593                 $this->relationship =& $relationship;
01594                 foreach ( $labels as $field=>$l ){
01595                         if ( $l==$label ) $this->fields[] = $field;
01596                 }
01597         }
01598         
01604         function getFields(){
01605                 return $this->fields;
01606         }
01607         
01619         function getFurthestField(){
01620                 $d = 0;
01621                 $f = null;
01622                 foreach ($this->fields as $field){
01623                         list($table) = explode('.', $field);
01624                         $td = $this->relationship->getDistance($table);
01625                         if ( $td > $d ){
01626                                 $d = $td;
01627                                 $f = $field;
01628                         }
01629                 }
01630                 return $f;
01631         }
01632         
01633         
01634 }
01635 
 All Data Structures Namespaces Files Functions Variables Enumerations