Xataface  2.0alpha2
Xataface Application Framework
 All Data Structures Namespaces Files Functions Variables Groups Pages
Relationship.php
Go to the documentation of this file.
1 <?php
2 /*-------------------------------------------------------------------------------
3  * Xataface Web Application Framework
4  * Copyright (C) 2005-2008 Web Lite Solutions Corp (shannah@sfu.ca)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *-------------------------------------------------------------------------------
20  */
21 
39 
40  /*
41  * The name of the relationship.
42  */
43  var $_name;
44 
45  /*
46  * Reference to the source table of the relationship.
47  */
49 
50  /*
51  * Arrayh of references to the destination tables of the relationship.
52  */
54 
55  /*
56  * An associative array mapping field names to tables.
57  */
59 
60  /*
61  * A Descriptor array to describe the relationship.
62  */
63  var $_schema;
64 
65  /*
66  * The key fields of the relationship.
67  */
68  var $_keys;
69 
70 
77  var $_sql_generated = array();
78 
79  var $_permissions = array();
80 
81  var $app;
82 
87  var $_cache=array();
88 
89  var $addNew;
91 
92 
93 
104  function Dataface_Relationship($tablename, $relationshipName, &$values){
105  $this->app =& Dataface_Application::getInstance();
106  $this->_name = $relationshipName;
107  $this->_sourceTable =& Dataface_Table::loadTable($tablename);
108  $this->_schema = array();
109  $res = $this->_init($values);
110  if ( PEAR::isError($res) ){
111  throw new Exception($res->getMessage());
112  }
113 
114  if ( !isset($this->_schema['permissions']) ){
116  $this->_schema['permissions'] = Dataface_PermissionsTool::getRolePermissions($app->_conf['default_relationship_role']);
117  }
118  $this->_permissions =& $this->_schema['permissions'];
119 
120  }
121 
131  function &fields($includeAll=false, $includeTransient=false){
132  if ( !$includeAll ){
133 
134  return $this->_schema['columns'];
135  } else {
136  if ( !isset($this->_cache[__FUNCTION__][intval($includeAll)][intval($includeTransient)]) ){
137  $tables =& $this->getDestinationTables();
138  $out = array();
139  $used_names = array();
140  foreach ( array_keys($tables) as $i ){
141  foreach ( $tables[$i]->fields(false,true, $includeTransient) as $fld ){
142  if ( @$fld['grafted'] and @$used_names[$fld['Field']] ) continue;
143  // We don't want grafted fields overwriting valid fields in
144  // other tables.
145  $out[] = $tables[$i]->tablename.'.'.$fld['Field'];
146  $used_names[ $fld['Field'] ] = 1;
147  }
148  }
149  $this->_cache[__FUNCTION__][intval($includeAll)][intval($includeTransient)] = $out;
150  }
151  return $this->_cache[__FUNCTION__][intval($includeAll)][intval($includeTransient)];
152  }
153  }
154 
155 
156  function getCardinality(){
157  if ( isset($this->_schema['__cardinality__']) ){
158  return $this->_schema['__cardinality__'];
159  } else {
160  return '*';
161  }
162  }
163 
164 
165 
166 
190  function hasField($fieldname, $checkAll=false, $includeTransient=false){
191  if ( strpos($fieldname,'.') === false ){
192  if (in_array($fieldname, $this->_schema['short_columns'] ) ) return true;
193  if ( ($checkAll or $includeTransient) and preg_grep('/\.'.preg_quote($fieldname, '/').'$/', $this->fields($checkAll, $includeTransient))){
194  return true;
195  }
196  return false;
197  } else {
198  return in_array( $fieldname, $this->fields($checkAll, $includeTransient) );
199  }
200  }
201 
202 
203 
214  function _init(&$rel_values){
215  $r =& $this->_schema;
216  /*
217  * First we will check the array for parameters. Parameters might include
218  * default values for new records in the relationship - or for existing records
219  * in the relationship.
220  */
221  foreach ($rel_values as $key=>$value){
222  if ( strpos($key,":") !== false ){
223  $path = explode(":", $key);
224  $len = count($path);
225 
226  $val =& $r;
227 
228  for ($i=0; $i<$len; $i++ ){
229  //if (!isset($val[$path[$i]]) ){
230  if ( $i == $len -1 ) $val[$path[$i]] = $value;
231  else {
232  if ( !isset($val[$path[$i]]) ) {
233  $val[$path[$i]] = array();
234  }
235  $valTemp =& $val;
236  unset($val);
237  $val =& $valTemp[$path[$i]];
238  unset($valTemp);
239  }
240  //}
241  }
242 
243  }
244  }
245  if ( isset($rel_values['__cardinality__']) ) $r['__cardinality__'] = $rel_values['__cardinality__'];
246 
247  if ( array_key_exists( '__sql__', $rel_values ) ){
248  // The relationship was defined using an SQL statement
249  $r['sql'] = $rel_values['__sql__'];
250  $matches = array();
251  /* MOD START 051021 - shannah@sfu.ca - Using PEAR SQL parser package instead of regexes. */
252  $parser = new SQL_Parser();
253  $struct = $parser->parse($r['sql']);
254  if ( PEAR::isError($struct) ){
255  error_log($struct->toString()."\n".implode("\n", $struct->getBacktrace()));
256  throw new Exception("Failed to parse relationship SQ. See error log for details.", E_USER_ERROR);
257 
258  }
259  $parser_wrapper = new SQL_Parser_wrapper($struct);
260  $parser_wrapper->fixColumns();
261  $r['parsed_sql'] =& $struct;
262  $r['tables'] = $struct['table_names'];
263  $r['columns'] = $struct['column_names'];
264  foreach ($struct['columns'] as $colstruct){
265  if ( $colstruct['type'] == 'ident' and @$colstruct['alias'] ){
266  $r['aliases'][$colstruct['value']] = $colstruct['alias'];
267  }
268  }
269  $temp = array();
270  foreach ( $r['columns'] as $column ){
271  $col = $parser_wrapper->resolveColumnName($column);
272  if (preg_match('/\.$/', $col) ){
273  $col = $col.'*';
274  }
275  $temp[] = $col;
276  }
277  $r['columns'] = $temp;
278  unset($struct);
279  unset($temp);
280  /* MOD END 051021 */
281 
282 
283  } else {
284  // The relationship was not defined using SQL. It merely defines match columns
285  // and select columns
286 
287  $select = '*';
288  // Default selection to all columns
289 
290  if ( array_key_exists( '__select__', $rel_values ) ){
291  // __select__ should be comma-delimited list of column names.
292  $select = $rel_values['__select__'];
293  }
294 
295  $tables = array();
296  // stores list of table names involved in this relation
297 
298  // Let's generate an SQL query based on the information given
299  //
300 
301  $from = 'from ';
302  // from portion of generated sql query
303  $where = 'where ';
304  // where portion of generated sql query
305 
306  foreach ( $rel_values as $c1 => $c2 ){
307  // Iterate through all of the match columns of the relationship
308 
309  if ( in_array( $c1, array('__sql__', '__select__', '__sort__','__domain__','__cardinality__') ) ) continue;
310  // special flags like sql, select, and sort are not column matchings.. we skip them.
311  if ( strpos( $c1, ":" ) !== false ) continue;
312  // This is a parameter so we ignore it.
313 
314 
315 
316  // get the paths of the related columns
317  // Match columns may be given as Table_name.Column_name dotted pairs... we need to separate
318  // the tablenames from the column names.
319  $p1 = explode( '.', $c1);
320  $p2 = explode('.', $c2);
321 
322  if ( count( $p1 ) == 1 ){
323  // Only column name is given.. we assume the tablename is the current table.
324  array_unshift( $p1, $this->_sourceTable->tablename );
325  }
326  if ( count($p2) ==1 ){
327  // Only the column name is given for rhs... assume current table name.
328  array_unshift( $p2, $this->_sourceTable->tablename );
329  }
330 
331  // add the tables to our table array... we omit the current table though.
332  if ( !in_array( $p1[0], $tables ) && $p1[0] != $this->_sourceTable->tablename) $tables[] = $p1[0];
333  if ( !in_array( $p2[0], $tables ) && $p2[0] != $this->_sourceTable->tablename) $tables[] = $p2[0];
334 
335  // Simplify references to current table to be replaced by variable value
336  if( $p1[0] == $this->_sourceTable->tablename ){
337  $lhs = "'\$$p1[1]'";
338  } else {
339  $lhs = "$p1[0].$p1[1]";
340  }
341 
342  if ( $p2[0] == $this->_sourceTable->tablename ){
343  if ( strpos($p2[1], '$')===0){
344  $var = '';
345  } else {
346  $var = '$';
347  }
348  $rhs = "'".$var.$p2[1]."'";
349  } else {
350  $rhs = "$p2[0].$p2[1]";
351  }
352 
353  // append condition to where clause
354  $where .= strlen($where) > 6 ? ' and ' : '';
355  $where .= "$lhs=$rhs";
356  }
357 
358 
359 
360  foreach ($tables as $table){
361  $from .= $table.', ';
362  }
363 
364  $from = substr( $from, 0, strlen($from)-2);
365 
366  $r['sql'] = "select $select $from $where";
367 
368 
369 
370  /* MOD START 051021 - shannah@sfu.ca - Using PEAR SQL parser package instead of regexes. */
371  $parser = new SQL_Parser(null, 'MySQL');
372 
373  $struct = $parser->parse($r['sql']);
374  $parser_wrapper = new SQL_Parser_wrapper($struct, 'MySQL');
375  $parser_wrapper->fixColumns();
376  $r['parsed_sql'] =& $struct;
377  $r['tables'] = $struct['table_names'];
378  $r['columns'] = $struct['column_names'];
379  $temp = array();
380  foreach ( $r['columns'] as $column ){
381  $col = $parser_wrapper->resolveColumnName($column);
382  if (preg_match('/\.$/', $col) ){
383  $col = $col.'*';
384  }
385  $temp[] = $col;
386  }
387  $r['columns'] = $temp;
388  unset($struct);
389 
390  }
391 
392  $res = $this->_normalizeColumns();
393  if ( PEAR::isError($res) ) return $res;
394  $r['short_columns'] = array();
395  foreach ($r['columns'] as $col ){
396  list($table,$col) = explode('.', $col);
397  $r['short_columns'][] = $col;
398  }
399 
400  }
401 
406  function _normalizeColumns(){
407 
408  $rel =& $this->_schema;
409 
410 
411  $tables =& $rel['tables'];
412  $selected_tables = array();
413  $rel['selected_tables'] =& $selected_tables;
414  // contains a list of the tables that actually have values returned in the select statement
415 
416  $len = sizeof($rel['columns']);
417  for ($i=0; $i<sizeof($rel['columns']); $i++){
418  $matches = array();
419 
420  // Case 1: This column has a wildcard. eg: Profiles.* or just simply *
421  if ( preg_match('/^(\w+\.){0,1}\*$/', $rel['columns'][$i], $matches) ){
422  // we are returning all columns from a particular table
423  if ( isset( $matches[1]) ){
424  $table = $matches[1];
425 
426  $temp_tables = array();
427  $temp_tables[] = substr($table, 0, strlen($table)-1);
428  } else {
429  $temp_tables = $tables;
430  }
431  $temp_columns = array();
432 
433  // go through each table requested, and extract its columns
434  foreach ($temp_tables as $table){
435 
436  $table_table =& Dataface_Table::loadTable($table, $this->_sourceTable->db);
437  if ( PEAR::isError($table_table) ){
438  $table_table->addUserInfo("Failed to load table for table '$table'");
439  return $table_shema;
440  }
441 
442  $fields = array_keys($table_table->fields());
443  for ($j=0; $j<count($fields); $j++){
444  $fields[$j] = $table.'.'.$fields[$j];
445  }
446 
447  $temp_columns = array_merge($temp_columns, $fields);
448  if ( !in_array( $table, $selected_tables ) ){
449  $selected_tables[] = $table;
450  }
451  }
452 
453  // We need to add all of the columns that we found to the persistent columns list for this relationship.
454  // But we need to remove the entry with the '*' because it is meaningless from here on out.
455  // Case A: We are at the first element
456  if ( $i==0 ){
457  $rel['columns'] = array_merge( $temp_columns, array_slice( $rel['columns'], 1, $len-1) );
458 
459  // Case B: We are at the last element
460  } else if ( $i==$len-1 ){
461  $rel['columns'] = array_merge( $rel['columns'], $temp_columns );
462  $len = sizeof($rel['columns']);
463  $i = $len-1;
464  // increment the counter so that we don't repeat all of the ones we just created.
465 
466  // Case C: We are somewhere in the middle of the columns list
467  } else {
468 
469  $rel['columns'] = array_merge( array_slice( $rel['columns'], 0, $i),
470  $temp_columns,
471  array_slice( $rel['columns'], $i+1, $len-$i-1) );
472  $len = sizeof($rel['columns']);
473  $i = $i + sizeof($temp_columns) -1;
474  // increment the counter so that we don't repeat all of the ones we just created.
475  }
476  unset($table_table);
477  // to keep us from doing damage
478 
479 
480  // Case 2: This is a fully qualified column address.
481  } else if ( preg_match('/^(\w+)\.(\w+)$/', $rel['columns'][$i], $matches) ) {
482 
483  $table = $matches[1];
484  $column = $matches[2];
485  $table_table =& Dataface_Table::loadTable($table, $this->_sourceTable->db);
486  if ( PEAR::isError($table_table) ){
487  $table_table->addUserInfo("Failed to load table for table '$table'");
488  error_log($table_table->toString()."\n".implode("\n", $table_table->getBacktrace()));
489  throw new Exception("Failed to validate column ".$rel['columns'][$i].". See error log for details.", E_USER_ERROR);
490 
491  }
492 
493  $selected_tables[] = $table;
494  // this column is ok and already absolute.
495 
496  // Case 3: This column is specified by only a column name - needs to be made absolute.
497  } else {
498  // it is just a single column declaration
499  $name = Dataface_Table::absoluteFieldName($rel['columns'][$i], $tables, $this->_sourceTable->db);
500  if ( PEAR::isError($name) ){
501  $name->addUserInfo("Failed get absolute field name for '".$rel['columns'][$i]."'");
502 
503  return $name;
504  }
505  $rel['columns'][$i] = $name;
506 
507  $matches = array();
508  if ( preg_match('/(\w+)\.(\w+)/', $name, $matches)){
509  $selected_tables[] = $matches[1];
510  } else {
511  PEAR::raiseError(Dataface_SCHEMA_PARSE_ERROR,null,null,null,"Error parsing table name from '$name' ");
512  }
513  }
514  }
515 
516  $this->_schema['selected_tables'] = array_unique($this->_schema['selected_tables']);
517 
518 
519 
520 
521 
522 
523  }
524 
525  function getName(){
526  return $this->_name;
527  }
528 
542  function getSQL($getBlobs=false, $where=0, $sort=0, $preview=1){
543  $start = microtime_float();
544  import('SQL/Compiler.php');
545  import( 'SQL/Parser/wrapper.php');
546  $loadParserTime = microtime_float() - $start;
547  if ( isset($this->_sql_generated[$where][$sort][$preview]) and $this->_sql_generated[$where][$sort][$preview] ){
548  /*
549  * The SQL has already been generated and stored. We can just return it.
550  */
551  if ( $getBlobs ){
552  // We will be returning blob columns as well
553  return $this->_schema['sql_with_blobs'][$where][$sort][$preview];
554  } else {
555  // We will NOT be returning BLOB columns
556  return $this->_schema['sql_without_blobs'][$where][$sort][$preview];
557  }
558  } else {
559  /*
560  * The SQL has not been generated yet. We will generate it.
561  */
562  $this->_sql_generated[$where][$sort][$preview] = true;
563  if ( !isset( $this->_schema['sql_without_blobs'] ) ){
564  $this->_schema['sql_without_blobs'] = array();
565  }
566  if ( !isset($this->_schema['sql_with_blobs']) ){
567  $this->_schema['sql_with_blobs'] = array();
568  }
569 
570  if ( defined('DATAFACE_USE_CACHE') and DATAFACE_USE_CACHE ){
571  $cache_key_blobs = 'tables/'.$this->_sourceTable->tablename.'/relationships/'.$this->_name.'/sql/withblobs';
572  $cache_key_noblobs = 'tables/'.$this->_sourceTable->tablename.'/relationships/'.$this->_name.'/sql/withoutblobs';
573  // we are using the APC cache
574  import( 'Dataface/Cache.php');
575  $cache =& Dataface_Cache::getInstance();
576  $this->_schema['sql_with_blobs'] = $cache->get($cache_key_blobs);
577  $this->_schema['sql_without_blobs'] = $cache->get($cache_key_noblobs);
578 
579  }
580 
581 
582 
583  if ( !isset($this->_schema['sql_without_blobs'][$where][$sort][$preview]) or !isset($this->_schema['sql_with_blobs'][$where][$sort][$preview])){
584  //if ( !$this->_schema['sql_without_blobs'][$where][$sort] ) $this->_schema['sql_without_blobs'] = array();
585  //if ( !$this->_schema['sql_with_blobs'] ) $this->_schema['sql_with_blobs'] = array();
586 
587 
588  $parsed = unserialize(serialize($this->_schema['parsed_sql']));
589  $parsed['column_names'] = array();
590  $parsed['column_aliases'] = array();
591  $parsed['columns'] = array();
592  $wrapper = new SQL_Parser_wrapper($parsed, 'MySQL');
593  $blobCols = array();
594 
595  $tableAliases = array();
596  // For tables that have custom SQL defined we sub in its SQL
597  // here.
598  foreach ( array_keys($parsed['tables']) as $tkey ){
599  if ( $parsed['tables'][$tkey]['type'] == 'ident' ){
600  $table =& Dataface_Table::loadTable($parsed['tables'][$tkey]['value']);
601  $proxyView = $table->getProxyView();
602  $tsql = $table->sql();
603  if ( isset($tsql) and !$proxyView){
604  $parsed['tables'][$tkey]['type'] = 'compiled_subselect';
605  $parsed['tables'][$tkey]['value'] = $tsql;
606  if ( !$parsed['tables'][$tkey]['alias'] ) $parsed['tables'][$tkey]['alias'] = $table->tablename;
607  } else if ( $proxyView ){
608  $parsed['tables'][$tkey]['value'] = $proxyView;
609  if ( !$parsed['tables'][$tkey]['alias'] ) $parsed['tables'][$tkey]['alias'] = $table->tablename;
610  }
611  $tableAliases[$table->tablename] = $parsed['tables'][$tkey]['alias'];
612  unset($table);
613  unset($tsql);
614  }
615  }
616  $done = array();
617  $dups = array();
618  foreach ( $this->fields(true) as $colname){
619  // We go through each column in the query and add meta columns for length.
620 
621  //$table =& Dataface_Table::getTableTableForField($colname);
622  list($tablename, $col) = explode('.',$colname);
623  if ( $tablename != $this->getDomainTable() and Dataface_Table::loadTable($this->getDomainTable())->hasField($col) ){
624  // If this is a duplicate field we take the domain table value.
625  $dups[$col] = $this->getDomainTable();
626  continue;
627  }
628  if ( isset($done[$col]) ) $dups[$col] = $tablename;
629  $done[$col] = true;
630 
632  $alias = $wrapper->getTableAlias($tablename);
633  if ( !$alias ){
634  $alias = $tablename;
635  }
636  $colname = $alias.'.'.$col;
637  if ( isset($field) ) unset($field);
638  $field =& $table->getField($col);
639  if ( PEAR::isError($field) ) $field = array();
640  if ( $table->isPassword($col) ){
641  unset($table);
642  continue;
643  }
644 
645  if ( $table->isBlob($col) ){
646  $blobCols[] = $colname;
647  }
648  if ( @$tableAliases[$tablename] ){
649  $tableAlias = $tableAliases[$tablename];
650  } else {
651  $tableAlias = $tablename;
652  }
653 
654  if ( $tableAlias ) {
655  $colFull = '`'.$tableAlias.'`.`'.$col.'`';
656  //echo "Full";
657  }
658  else {
659  $colFull = '`'.$col.'`';
660 
661  }
662 
663  $rfieldProps = array();
664  if ( isset($this->_schema['field']) and isset($this->_schema['field'][$col])){
665  $rfieldProps = $this->_schema['field'][$col];
666  }
667 
668  $maxlen = 255;
669  if ( @$rfieldProps['max_length'] ){
670  $maxlen = intval($rfieldProps['max_length']);
671  }
672 
673  if ( in_array(strtolower($table->getType($col)), array('timestamp','datetime')) ){
674 
675  $parsed['columns'][] = array('type'=>'compiled_func', 'table'=>null, 'value'=>"ifnull(convert_tz(".$colFull.",'SYSTEM','".addslashes(df_tz_or_offset())."'), ".$colFull.")", 'alias'=>$col);
676  } else if ( $preview and $table->isText($col) and !@$field['struct'] and !$table->isXML($col)){
677  $parsed['columns'][] = array('type'=>'compiled_func', 'table'=>null, 'value'=>"SUBSTRING($colFull, 1, $maxlen)", 'alias'=>$col);
678  } else {
679 
680  $parsed['columns'][] = array('type'=>'ident', 'table'=>$tableAlias, 'value'=>$col, 'alias'=>null);
681  }
682  //$wrapper->addMetaDataColumn($colname);
683  // Note: Removed *length* metadata columns for now.. not hard to add
684  // back. Will wait to see if anyone screams!
685  // Steve Hannah 071229
686  unset($table);
687 
688  }
689 
690 
691  if ( $where !== 0 ){
692  $whereClause = $where;
693  // Avoid ambiguous column error. Any duplicate columns need to be specified.
694  foreach ( $dups as $dcolname=>$dtablename ){
695  $whereClause = preg_replace('/([^.]|^) *`'.preg_quote($dcolname).'`/','$1 `'.$dtablename.'`.`'.$dcolname.'`', $whereClause);
696  }
697  $wrapper->addWhereClause($whereClause);
698  }
699  if ( $sort !==0){
700  $sortClause = $sort;
701  foreach ( $dups as $dcolname=>$dtablename ){
702  $sortClause = preg_replace('/([^.]|^) *`'.preg_quote($dcolname).'`/','$1 `'.$dtablename.'`.`'.$dcolname.'`', $sortClause);
703  }
704  $wrapper->setSortClause($sortClause);
705  }
706 
707  //$compiler = new SQL_Compiler(null, 'mysql');
708  $compiler =& SQL_Compiler::newInstance('mysql');
709  $compiler->version = 2;
710  $this->_schema['sql_with_blobs'][$where][$sort][$preview] = $compiler->compile($parsed);
711 
712 
713  foreach ($blobCols as $blobCol){
714  $wrapper->removeColumn($blobCol);
715  }
716  $this->_schema['sql_without_blobs'][$where][$sort][$preview] = $compiler->compile($parsed);
717 
718  if ( defined('DATAFACE_USE_CACHE') and DATAFACE_USE_CACHE){
719  $cache->set($cache_key_blobs, $this->_schema['sql_with_blobs']);
720  $cache->set($cache_key_noblobs, $this->_schema['sql_without_blobs']);
721 
722  }
723 
724 
725  }
726 
727  /*
728  * Now that the SQL is generated we can call ourselves and the first
729  * case will now be initiated (ie: the generated sql will be returned).
730  */
731  $timeToGenerate = microtime_float()-$start;
732  if ( DATAFACE_DEBUG ){
733  $this->app->addDebugInfo("Time to generate sql for relationship {$this->name} : $timeToGenerate");
734  }
735  return $this->getSQL($getBlobs, $where, $sort);
736  }
737 
738 
739 
740 
741  }
742 
743 
753  function getLabel(){
754  $action = $this->_sourceTable->getRelationshipsAsActions(array(), $this->_name);
755  return $action['label'];
756  }
757 
767  function getSingularLabel(){
768  $action = $this->_sourceTable->getRelationshipsAsActions(array(), $this->_name);
769  if ( !isset($action['singular_label']) ){
770 
771  $label = $this->getLabel();
772  $action['singular_label'] = df_singularize($label);
773 
774 
775  }
776 
777  return $action['singular_label'];
778 
779 
780 
781  }
782 
783 
790  function supportsAddNew(){
791  if ( !isset($this->addNew) ){
792  $this->addNew = !( isset( $this->_schema['actions']['addnew'] ) and !$this->_schema['actions']['addnew'] );
793  }
794  return $this->addNew;
795  }
796 
804  if ( !isset($this->addExisting) ){
805  $this->addExisting=true;
806  $fkeys = $this->getForeignKeyValues();
807  if ( count($fkeys) == 1 ){
808  $this->addExisting = false;
809  // If the relationship only has a single destination table
810  // then it probably won't support adding existing records
811  // Unless the foreign key allows null values - then it is
812  // possible that records that aren't currently part of a
813  // relationship can be added.
814  /*
815  $table =& Dataface_Table::loadTable($this->getDomainTable());
816  $keys = array_keys($fkeys[$this->getDomainTable()]);
817  foreach ($keys as $key){
818  $field =& $table->getField($key);
819  if ( strtoupper($field['Null']) == 'YES' ){
820  $this->addExisting=true;
821  break;
822  }
823  }
824  */
825  }
826  if ( isset( $this->_schema['actions']['addexisting'] ) and !$this->_schema['actions']['addexisting'] ){
827  $this->addExisting = false;
828  }
829  else if ( isset( $this->_schema['actions']['addexisting'] ) and $this->_schema['actions']['addexisting'] ) {
830  $this->addExisting = true;
831  }
832  }
833  return $this->addExisting;
834  }
835 
842  function supportsRemove(){
843  if (isset( $this->_schema['actions']['remove'] ) and !$this->_schema['actions']['remove'] ) return false;
844  return true;
845 
846  }
847 
848  function showTabsForAddNew(){
849  return ( @$this->_schema['prefs']['addnew']['show_tabs'] !== '0' );
850  }
851 
852 
853 
857  function &getSourceTable(){
858  return $this->_sourceTable;
859  }
860 
864  function &keys(){
865  if ( !isset($this->_keys) ){
866  $this->_keys = array();
867  $destTables =& $this->getDestinationTables();
868  foreach ( array_keys($destTables) as $x ){
869  $table =& $destTables[$x];
870  $tkeys = array_keys($table->keys());
871  foreach ($tkeys as $tkey){
872  $this->_keys[$tkey] = $table->getField($tkey);
873  }
874  }
875  }
876  return $this->_keys;
877  }
878 
883  function &getDestinationTables(){
884  if ( !isset( $this->_destinationTables ) ){
885  $this->_destinationTables = array();
886  $this->_fieldTableLookup = array();
887  $columns =& $this->_schema['columns'];
888  $tables = array();
889  foreach ($columns as $column){
890  list($tablename, $fieldname) = explode('.', $column);
891  //$table =& $this->_sourceTable->getTableTableForField($this->_name.'.'.$column);
892 
894  $this->_fieldTableLookup[$fieldname] =& $table;
895  $tables[] =& $table;
896  unset($table);
897  }
898  //$this->_destinationTables = array_unique($tables);
899  // For some reason array_unique does not seem to work with references in PHP 4
900  $this->_destinationTables = array();
901  $found = array();
902  foreach ( array_keys($tables) as $tableIndex ){
903  if ( @$found[$tables[$tableIndex]->tablename] ) continue;
904 
905  $found[$tables[$tableIndex]->tablename] = true;
906  $this->_destinationTables[] =& $tables[$tableIndex];
907  }
908  }
909 
911  }
912 
918  function &getTable($field=null){
919  if ( $field === null ) return $this->_sourceTable;
920  if ( strpos($field, '.') !== false ){
921  list($tablename, $field) = explode('.', $field);
923  return $table;
924  }
925  $this->getDestinationTables();
926  if ( isset($this->_fieldTableLookup[$field]) ) return $this->_fieldTableLookup[$field];
927  else {
928  $fields = preg_grep('/\.'.preg_quote($field,'/').'$/', $this->fields(true, true));
929  if ( !$fields ){
930  $null = null;
931  return $null;
932  } else {
933  list($tablename) = explode('.', reset($fields));
934  $this->_fieldTableLookup[$field] =& Dataface_Table::loadTable($tablename);
935  //echo $tablename;
936  return $this->_fieldTableLookup[$field];
937  }
938 
939  }
940  }
941 
946  function getTableAlias($tableName){
947  if ( !isset($this->_schema) || !isset($this->_schema['parsed_sql']) || !is_array($this->_schema['parsed_sql']['table_names']) ) return null;
948  $idx = array_search($tableName, $this->_schema['parsed_sql']['table_names']);
949  if ( $idx !== false ){
950  return $this->_schema['parsed_sql']['table_aliases'][$idx];
951  }
952  return null;
953 
954  }
955 
956 
964  function &getField($fieldname){
965  if ( !isset($this->_cache[__FUNCTION__][$fieldname]) ){
966  if ( strpos($fieldname, '.') !== false ){
967  list($tablename, $sfieldname) = explode('.', $fieldname);
969  $field =& $table->getField($sfieldname);
970 
971  } else {
972  // Check the domain table first
973  $domainTable = Dataface_Table::loadTable($this->getDomainTable());
974  $f =& $domainTable->getField($fieldname);
975  if ( !PEAR::isError($f) ) return $field =& $f;
976  else {
977 
978  // Domain table doesn't have a field by this name
979  $fields = preg_grep('/\.'.preg_quote($fieldname,'/').'$/', $this->fields(true));
980 
981  if ( count($fields) > 0 ){
982  $lfieldname = reset($fields);
983 
984  list($tablename, $sfieldname) = explode('.',$lfieldname);
986  $field =& $table->getField($sfieldname);
987 
988  } else {
989  $field = null;
990  }
991  }
992  }
993  $this->_cache[__FUNCTION__][$fieldname] =& $field;
994  }
995  return $this->_cache[__FUNCTION__][$fieldname];
996  }
997 
998 
1004  function getDomainSQL(){
1005 
1006  $relationship =& $this->_schema;
1007  // obtain reference to the relationship in question
1008 
1009 
1010  // The 'domain_sql' attribute of a relationship defines the SQL select statement that
1011  // is used to obtain the set of candidates for a relationship. This can be specified
1012  // in the ini file using the __domain__ attribute of a relationship, or it can be parsed
1013  // from the existiing 'sql' attribute.
1014  if ( !isset( $relationship['domain_sql'] ) ){
1015  import( 'SQL/Compiler.php');
1016  // compiles SQL tree structure into query strings
1017  import( 'SQL/Parser/wrapper.php');
1018  // utility methods for dealing with SQL structures
1019  $compiler = new SQL_Compiler();
1020  // the compiler we will use to generate the eventual SQL
1021  $parsed_sql = unserialize(serialize($relationship['parsed_sql']));
1022  // we make a deep copy of the existing 'parsed_sql' structure that was
1023  // created in the "readRelationshipsIniFile" method. We deep copy, because
1024  // some of the methods in SQL_Parser_wrapper work directly on the
1025  // datastructure - but we want to leave it unchanged.
1026  $wrapper = new SQL_Parser_wrapper($parsed_sql);
1027  // create a new wrapper to operate on the sql data structure.
1028  $wrapper->removeWhereClausesWithTable( $this->_sourceTable->tablename);
1029  $wrapper->removeJoinClausesWithTable( $this->_sourceTable->tablename);
1030  // We remove all Where and Join clauses that use columns from the current table.
1031  // This is because portions of the sql pertaining to the current table
1032  // likely represent specifications within the domain to mark that an
1033  // element of the domain is related to the current table.
1034  $wrapper->removeWhereClausesWithPattern( '/\$\w+/' );
1035  $wrapper->removeJoinClausesWithPattern( '/\$\w+/' );
1036  // Similarly we need to remove any clauses containing variables which
1037  // get filled in by the current table. The rationale is the same as
1038  // for removing clauses pertaining to the current table.
1039  $fkVals = $this->getForeignKeyValues();
1040  // We obtain the foreign key values for this relationship because they
1041  // will help us to decide which columns in the remaining query are
1042  // helpful for obtaining the domain.
1043  $uselessTables = array();
1044  // will hold list of tables that we don't need
1045  $fkTables = array_keys($fkVals);
1046  // list of tables that are involved in foreign key relationships in this
1047  // relationship.
1048  foreach ($fkVals as $fkTable => $fkFields){
1049  $foundVal = 0;
1050  $foundLink = 0;
1051  // keep track of which tables actually have real values assigned.
1052  foreach ($fkFields as $fieldVal){
1053  //if ( !preg_match('/^__(\w+)_auto_increment__$/', $fieldVal) ){
1054  // // A field with a value of the form __Tablename__auto_increment__ is a placeholder
1055  // // for an auto generated id. If the only values specified for a table are placeholders
1056  // // then that table is pretty much useless as a domain query... it can be eliminated.
1057  // $foundVal++;
1058  //
1059  //
1060  //}
1061  if ( is_scalar($fieldVal) and strpos($fieldVal, '$') === 0 ){
1062  // This table is linked directly to the current table... hence it is only a join
1063  // table.
1064  $foundLink++;
1065  }
1066  }
1067  if ( $foundLink){
1068  // no real valus found.. mark table as useless.
1069  $uselessTables[] = $fkTable;
1070  }
1071 
1072  }
1073 
1074 
1075 
1076  foreach ($uselessTables as $table_name){
1077  // Remove all useless tables from the query's where and join clauses.
1078  $wrapper->removeWhereClausesWithTable( $table_name );
1079  $wrapper->removeJoinClausesWithTable( $table_name );
1080  $wrapper->removeColumnsFromTable( $table_name );
1081  }
1082 
1083  $domain_tables = array_diff($relationship['selected_tables'], $uselessTables);
1084  if ( !$domain_tables ) $domain_tables = $relationship['selected_tables'];
1085 
1086  $table_ranks = array();
1087  foreach ($this->_schema['columns'] as $col){
1088  list($tname) = explode('.',$col);
1089  if ( !isset($table_ranks[$tname]) ) $table_ranks[$tname] = 0;
1090  $table_ranks[$tname]++;
1091  }
1092 
1093  $high = null;
1094  $high_score = 0;
1095  foreach ( $domain_tables as $dt ){
1096  if ( $table_ranks[$dt] > $high_score ){
1097  $high = $dt;
1098  $high_score = $table_ranks[$dt];
1099  }
1100  }
1101  $domain_tables = array($high);
1102 
1103 
1104  if ( count($domain_tables) !== 1 ){
1105  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);
1106  }
1107  $relationship['domain_table'] = array_pop($domain_tables);
1108 
1109 
1110  $wrapper->packTables(/*$relationship['selected_tables']*/);
1111  // Previous steps have only eliminated useless tables with respect to query
1112  // parameters. There may still be some tables listed in the query that don't
1113  // offer anything. Notice that we pass the list of selected tables to this
1114  // method to indicate that tables whose columns are selected need to be there
1115  // and should be left intact.
1116  $relationship['domain_sql'] = $compiler->compile($parsed_sql);
1117  }
1118  return $relationship['domain_sql'];
1119 
1120 
1121  }
1122 
1130  function getDomainTable(){
1131  if ( !isset($this->domainTable) ){
1132  $res = $this->getDomainSQL();
1133  if ( PEAR::isError($res) ){
1134  return $res;
1135  }
1136  $this->domainTable = $this->_schema['domain_table'];
1137  }
1138  return $this->domainTable;
1139  }
1140 
1141 
1157  function getForeignKeyValues($values = null, $sql = null, $record = null){
1158  if ( is_object($record) ) $record_id = $record->getId();
1159  if ( !isset($values) and !isset($sql) and !isset($record) and isset($this->_cache[__FUNCTION__]) ){
1160  return $this->_cache[__FUNCTION__];
1161  }
1162  if ( !isset($values) and !isset($sql) and is_object($record) and isset($this->_cache[__FUNCTION__.$record_id]) ){
1163  return $this->_cache[__FUNCTION__.$record_id];
1164  }
1165  // Strategy:
1166  // ----------
1167  // 1. Label all fields involved in the foreign key so that fields that are equal have the
1168  // same label. Eg: In the query:
1169  // select *
1170  // from Students
1171  // inner join Student_Courses
1172  // on Students.id = Student_Courses.studentid
1173  // inner join Courses
1174  // on Student_Courses.courseid = Courses.id
1175  // where
1176  // Students.id = '$id'
1177  //
1178  // In the above query Students.id and Student_Course.studentid would have the same label, and
1179  // Student_Courses.courseid and Courses.id would have the same label.
1180  // ie: Label(Students.id) = Label(Student_Courses.studentid) ^ Label(Student_Courses.courseid) = Label(Courses.id)
1181  //
1182  // 2. Assign values for each label. All fields with a particular label have the same value.
1183  // In the above query, we would have:
1184  // Value(Label(Students.id)) = '$id'
1185  // **Note from above that Label(Students.id)=Label(Student_Courses.studentid) so their values are also equal.
1186  //
1187  // 3. For labels without a value find out if one of the fields assuming that label is an auto-increment field. If so
1188  // we assign the special value '__Tablename__auto_increment__' where "Tablename" is the name of the table whose
1189  // field is to be auto incremented.
1190  //
1191  // 4. Collect the the values in a structure so that we can lookup the values of any particular field in any particular
1192  // table easily. Return this structure.
1193  $relationship =& $this->_schema;
1194 
1195  if ( $sql !== null ){
1196  // An SQL query was specified as a parameter, we parse this and use the resulting data structure
1197  // for the rest of the computations.
1198  if ( is_string($sql) ) {
1199  $parser = new SQL_Parser(null,'MySQL');
1200  $sql = $parser->parse($sql);
1201  }
1202  $select =& $sql;
1203  } else {
1204  // We use the 'parsed_sql' entry in the relationship as the basis for our dissection.
1205  $select =& $relationship['parsed_sql'];
1206  }
1207 
1208 
1209  // Build equivalence classes for column names.
1210  $labels = array();
1211  $vals = array();
1212  $this->_makeEquivalenceLabels($labels, $vals, $select);
1213 
1214  // Fill in some default values
1215 
1216  if ( is_array($values) ){
1217  foreach ($values as $field_name => $field_value ){
1218 
1219  if ( !$field_value ) continue;
1220  // we don't want empty and null values to act as defaults because they
1221  // tend to screw things up when we are adding related records.
1222 
1223  if ( isset( $labels[$field_name] ) ) $label = $labels[$field_name];
1224  else {
1225  $label = $field_name;
1226  $labels[$field_name] = $label;
1227  }
1228 
1229 
1230  $vals[$label] = $field_value;
1231 
1232  }
1233  }
1234 
1235 
1236 
1237  // 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
1238  // time this means that one of the fields is an auto increment field that will be automatically filled in. We need to insert
1239  // a special value (in this case), so that we know this is the case.
1240  foreach ( $labels as $field_name=>$label ){
1241  if ( !isset( $vals[$label] ) ){
1242  $field =& Dataface_Table::getTableField($field_name);
1243  $table_auto_increment = null;
1244  foreach ( $labels as $auto_field_name=>$auto_label ){
1245  if ( $auto_label == $label ){
1246  $auto_field =& Dataface_Table::getTableField($auto_field_name);
1247  if ( $auto_field['Extra'] == 'auto_increment' ){
1248  list($table_auto_increment) = explode('.', $auto_field_name);
1249  unset($auto_field);
1250  break;
1251  }
1252  unset($auto_field);
1253  }
1254  }
1255  if ( isset($table_auto_increment) ){
1256  //list($table) = explode('.', $field_name);
1257  $vals[$label] = "__".$table_auto_increment."__auto_increment__";
1258  } else {
1259  $vals[$label] = new Dataface_Relationship_ForeignKey($this, $labels, $label);
1260  }
1261  unset($field);
1262  }
1263  }
1264 
1265  $table_cols = array();
1266  foreach ( $labels as $field_name=>$label){
1267  $fieldArr =& Dataface_Table::getTableField($field_name);
1268  list( $table, $field ) = explode('.', $field_name);
1269  if ( !$table ) continue;
1270  if ( !isset( $table_cols[$table] ) ) $table_cols[$table] = array();
1271  $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];
1272  unset($fieldArr);
1273  }
1274 
1275  // make sure that each table at least sets all of the mandatory fields.
1276  foreach ( $table_cols as $table=>$cols ){
1277  $tableObject =& Dataface_Table::loadTable($table);
1278  foreach ( array_keys($tableObject->mandatoryFields()) as $key ){
1279  if ( !isset( $cols[$key] ) ){
1280  $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.");
1281  }
1282  }
1283  unset($tableObject);
1284  }
1285 
1286 
1287  if ( !isset($values) and !isset($sql) and !isset($record) ){
1288  $this->_cache[__FUNCTION__] = $table_cols;
1289  }
1290  if ( !isset($values) and !isset($sql) and is_object($record) ){
1291  $this->_cache[__FUNCTION__.$record_id] = $table_cols;
1292  }
1293 
1294  return $table_cols;
1295 
1296  }
1297 
1298  function isNullForeignKey(&$val){
1299  return is_a($val, 'Dataface_Relationship_ForeignKey');
1300  }
1301 
1311  function getDistance($table){
1312  $fkeys =& $this->getForeignKeyValues();
1313  if ( !isset($fkeys[$table]) ) return 999;
1314  foreach ( $fkeys[$table] as $key=>$value ){
1315  if ( is_scalar($value) and strlen($value) > 0 and $value{0} == '$' ) return 1;
1316 
1317  }
1318  return 2;
1319  }
1320 
1322  $this->_addExistingFilters = null;
1323  if ( !isset($this->_addExistingFilters) ){
1324  $this->_addExistingFilters = array();
1325  $fkeys = $this->getForeignKeyValues();
1326  if ( count($fkeys) == 1 ){
1327  foreach ( $fkeys[$this->getDomainTable()] as $fname=>$fval){
1328  $this->_addExistingFilters[$fname] = '=';
1329  }
1330  }
1331  }
1332  return $this->_addExistingFilters;
1333 
1334  }
1335 
1344  function getAddableValues(&$record, $filter=array()){
1345  $filter = array_merge($this->getAddExistingFilters(), $filter);
1346  $t =& Dataface_Table::loadTable($this->getDomainTable());
1347  $r =& $this->_schema;
1348  $tkey_names = array_keys($t->keys());
1349  if ( !is_a($record, 'Dataface_Record') ){
1350  throw new Exception("Attempt to call getAddableValues() without providing a Dataface_Record as context.", E_USER_ERROR);
1351 
1352  }
1353  if ( ( $res = $record->callDelegateFunction($this->_name.'__'.__FUNCTION__) ) !== null ){
1354  return $res;
1355  }
1356 
1357  if ( isset( $this->_schema['vocabulary']['existing'] ) ){
1358  // A custom vocabulary has been specified in the relationships.ini
1359  // file for this relationship.
1360  $options_temp = $t->getValuelist($r['vocabulary']['existing']);
1361  $options = array();
1362  foreach (array_keys($options_temp) as $optkey){
1363  if ( strpos($optkey, '=') === false ){
1364  $options[$tkey_names[0].'='.urlencode($optkey)] = $options_temp[$optkey];
1365  } else {
1366  $options[$optkey] = $options_temp[$optkey];
1367  }
1368  }
1369  } else {
1370  // No custom vocabulary has been specified. Let's do our best
1371  // to figure out what the vocabulary should be.
1372  //$fkeys = $this->getForeignKeyValues(null,null,$record);
1373  $table = $this->getDomainTable();
1374  if ( isset( $fkeys[$table] ) ){
1375  $query = $fkeys[$table];
1376  foreach ($query as $key=>$val){
1377  if ( $this->isNullForeignKey($val) or strpos($val,'$')===0 or $val == '__'.$table.'__auto_increment__'){
1378  unset($query[$key]);
1379  }
1380  }
1381  } else {
1382  $query = array();
1383  }
1384  $query = array_merge($filter, $query);
1385  $qt = new Dataface_QueryTool($table, $this->_sourceTable->db, $query);
1386  $options = $qt->getTitles(true,false,true/*Ignores 250 record limit*/);
1387  }
1388 
1389  return $options;
1390 
1391  }
1392 
1393 
1394 
1404  function _makeEquivalenceLabels(&$labels, &$values, &$sql_data){
1405 
1406  $roots = array();
1407  if ( isset( $sql_data['where_clause'] ) and is_array( $sql_data['where_clause'] )){
1408  $roots[] =& $sql_data['where_clause'];
1409  }
1410  if ( isset( $sql_data['table_join_clause'] ) and is_array( $sql_data['table_join_clause']) ){
1411  foreach ( $sql_data['table_join_clause'] as $clause ){
1412  if ( is_array($clause) ){
1413  $roots[] = $clause;
1414  }
1415  }
1416  }
1417  $parser_wrapper = new SQL_Parser_wrapper($sql_data);
1418  foreach ($roots as $root){
1419  $this->_makeEquivalenceLabels_rec($labels, $values, $root, $parser_wrapper);
1420  }
1421 
1422 
1423  }
1424 
1425 
1435  function _makeEquivalenceLabels_rec( &$labels, &$values, &$root, &$parser_wrapper){
1436 
1437  if ( isset( $root['op'] ) ){
1438  if ( $root['op'] == '=' ){
1439  $label = '';
1440  $value = null;
1441  $fields = array();
1442  $existingLabels = 0;
1443  $oldLabel = null;
1444  // keep track of the number of existing labels.
1445  foreach ( array('arg_1','arg_2') as $arg ){
1446  switch ($root[$arg]['type']){
1447  case 'ident':
1448  $field_name = Dataface_Table::absoluteFieldName(
1449  $parser_wrapper->resolveColumnName($root[$arg]['value']),
1450  $parser_wrapper->_data['table_names']
1451 
1452  );
1453  if ( !is_string($field_name) ){
1454  echo "Field name is not a string.";
1455  echo get_class($field_name);
1456  if ( is_a($field_name, 'PEAR_Error') ) echo $field_name->toString();
1457  }
1458  $fields[] = $field_name;
1459 
1460  // If this column already has a label, then we use it as the common label
1461  if ( isset( $labels[$field_name] ) ) {
1462  $existingLabels++;
1463  if ( $existingLabels > 1 ){
1464  // If the other column already had a label, then we keep track of it
1465  $oldLabel = $label;
1466  }
1467  $label = $labels[$field_name];
1468  }
1469 
1470  break;
1471 
1472  case 'text_val':
1473  case 'int_val':
1474  case 'real_val':
1475  $value = $root[$arg]['value'];
1476  break;
1477  }
1478 
1479  }
1480  // Assert (count($fields) == 1 or count($fields) == 2)
1481  // Assert (count($fields) == 1 => $value !== null )
1482  // Assert (count($fields) == 2 => $value === null )
1483 
1484  $label = ( $label ? $label : $fields[0] );
1485  // Obtain the label for these columns. If there are 2 columns, they must have the same label
1486  foreach ( $fields as $field ){
1487  if ( !isset( $labels[$field] ) ) $labels[$field] = $label;
1488  }
1489 
1490  // Now we have to change labels of all fields that contained the old label.
1491  if ( $oldLabel !== null ){
1492  foreach ( $labels as $field_name=>$field_label ) {
1493  if ( $field_label == $oldLabel ){
1494  $labels[$field_name] = $label;
1495  }
1496  }
1497  }
1498 
1499  // Now we update the value for the label if there is a value.
1500  if ( $value !== null ){
1501  $values[$label] = $value;
1502  }
1503  }
1504  }
1505 
1506  foreach ( $root as $key=>$value ){
1507  if ( is_array($value) ){
1508  $this->_makeEquivalenceLabels_rec($labels, $values, $value, $parser_wrapper);
1509  }
1510  }
1511  }
1512 
1513 
1534  function getOrderColumn(){
1535 
1536  $order_col = ( ( isset( $this->_schema['metafields']['order']) and $this->_schema['metafields']['order'] ) ? $this->_schema['metafields']['order'] : null );
1537  if ( !isset($order_col) ){
1538  return PEAR::isError('Attempt to sort relationship "'.$this->_name.'" but no order column was defined.');
1539  }
1540  return $order_col;
1541  }
1542 
1543 
1555  return ( isset( $this->_schema['meta']['class']) and strtolower($this->_schema['meta']['class']) == 'parent');
1556  }
1557 
1570  return ( isset($this->_schema['meta']['class']) and strtolower($this->_schema['meta']['class']) == 'children');
1571  }
1572 
1573 
1585  function isOneToMany(){
1586  if ( $this->getMaxCardinality() === 1 ) return false;
1587  if ( isset($this->_cache[__FUNCTION__]) ) return $this->_cache[__FUNCTION__];
1588  $this->_cache[__FUNCTION__] = (count($this->getForeignKeyValues()) == 1);
1589  return $this->_cache[__FUNCTION__];
1590  }
1591 
1601  function isManyToMany(){
1602  if( $this->getMaxCardinality() === 1 ) return false;
1603  return !$this->isOneToMany();
1604  }
1605 
1606  function getMinCardinality(){
1607  $cardinality = $this->getCardinality();
1608  if ( $cardinality == '*' ) return 0;
1609  else if ( $cardinality == '+' ) return 1;
1610  else if ( strpos($cardinality, '..') !== false ){
1611  list($min,$max) = array_map('trim',explode('..', $cardinality));
1612  $min = intval($min);
1613  return $min;
1614 
1615  } else {
1616  return 0;
1617  }
1618  }
1619 
1620  function getMaxCardinality(){
1621  $cardinality = $this->getCardinality();
1622  if ( $cardinality == '*' ) return 0;
1623  else if ( $cardinality == '+' ) return 1;
1624  else if ( strpos($cardinality, '..') !== false ){
1625  list($min,$max) = array_map('trim',explode('..', $cardinality));
1626  if ( $max == '*' ) return 0;
1627  return intval($max);
1628 
1629  } else {
1630  return 0;
1631  }
1632  }
1633 
1634 
1635  function isOneToOne(){
1636  return ( $this->getMaxCardinality() ===1 and $this->getMinCardinality() === 1);
1637  }
1638 
1639  function isOneToZeroOrOne(){
1640  $max = $this->getMaxCardinality();
1641  $min = $this->getMinCardinality();
1642 
1643  return ($min === 0 and $max === 1);
1644  }
1645 
1646 
1647 
1648 
1649 
1650  function getPermissions($params=array(), $table=null){
1651 
1652  // 1. Get the permissions for the particular field
1653  if ( isset($params['field']) ){
1654  if ( strpos($params['field'],'.') !== false ){
1655  list($junk,$fieldname) = explode('.', $params['field']);
1656  } else {
1657  $fieldname = $params['field'];
1658  }
1659  $t =& $this->getTable($fieldname);
1660  //$rec = $this->toRecord($t->tablename);
1661 
1662 
1663  $perms = $t->getPermissions(array('field'=>$fieldname, 'nobubble'=>1));
1664  if ( !$perms ) $perms = array();
1665 
1666 
1667 
1668 
1669  $rfperms = $this->_sourceTable->getPermissions(array('relationship'=>$this->getName(), 'field'=>$fieldname, 'nobubble'=>1));
1670  //echo "RFPerms: ";print_r($rfperms);
1671  if ( $rfperms ){
1672  foreach ($rfperms as $k=>$v){
1673  $perms[$k] = $v;
1674  }
1675  }
1676 
1677  unset($params['field']);
1678  $recPerms = $this->getPermissions($params, $t->tablename);
1679 
1680 
1681  foreach ($perms as $k=>$v){
1682  $recPerms[$k] = $v;
1683  }
1684 
1685  //print_r($perms);
1686  return $recPerms;
1687  } else {
1688  $domainTable = $this->getDomainTable();
1689  $destinationTables = $this->getDestinationTables();
1690  $isManyToMany = $this->isManyToMany();
1691  $targetTable = $table;
1692  if ( !@$targetTable ){
1693  if ( $isManyToMany ){
1694  foreach ($destinationTables as $candidateTable){
1695  if ( strcmp($candidateTable->tablename, $domainTable) !== 0 ){
1696  $targetTable = $candidateTable->tablename;
1697  break;
1698  }
1699  }
1700  }
1701  }
1702  if ( !@$targetTable ){
1703  $targetTable = $domainTable;
1704  }
1705 
1706  $parentPerms = $this->_sourceTable->getPermissions(array('relationship'=>$this->getName()));
1707  $targetTableObj = Dataface_Table::loadTable($targetTable);
1708  //$domainRecord = $this->toRecord($targetTable);
1709 
1710 
1711  $isDomainTable = (strcmp($domainTable, $targetTable) === 0 );
1712 
1713 
1714  $perms = $targetTableObj->getPermissions();
1715  if ( $isManyToMany ){
1716  if ( @$parentPerms['add new related record'] ){
1717  $perms['new'] = 1;
1718  } else if ( @$parentPerms['add existing related record'] and !$isDomainTable ){
1719  $perms['new'] = 1;
1720  } else if ( $isDomainTable and isset($parentPerms['add new related record']) and !@$parentPerms['add new related record'] ){
1721  $perms['new'] = 0;
1722  } else if ( isset($parentPerms['add existing related record']) and !@$parentPerms['add existing related record'] ){
1723  $perms['new'] = 0;
1724  }
1725 
1726  if ( @$parentPerms['delete related record'] ){
1727  $perms['delete'] = 1;
1728  } else if ( $isDomainTable and isset($parentPerms['delete related record']) and !@$parentPerms['delete related record'] ){
1729  $perms['delete'] = 0;
1730  } else if ( !$isDomainTable and @$parentPerms['remove related record'] ){
1731  $perms['delete'] = 1;
1732  } else if ( !$isDomainTable and isset($parentPerms['remove related record']) and !@$parentPerms['remove related record'] ){
1733  $perms['delete'] = 0;
1734  }
1735 
1736  if ( !$isDomainTable ){
1737  if ( @$parentPerms['edit related records'] ){
1738  $perms['edit'] = 1;
1739  } else if ( isset($parentPerms['edit related records']) and !@$parentPerms['edit related records'] ){
1740  $perms['edit'] = 0;
1741  }
1742 
1743  if ( @$parentPerms['link related records'] ){
1744  $perms['link'] = 1;
1745  } else if ( isset($parentPerms['link related records']) and !@$parentPerms['link related records'] ){
1746  $perms['link'] = 0;
1747  }
1748  }
1749 
1750 
1751  } else {
1752  if ( $parentPerms['add new related record'] ){
1753  $perms['new'] = 1;
1754  } else if ( isset($parentPerms['add new related record']) and !@$parentPerms['add new related record'] ){
1755  $perms['new'] = 0;
1756  }
1757 
1758  if ( @$parentPerms['delete related record'] ){
1759  $perms['delete'] = 1;
1760  } else if ( isset($parentPerms['delete related record']) and !@$parentPerms['delete related record'] ){
1761  $perms['delete'] = 0;
1762  }
1763  if ( @$parentPerms['edit related records'] ){
1764  $perms['edit'] = 1;
1765  } else if ( isset($parentPerms['edit related records']) and !@$parentPerms['edit related records'] ){
1766  $perms['edit'] = 0;
1767  }
1768  if ( @$parentPerms['link related records'] ){
1769  $perms['link'] = 1;
1770  } else if ( isset($parentPerms['link related records']) and !@$parentPerms['link related records'] ){
1771  $perms['link'] = 0;
1772  }
1773  }
1774 
1775 
1776 
1777  if ( @$parentPerms['view related records'] ){
1778  $perms['view'] = 1;
1779  } else if ( isset($parentPerms['view related records']) and !@$parentPerms['view related records'] ){
1780  $perms['view'] = 0;
1781  }
1782  if ( @$parentPerms['find related records'] ){
1783  $perms['find'] = 1;
1784  } else if ( isset($parentPerms['find related records']) and !@$parentPerms['find related records'] ){
1785  $perms['find'] = 0;
1786  }
1787 
1788 
1789  /*
1790  foreach ( $this->toRecords() as $record){
1791  $rperms = $record->getPermissions(array());
1792  if ( $perms ){
1793  $perms = array_intersect_assoc($perms, $rperms);
1794 
1795  } else {
1796  $perms = $rperms;
1797  }
1798 
1799  }
1800  */
1801  return $perms;
1802 
1803  }
1804  }
1805 
1806 
1807  function checkPermission($perm, $params=array()){
1808  $perms = $this->getPermissions($params);
1809  return @$perms[$perm]?1:0;
1810 
1811  }
1812 
1813 
1814 
1815 }
1816 
1823  var $fields = array();
1824  var $relationship = null;
1825 
1832  $this->relationship =& $relationship;
1833  foreach ( $labels as $field=>$l ){
1834  if ( $l==$label ) $this->fields[] = $field;
1835  }
1836  }
1837 
1843  function getFields(){
1844  return $this->fields;
1845  }
1846 
1858  function getFurthestField(){
1859  $d = 0;
1860  $f = null;
1861  foreach ($this->fields as $field){
1862  list($table) = explode('.', $field);
1863  $td = $this->relationship->getDistance($table);
1864  if ( $td > $d ){
1865  $d = $td;
1866  $f = $field;
1867  }
1868  }
1869  return $f;
1870  }
1871 
1872 
1873 }
1874