Source for file dbrow.php

Documentation is available at dbrow.php

  1. <?php
  2. /**
  3. * Database row base class
  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 'dbobject.php';
  18. /** status codes for success/failure of database actions */
  19. require_once 'inc/statuscodes.php';
  20.  
  21.  
  22. /**
  23. * Object representing a database row (and extensible to represent joined rows)
  24. *
  25. * Typical usage:<code>
  26. *   #set database connection parameters
  27. *   $obj = new DBRow("users", 14, "userid");
  28. *   #set the fields required and their attributes
  29. *   $obj->addElement(....);
  30. *   #connect to the database
  31. *   $obj->fill();
  32. *   #check to see if user data changes some values
  33. *   $obj->update($POST);
  34. *   $obj->checkValid();
  35. *   #synchronise with database
  36. *   $obj->sync();</code>
  37. *
  38. @package    Bumblebee
  39. @subpackage FormsLibrary
  40. */
  41. class DBRow extends DBO {
  42.   /** @var boolean  this is a new object the form for which has not yet been shown to the user */
  43.   var $newObject = 0;
  44.   /** @var boolean  this row should be inserted into the db */
  45.   var $insertRow = 0;
  46.   /** @var boolean  include all fields in the SQL statement not just ones that have changed or have values  */
  47.   var $includeAllFields = 0;
  48.   /** @var boolean  automatically number new objects (i.e. the database will do it for us) */
  49.   var $autonumbering = 1;
  50.   /** @var string   a restriction that is included in the WHERE statement for all queries  */
  51.   var $restriction = '';
  52.   /** @var string   number of the start record number for a LIMIT statement */
  53.   var $recStart = '';
  54.   /** @var string   number of the stop record number for a LIMIT statement */
  55.   var $recNum   = '';
  56.   /** @var string   do a two-step synchronisation routine whereby the record is created first then updated second */
  57.   var $use2StepSync;
  58.   /** @var array    additional rows to be included at the end of the display table */
  59.   var $extrarows;
  60.   /** @var boolean  row is marked as deleted in the table (but not actually deleted)  */
  61.   var $isDeleted = false;
  62.   /** @var string   this object can be deleted from the table (using DELETE); otherwise set the delete column to 1 for delete */
  63.   var $deleteFromTable = 1;
  64.  
  65.   /**
  66.   *  Create a new database row object
  67.   *
  68.   * @param string $table   name of the table to be used
  69.   * @param integer $id     row id number in the table (-1 for new object)
  70.   * @param string $idfield (optional) the column in the table for the primary key (id)
  71.   */
  72.   function DBRow($table$id$idfield='id'{
  73.     $this->DBO($table$id$idfield);
  74.     #$this->fields = array();
  75.   }
  76.  
  77.   /**
  78.   * Set the value of the primary key (id) for this object
  79.   *
  80.   * @param integer $newId    the id value to use
  81.   */
  82.   function setId($newId{
  83.     $this->log('DBRow: setting new id'.$newId);
  84.     $this->id = $newId;
  85.     $this->fields[$this->idfield]->set($this->id);
  86.     foreach (array_keys($this->fieldsas $k{
  87.       if ($this->fields[$k]->notifyIdChange{
  88.         $this->fields[$k]->idChange($newId);
  89.       }
  90.     }
  91.   }
  92.  
  93.   /**
  94.   *  Update the object with the user-submitted data
  95.   *
  96.   *  update the value of each of the objects fields according to the user
  97.   *  input data, and validate the data if appropriate
  98.   *  @param array user supplied data (field => $value)
  99.   *  @return boolean data is valid
  100.   */
  101.   function update($data{
  102.     $this->log('DBRow:'.$this->namebase.' Looking for updates:');
  103.     // First, check to see if this record is new
  104.     if ($this->id == -&& $this->ignoreId{
  105.       $this->insertRow = 1;
  106.     }
  107.  
  108.     // We're a new object, but has the user filled the form in, or is the
  109.     // user about to fill the form in?
  110.     $this->newObject = 1;
  111.     foreach (array_keys($this->fieldsas $k{
  112.       if ($k != $this->idfield && isset($data[$this->namebase.$k]&& $this->fields[$k]->hidden{
  113.         $this->log('I AM NOT NEW '.$k.':changed');
  114.         $this->newObject = 0;
  115.         break;
  116.       }
  117.     }
  118.  
  119.     // check each field in turn to allow it to update its data
  120.     foreach (array_keys($this->fieldsas $k{
  121.       $this->log("Check $k ov:".$this->fields[$k]->value
  122.                             .'('.$this->fields[$k]->useNullValues .'/'$this->newObject.')');
  123.       if (!($this->fields[$k]->useNullValues && $this->newObject)) {
  124.         $this->changed += $this->fields[$k]->update($data);
  125.       }
  126.       $this->log('nv:'.$this->fields[$k]->value.' '.($this->changed ? 'changed' 'not changed'));
  127.     }
  128.     #$this->checkValid();
  129.     return $this->changed;
  130.   }
  131.  
  132.   /**
  133.   * check the validity of the data
  134.   * @return boolean data is valid
  135.   */
  136.   function checkValid({
  137.     $this->isValid = 1;
  138.     // check each field in turn to allow it to update its data
  139.     // if this object has not been filled in by the user, then
  140.     // suppress validation
  141.     foreach (array_keys($this->fieldsas $k{
  142.       if (($this->newObject && $this->insertRow)) {
  143.         $this->log('Checking valid '.$this->fields[$k]->namebase $k);
  144.         if ($this->fields[$k]->isValid()) {
  145.           $this->errorMessage .= T_('Invalid data:').' '.$this->fields[$k]->longname
  146.                                     .'('.$this->fields[$k]->name.')'
  147.                                   .' = "'$this->fields[$k]->getValue(.'"<br />';
  148.           $this->isValid = false;
  149.         }
  150.       }
  151.     }
  152.     if ($this->isValid{
  153.       $this->errorMessage .= '<br />'
  154.           .T_('Some values entered into the form are not valid and should be highlighted in the form below. Please check your data entry and try again.');
  155.     }
  156.     return $this->isValid;
  157.   }
  158.  
  159.   /**
  160.   * Synchronise this object's fields with the database
  161.   *
  162.   * If the object is new, then INSERT the data, if the object is pre-existing
  163.   * then UPDATE the data. Fancier fields that are only pretending to
  164.   * do be simple fields (such as JOINed data) should perform their updates
  165.   * during the _sqlvals() call
  166.   *
  167.   * @return integer  from statuscodes
  168.   */
  169.   function sync({
  170.     global $TABLEPREFIX;
  171.     // If the input isn't valid then bail out straight away
  172.     if ($this->changed{
  173.       $this->log('not syncing: changed='.$this->changed);
  174.       return STATUS_NOOP;
  175.     elseif ($this->isValid{
  176.       $this->log('not syncing: valid='.$this->isValid);
  177.       return STATUS_ERR;
  178.     }
  179.     $this->log('syncing: changed='.$this->changed.' valid='.$this->isValid);
  180.     if ($this->use2StepSync{
  181.       $this->log('starting two-step sync');
  182.       $this->_twoStageSync();
  183.     }
  184.     $sql_result STATUS_NOOP;
  185.     //obtain the *clean* parameter='value' data that has been SQL-cleansed
  186.     //this will also trip any complex fields to sync
  187.     $vals $this->_sqlvals($this->insertRow || $this->includeAllFields);
  188.     if ($vals != ''{
  189.       //echo "changed with vals=$vals<br/>";
  190.       if ($this->insertRow{
  191.         //it's an existing record, so update
  192.         $q 'UPDATE '.$TABLEPREFIX.$this->table
  193.             .' SET '.$vals
  194.             .' WHERE '.$this->idfield.'='.qw($this->id)
  195.             .(($this->restriction !== ''' AND '.$this->restriction : '')
  196.             .' LIMIT 1';
  197.         $sql_result db_quiet($q$this->fatal_sql);
  198.       else {
  199.         //it's a new record, insert it
  200.         $q 'INSERT '.$TABLEPREFIX.$this->table.' SET '.$vals;
  201.         $sql_result db_quiet($q$this->fatal_sql);
  202.         # FIXME: do we need to check that this was successful in here?
  203.         if ($this->autonumbering{
  204.           //the record number can now be copied into the object's data.
  205.           $this->setId(db_new_id());
  206.         }
  207.         $this->insertRow = 0;
  208.       }
  209.     }
  210.     $this->errorMessage .= $this->oob_errorMessage;
  211.     //echo "sql=$sql_result, oob=$this->oob_status\n";
  212.     return $sql_result $this->oob_status;
  213.   }
  214.  
  215.   /**
  216.   * An alternative way of synchronising this object's fields with the database.
  217.   *
  218.   * Using this approach, we:
  219.   *   - If the object is new, then INSERT a temp row first.
  220.   *   - Then, trip the sqlvals() calls.
  221.   *   - Then, UPDATE the data.
  222.   *
  223.   * Here, we to the 'create temp row' part.
  224.   */
  225.   function _twoStageSync({
  226.     if ($this->id == -1{
  227.       $row new DBRow($this->table-1'id');
  228.       $f new Field($this->idfield);
  229.       $f->value = -1;
  230.       $row->addElement($f);
  231.       foreach ($this->fields as $field{
  232.         if ($field->requiredTwoStage{
  233.           $row->addElement(clone($field));
  234.         }
  235.       }
  236.       $row->isValid 1;
  237.       $row->changed 1;
  238.       $row->insertRow 1;
  239.       $row->sync();
  240.       if ($this->autonumbering{
  241.         //the record number can now be copied into the object's data.
  242.         $this->setId($row->id);
  243.       }
  244.       $this->insertRow = 0;
  245.       $this->log('Created temp row for locking, id='.$this->id.')');
  246.     }
  247.   }
  248.  
  249.   /**
  250.   * Delete this object's row from the database.
  251.   *
  252.   * @param mixed (optional) string or array of column => value added to the UPDATE statement objects are only to be marked as deleted not actually deleted.
  253.   * @return integer from statuscodes
  254.   */
  255.   function delete($extraUpdates=NULL{
  256.     global $TABLEPREFIX;
  257.     if ($this->id == -1{
  258.       // nothing to do
  259.       $this->log('$id == -1, so nothing to do');
  260.       return STATUS_NOOP;
  261.     }
  262.     if ($this->deletable{
  263.       $this->log('Object not deletable by rule.');
  264.       $this->errorMessage = T_('Cannot delete this item. Permission denied.');
  265.       return STATUS_FORBIDDEN;
  266.     }
  267.     $sql_result = -1;
  268.     if ($this->deleteFromTable{
  269.       $q 'DELETE FROM '.$TABLEPREFIX.$this->table
  270.           .' WHERE '.$this->idfield.'='.qw($this->id)
  271.           .(($this->restriction !== ''' AND '.$this->restriction : '')
  272.           .' LIMIT 1';
  273.     else {
  274.       $updates array();
  275.       if (is_array($extraUpdates)) {
  276.         $updates array_merge($updates$extraUpdates);
  277.       elseif ($extraUpdates !== NULL{
  278.         $updates[$extraUpdates;
  279.       }
  280.       // toggle the deleted state
  281.       $updates['deleted='.($this->isDeleted?0:1)// old MySQL cannot handle true, use 0,1 instead
  282.       $q 'UPDATE '.$TABLEPREFIX.$this->table
  283.           .' SET '.join($updates', ')
  284.           .' WHERE '.$this->idfield.'='.qw($this->id)
  285.           .(($this->restriction !== ''' AND '.$this->restriction : '')
  286.           .' LIMIT 1';
  287.     }
  288.     #$this->log($q);
  289.     $sql_result db_quiet($q$this->fatal_sql);
  290.     return $sql_result;
  291.   }
  292.  
  293.   /**
  294.   * Generate name='value' data for the SQL statement
  295.   *
  296.   * @param boolean $force (optional) force all fields to be included
  297.   * @return string of data statements
  298.   */
  299.   function _sqlvals($force=0{
  300.     $vals array();
  301.     foreach (array_keys($this->fieldsas $k{
  302.       if ($this->fields[$k]->changed || $force{
  303.         //obtain a string of the form "name='Stuart'" from the field.
  304.         //Complex fields can use this as a JIT syncing point, and may
  305.         //choose to return nothing here, in which case their entry is
  306.         //not added to the return list for the row
  307.         $this->log('Getting SQL string for '.$this->fields[$k]->name8);
  308.         $sqlval $this->fields[$k]->sqlSetStr(''$force);
  309.         #echo "$k,oob = '".$this->fields[$k]->oob_status."' ";
  310.         $this->oob_status |= $this->fields[$k]->oob_status;
  311.         $this->oob_errorMessage .= $this->fields[$k]->oob_errorMessage;
  312.         if ($sqlval{
  313.           #echo "SQLUpdate: '$sqlval' <br />";
  314.           $vals[$sqlval;
  315.         }
  316.         #$vals[] = "$k=" . qw($v->value);
  317.       }
  318.     }
  319.     #echo "<pre>"; print_r($vals); echo "</pre>";
  320.     return join(', ',$vals);
  321.   }
  322.  
  323.   /**
  324.   * Add a new field to the row
  325.   *
  326.   * Add an element into the fields[] array. The element must conform
  327.   * to the Fields class (or at least its interface!) as that will be
  328.   * assumed elsewhere in this object.
  329.   * Inheritable attributes are also set here.
  330.   *
  331.   * @param Field $el the field to add
  332.   */
  333.   function addElement($el{
  334.     $this->fields[$el->name$el;
  335.     if ($this->fields[$el->name]->editable == -1{
  336.       $this->fields[$el->name]->editable $this->editable;
  337.     }
  338.     if (isset($this->fields[$el->name]->namebase)) {
  339.       $this->fields[$el->name]->namebase $this->namebase;
  340.       #echo "Altered field $el->name to $this->namebase\n";
  341.     }
  342.     if ($this->fields[$el->name]->suppressValidation == -1{
  343.       $this->fields[$el->name]->suppressValidation $this->suppressValidation;
  344.       #echo "Altered field $el->name to $this->namebase\n";
  345.     }
  346.     #echo $el->name;
  347.     #echo "foo:".$this->fields[$el->name]->name.":bar";
  348.   }
  349.  
  350.   /**
  351.   * Add multiple new fields to the row
  352.   *
  353.   * Adds multiple elements into the fields[] array.
  354.   *
  355.   * @param array $els array of Field objects
  356.   */
  357.   function addElements($els{
  358.     foreach ($els as $e{
  359.       #echo $e->text_dump();
  360.       $this->addElement($e);
  361.     }
  362.   }
  363.  
  364.   /**
  365.   * Perform the SQL lookup to fill the object with the current data
  366.   *
  367.   * Fill this object (i.e. its fields) from the SQL query
  368.   * @global string  prefix for table names
  369.   */
  370.   function fill({
  371.     global $TABLEPREFIX;
  372.     //echo "foo:$this->id:bar";
  373.     if (($this->id !== NULL && $this->id !== '' && $this->id != -1|| $this->ignoreId{
  374.       //FIXME: can we do this using quickSQLSelect()?
  375.       $where array();
  376.       if ($this->ignoreId{
  377.         $where[$this->idfield.'='.qw($this->id);
  378.       }
  379.       if (is_array($this->restriction)) {
  380.         $where array_merge($where$this->restriction);
  381.       elseif ($this->restriction !== ''{
  382.         $where[$this->restriction;
  383.       }
  384.       $q 'SELECT * FROM '
  385.           .$TABLEPREFIX.$this->table .' AS '$this->table
  386.           .' WHERE '.join($where' AND ')
  387.           .(($this->recStart !== ''&& ($this->recNum !== '')
  388.                           ? " LIMIT $this->recStart,$this->recNum" '');
  389.       $g db_get_single($q);
  390.       if (is_array($g)) {
  391.         foreach (array_keys($this->fieldsas $k{
  392.           if ($this->fields[$k]->sqlHidden{
  393.             $val issetSet($g,$k);
  394.             $this->fields[$k]->set($val);
  395.           }
  396.         }
  397.         $this->isDeleted = issetSet($g'deleted'false);
  398.       else {
  399.         $this->insertRow = $this->ignoreId;
  400.       }
  401.     }
  402.     if ($this->ignoreId{
  403.       $this->id = $this->fields[$this->idfield]->value;
  404.     else {
  405.       //we have to have an id present otherwise we're in trouble next time
  406.       $this->fields[$this->idfield]->set($this->id);
  407.     }
  408.   }
  409.  
  410.   function fillWithDefaults({
  411.     #echo "Filling with defaults";
  412.     foreach (array_keys($this->fieldsas $k{
  413.       #echo get_class($this->fields[$k]);
  414.       #echo '"'.$this->fields[$k]->defaultValue.'"';
  415.       $this->fields[$k]->set($this->fields[$k]->defaultValue);
  416.     }
  417.   }
  418.  
  419.   /**
  420.   * Quick and dirty dump of fields (values only, not a full print_r
  421.   */
  422.   function text_dump({
  423.     $t  "<pre>$this->dumpheader $this->table (id=$this->id)\n{\n";
  424.     foreach ($this->fields as $v{
  425.       $t .= "\t".$v->text_dump();
  426.     }
  427.     $t .= "}\n</pre>";
  428.     return $t;
  429.   }
  430.  
  431.   function display({
  432.     return $this->text_dump();
  433.   }
  434.  
  435.   /**
  436.   * Display the row as a form in a table
  437.   *
  438.   * @param integer $j      (optional) number of columns in the table (will pad as necessary)
  439.   * @return string  html table
  440.   */
  441.   function displayInTable($j{
  442.     $t '<table class="tabularobject">';
  443.     foreach ($this->fields as $v{
  444.       $t .= $v->displayInTable($j);
  445.     }
  446.     $t .= '</table>';
  447.     if (is_array($this->extrarows)) {
  448.       foreach ($this->extrarows as $v{
  449.         $t .= '<tr>';
  450.         foreach ($v as $c{
  451.           $t .= '<td>'.$c.'</td>';
  452.         }
  453.         $t .= '</tr>';
  454.       }
  455.     }
  456.     return $t;
  457.   }
  458.  
  459.   function displayAsTable($cols=2{
  460.     return $this->displayInTable($cols);
  461.   }
  462.  
  463.  
  464.   /**
  465.   * PHP5 clone method
  466.   *
  467.   * PHP5 clone statement will perform only a shallow copy of the object. Any subobjects must also be cloned
  468.   */
  469.   function __clone({
  470.     parent::__clone();
  471.   }
  472.  
  473. // class dbrow
  474.  
  475. ?>

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