Source for file joindata.php

Documentation is available at joindata.php

  1. <?php
  2. /**
  3. * an object that manages data related by an SQL JOIN but pretends to be a single form field.
  4. *
  5. @author    Stuart Prescott
  6. @copyright  Copyright Stuart Prescott
  7. @license    http://opensource.org/licenses/gpl-license.php GNU Public License
  8. @version    $Id$
  9. @package    Bumblebee
  10. @subpackage FormsLibrary
  11. */
  12.  
  13. /** Load ancillary functions */
  14. require_once 'inc/typeinfo.php';
  15.  
  16. /** parent object */
  17. require_once 'field.php';
  18. /** database row manipulation object */
  19. require_once 'dbrow.php';
  20. /** connect to database */
  21. require_once 'inc/db.php';
  22.  
  23. /**
  24. * an object that manages data related by an SQL JOIN but pretends to be a single form field.
  25. *
  26. * If the element in the table is a selection list then the setup will be
  27. * as a join table.
  28. *
  29. * We respect the 'field' interface while overriding pretty much all of it.
  30. *
  31. * Primitive class for managing join data. Can be used on its own to just
  32. * join data or with a selection lists class to make a join table.
  33. * This may be used to determine the choices
  34. * that a user is permitted to select (e.g. dropdown list or radio buttons)
  35. *
  36. * Used in a many:many or many:1 relationships (i.e. a field in a
  37. * table that is the listed in a join table
  38. *
  39. * Typical usage:<code>
  40. *   $f = new JoinData('jointable', 'id1', $table1_key, 'fieldname', 'label1');
  41. *   $f2 = new DropList('id2', 'label2');
  42. *   $f2->connectDB('table2', array('id', 'name'));
  43. *   $f2->list->prepend(array('-1','(none)'));
  44. *   $f2->setFormat('id', '%s', array('name'), ' (%s)', array('longname'));
  45. *   $f->addElement($f2);
  46. *   $f3 = new TextField('field3', '');
  47. *   $f->addElement($f3, 'sum_is_100');
  48. *   $f->joinSetup('id2', array('total' => 3));
  49. *   $f->use2StepSync = 1;
  50. * </code>
  51. @package    Bumblebee
  52. @subpackage FormsLibrary
  53. */
  54. class JoinData extends Field {
  55.   /** @var string   name of the join table (table has columns of form (LeftID, RightID) to join Left and Right tables */
  56.   var $joinTable;
  57.   /** @var string   column name in the join table for the column with keys/Ids from the left table */
  58.   var $jtLeftIDCol;
  59.   /** @var string   value of the left column ID that we are matching */
  60.   var $jtLeftID;
  61.   /** @var string   column name in the join table for the column with keys/Ids from the right table */
  62.   var $jtRightIDCol;
  63.   /** @var DBRow    prototype DBRow object that is replicated for each entry in the join table (1:many join) */
  64.   var $protoRow;
  65.   /** @var array    list of DBRow objects for each row returned in a 1:many join */
  66.   var $rows;
  67.   /** @var string   number of columns that this field should span in the table */
  68.   var $colspan;
  69.   /** @var array    formatting control arguments (e.g. maximum number permitted in 1:many) */
  70.   var $format;
  71.   /** @var integer  number of rows in the join */
  72.   var $number = 0;
  73.   /** @var array    list of columns to return when the containing object asks for the SQL column=value sequence */
  74.   var $reportFields = array();
  75.   /** @var array    list of functions that should be applied to the group of results collectively to test validity */
  76.   var $groupValidTest;
  77.   /** @var boolean  SQL errors should be fatal (die()) */
  78.   var $fatalsql = 0;
  79.  
  80.   /**
  81.   *  Create a new joindata object
  82.   *
  83.   * @param string $jointable    see $this->joinTable
  84.   * @param string $jtLeftIDCol  see $this->jtLeftIDCol
  85.   * @param string $jtLeftID     see $this->jtLeftID
  86.   * @param string $name   the name of the field (db name, and html field name
  87.   * @param string $description  used in the html title or longdesc for the field
  88.   */
  89.   function JoinData($joinTable$jtLeftIDCol$jtLeftID,
  90.                      $name$description=''{
  91.     //$this->DEBUG=10;
  92.     $this->Field($name''$description);
  93.     $this->joinTable = $joinTable;
  94.     $this->jtLeftIDCol = $jtLeftIDCol;
  95.     $this->jtLeftID = $jtLeftID;
  96.     $this->protoRow = new DBRow($joinTable$jtLeftID$jtLeftIDCol);
  97.     $field new Field($jtLeftIDCol);
  98.     $field->defaultValue $jtLeftID;
  99.     $this->protoRow->addElement($field);
  100.     $this->protoRow->editable 1;
  101.     $this->protoRow->autonumbering = 0;
  102.     $this->rows = array();
  103.     $this->groupValidTest = array();
  104.     $this->notifyIdChange = 1;
  105.   }
  106.  
  107.   /**
  108.   * Connect the join table and fill from the database
  109.   *
  110.   * @param string $jtRightIDCol  see $this->jtRightIDCol
  111.   * @param mixed  $format        see $this->format (string is converted to array)
  112.   */
  113.   function joinSetup($jtRightIDCol$format=''{
  114.     $this->jtRightIDCol = $jtRightIDCol;
  115.     $this->format = (is_array($format$format array($format));
  116.     $this->_fill();
  117.     //preDump($this);
  118.   }
  119.  
  120.   /**
  121.   * Calculate the maximum number of rows to display (e.g. including spares)
  122.   */
  123.   function _calcMaxNumber({
  124.     if (isset($this->format['total'])) {
  125.       $this->number = $this->format['total'];
  126.       return;
  127.     }
  128.     $this->number = $this->_countRowsInJoin();
  129.     if (isset($this->format['minspare'])) {
  130.       $this->number += $this->format['minspare'];
  131.       return;
  132.     }
  133.     if (isset($this->format['matrix'])) {
  134.       $this->number = $this->protoRow->fields[$this->jtRightIDCol]->list->length;
  135.       return;
  136.     }
  137.   }
  138.  
  139.   /**
  140.   * add a field to the join table
  141.   * Field will appear in each row returned from the join table
  142.   * @param Field  $field            the field to be added
  143.   * @param string  $groupValidTest  data validation routine for this field
  144.   */
  145.   function addElement($field$groupValidTest=NULL{
  146.     $this->protoRow->addElement($field);
  147.     $this->groupValidTest[$field->name$groupValidTest;
  148.   }
  149.  
  150.   /**
  151.   * Create a new row from the protorow for storing data
  152.   * @param integer  $rowNum   number of this row (used as unique identifier in the namebase)
  153.   */
  154.   function _createRow($rowNum{
  155.     #echo "NAMEBASE={$this->namebase}";
  156.     #echo "NAME={$this->name}";
  157.     $this->rows[$rowNumclone($this->protoRow);
  158.     $this->rows[$rowNum]->setNamebase($this->namebase.$this->name.'-'.$rowNum.'-');
  159.  
  160.     #for ($i=0; $i<=$rowNum; $i++) {
  161.     #  echo "NB = ". $this->rows[$i]->namebase."<br />";
  162.     #}
  163.   }
  164.  
  165.   /**
  166.   * Fill from the database
  167.   */
  168.   function _fill({
  169.     $this->_fillFromProto();
  170.   }
  171.  
  172.   /**
  173.   * Fill from the database one row at a time
  174.   */
  175.   function _fillFromProto({
  176.     $oldnumber $this->number;
  177.     $this->_calcMaxNumber();
  178.     $this->log('Extending rows from '.$oldnumber.' to '.$this->number);
  179.     for ($i=$oldnumber$i $this->number$i++{
  180.       $this->_createRow($i);
  181.       $this->rows[$i]->recNum 1;
  182.       $this->rows[$i]->recStart $i;
  183.       $this->rows[$i]->fill();
  184.       $this->rows[$i]->restriction $this->jtRightIDCol .'='qw($this->rows[$i]->fields[$this->jtRightIDCol]->value);
  185.       $this->rows[$i]->insertRow ($this->rows[$i]->fields[$this->jtRightIDCol]->value 0);
  186.       $this->log('This row flagged with insertRow '.$this->rows[$i]->insertRow);
  187.       if ($this->rows[$i]->insertRow{
  188.         $this->rows[$i]->fillWithDefaults();
  189.       }
  190.     }
  191.     //preDump($this->rows);
  192.   }
  193.  
  194.   function display({
  195.     //check how many fields we need to have (again) as we might have to show more this time around.
  196.     $this->_fillFromProto();
  197.     return $this->selectable();
  198.   }
  199.  
  200.   function selectable($cols=2{
  201.     $t '';
  202.     #$errorclass = ($this->isValid ? '' : "class='inputerror'");
  203.     $errorclass '';
  204.     for ($i=0$i<$this->number$i++{
  205.       $t .= "<tr $errorclass><td colspan='$this->colspan'>\n";
  206.       #$t .= "FOO$i";
  207.       $t .= $this->rows[$i]->displayInTable(2);
  208.       $t .= "</td>\n";
  209.       for ($col=0$col<$cols-2$col++{
  210.         $t .= '<td></td>';
  211.       }
  212.       $t .= "</tr>\n";
  213.     }
  214.     return $t;
  215.   }
  216.  
  217.   function selectedValue({
  218.     return $this->selectable();
  219.   }
  220.  
  221.   function displayInTable($cols=3{
  222.     //check how many fields we need to have (again) as we might have to show more this time around.
  223.     $this->_fillFromProto();
  224.     //$cols += $this->colspan;
  225.     $t "<tr><td colspan='$cols'>{$this->description}</td></tr>\n";
  226.     if ($this->editable{
  227.       $t .= $this->selectable($cols);
  228.     } else {
  229.       //preDump($this);
  230.       //preDump(debug_backtrace());
  231.       $t .= $this->selectedValue();
  232.       $t .= "<input type='hiddenname='{$this->name}value='".xssqw($this->value)." />";
  233.     }
  234.     return $t;
  235.   }
  236.  
  237.   function update($data) {
  238.     for ($i=0; $i < $this->number$i++{
  239.       $rowchanged = $this->rows[$i]->update($data);
  240.       if ($rowchanged{
  241.         $this->log('JoinData-Row '.$i.' has changed.');
  242.         foreach (array_keys($this->rows[$i]->fieldsas $k{
  243.           #$this->rows[$i]->fields[$this->jtRightIDCol]->changed = $rowchanged;
  244.           #if ($v->name != $this->jtRightIDCol && $v->name != $this->jtLeftIDCol) {
  245.             $this->rows[$i]->fields[$k]->changed $rowchanged;
  246.           #}
  247.         }
  248.       }
  249.       $this->changed += $rowchanged;
  250.     }
  251.     $this->log('Overall JoinData row changed='.$this->changed);
  252.     return $this->changed;
  253.   }
  254.  
  255.   /**
  256.   *  Count the number of rows in the join table so we know how many to retrieve
  257.   * @return integer number of rows found
  258.   */
  259.   function _countRowsInJoin() {
  260.     $g = quickSQLSelect($this->joinTable$this->jtLeftIDCol$this->jtLeftID$this->fatalsql1);
  261.     $this->log('Found '.$g[0].' rows currently in join');
  262.     return $g[0];
  263.   }
  264.  
  265.   /**
  266.   * Trip the complex field within this object to sync()
  267.   * This allows the object to then know our actual value (at last) -- this has to be
  268.   * delayed for as long as possible as an INSERT might be needed before the value of the
  269.   * selection is actually known, but that shouldn't be done until all the data has passed
  270.   * all validation tests
  271.   *
  272.   * @param $name  (unused)
  273.   * @param $force (unused)
  274.   * @return string  sql name=value sequence
  275.   */
  276.   function sqlSetStr($name='', $force=false) {
  277.     //$this->DEBUG=10;
  278.     #echo "JoinData::sqlSetStr";
  279.     $this->_joinSync();
  280.     if (count($this->reportFields1{
  281.       //We return an empty string as this is only a join table entry,
  282.       //so it has no representation within the row itself.
  283.       return '';
  284.     } else {
  285.       // then we can return the value of the first row (any more doesn't make sense)
  286.       $t = array();
  287.       foreach ($this->reportFields as $f{
  288.         if (is_array($f)) {
  289.           reset($f);
  290.           $realfield = key($f);
  291.           $aliasfield = current($f);
  292.           $t[] = $this->rows[0]->fields[$realfield]->sqlSetStr($aliasfield);
  293.         } else {
  294.           $t[] = $this->rows[0]->fields[$f]->sqlSetStr();
  295.         }
  296.       }
  297.       return join(', ',$t);
  298.     }
  299.   }
  300.  
  301.   /**
  302.   * synchronise the join table
  303.   */
  304.   function _joinSync() {
  305.     for ($i=0; $i < $this->number$i++{
  306.       #echo "before sync row $i oob='".$this->oob_status."' ";
  307.       $this->changed += $this->rows[$i]->changed;
  308.       //preDump($this->rows[$i]->fields[$this->jtRightIDCol]);
  309.       if ($this->rows[$i]->fields[$this->jtRightIDCol]->value !== ''   // damned PHP '' == 0
  310.           && $this->rows[$i]->fields[$this->jtRightIDCol]->value == 0
  311.           && $this->rows[$i]->fields[$this->jtRightIDCol]->changed{
  312.         //then this row is to be deleted...
  313.         $this->oob_status |= $this->rows[$i]->delete();
  314.       } else {
  315.         $this->log('JoinData::_joinSync(): Syncing row '.$i);
  316. //         preDump($this->rows[$i]);
  317.         $this->oob_status |= $this->rows[$i]->sync();
  318.       }
  319.       $this->oob_errorMessage .= $this->rows[$i]->errorMessage;
  320.       $this->changed += $this->rows[$i]->changed;
  321.       #echo " after sync row $i oob='".$this->oob_status."'";
  322.     }
  323.   }
  324.  
  325.  
  326.   /**
  327.   * Check validity of data
  328.   *
  329.   * override the isValid method of the Field class, using the
  330.   * checkValid method of each member row completed as well as
  331.   * cross checks on other fields.
  332.   * @return boolean data is valid
  333.   */
  334.   function isValid() {
  335.     $this->log('Check JoinData validity: '.$this->name);
  336.     $this->isValid = 1;
  337.     for ($i=0$i $this->number$i++{
  338.       #echo "val". $this->rows[$i]->fields[$this->jtRightIDCol]->value.";";
  339.       //$this->log('Row: '.$i);
  340.       $this->rows[$i]->isValid 1;
  341.       if ($this->rows[$i]->fields[$this->jtRightIDCol]->value !== ''   // damned PHP '' == 0
  342.           && $this->rows[$i]->fields[$this->jtRightIDCol]->value == 0
  343.           && $this->rows[$i]->changed{
  344.         // this row will be deleted to mark it valid in the mean time
  345.         $this->rows[$i]->isValid 1;
  346.       } elseif ( ($this->rows[$i]->fields[$this->jtRightIDCol]->value == -1
  347.                    || $this->rows[$i]->fields[$this->jtRightIDCol]->value 0)
  348.                  && $this->rows[$i]->changed{
  349.         //this row will be sync'd against the database, so check its validity
  350.         //$this->log('Checking valid for row: '.$i);
  351.         $this->isValid = $this->rows[$i]->checkValid(&& $this->isValid;
  352.       }
  353.       //echo "JoinData::isValid = '$this->isValid'";
  354.       //echo "JoinData::isValid[$i] = '".$this->rows[$i]->isValid."'";
  355.     }
  356.     //now we need to check the validity of sets of data (e.g. sum of the same
  357.     //field across the different rows.
  358.     foreach ($this->rows[0]->fields as $k => $f{
  359.       if (isset($this->groupValidTest[$f->name])) {
  360.         $allvals = array();
  361.         for ($i=0; $i < $this->number$i++{
  362.           if ($this->rows[$i]->fields[$this->jtRightIDCol]->value 0{
  363.             $allvals[] = $this->rows[$i]->fields[$k]->value;
  364.           }
  365.         }
  366.         $fieldvalid = ValidTester($this->groupValidTest[$f->name]$allvals);
  367.         if ($fieldvalid{
  368.           for ($i=0; $i < $this->number$i++{
  369.             $this->rows[$i]->fields[$k]->isValid 0;
  370.           }
  371.         }
  372.         $this->isValid = $fieldvalid && $this->isValid;
  373.       }
  374.     }
  375.     //echo "JoinData::isValid = '$this->isValid'";
  376.     return $this->isValid;
  377.   }
  378.  
  379.   /**
  380.   * Change the Id value of each row
  381.   */
  382.   function idChange($newId) {
  383.     for ($i=0; $i < $this->number$i++{
  384.       $this->rows[$i]->setId($newId);
  385.     }
  386.   }
  387.  
  388.   /**
  389.   * Set the name base of the rows
  390.   */
  391.   function setNamebase($namebase='') {
  392.     for ($i=0; $i < $this->number$i++{
  393.       $this->rows[$i]->setNamebase($namebase);
  394.     }
  395.     $this->protoRow->setNamebase($namebase);
  396.   }
  397.  
  398.   /**
  399.   * set whether each row is editable
  400.   */
  401.   function setEditable($editable=false) {
  402.     for ($i=0; $i < $this->number$i++{
  403.       $this->rows[$i]->setEditable($editable);
  404.     }
  405.     $this->protoRow->setEditable($editable);
  406.   }
  407.  
  408. } // class JoinData

Documentation generated on Tue, 06 Mar 2007 10:01:42 +0000 by phpDocumentor 1.3.0