![]() |
Xataface 2.0
Xataface Application Framework
|
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