phpDocumentor pond
[ class tree: pond ] [ index: pond ] [ all elements ]

Source for file _dataobject.class.php

Documentation is available at _dataobject.class.php

  1. <?php
  2. /**
  3.  * This file implements the abstract DataObject base class.
  4.  *
  5.  * This file is part of Quam Plures - {@link http://quamplures.net/}
  6.  * See also {@link https://launchpad.net/quam-plures}.
  7.  *
  8.  * @copyright (c) 2009 - 2011 by the Quam Plures developers - {@link http://quamplures.net/}
  9.  * @copyright (c)2003-2009 by Francois PLANQUE - {@link http://fplanque.net/}
  10.  *  Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
  11.  *  Parts of this file are copyright (c)2005-2006 by PROGIDISTRI - {@link http://progidistri.com/}.
  12.  *
  13.  *  {@internal License choice
  14.  *  - If you have received this file as part of a package, please find the license.txt file in
  15.  *    the same folder or the closest folder above for complete license terms.
  16.  *  - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/)
  17.  *    then you must choose one of the following licenses before using the file:
  18.  *    - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php
  19.  *    - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php
  20.  *  }}}
  21.  *
  22.  *  {@internal Open Source relicensing agreement:
  23.  *  Daniel HAHLER grants Francois PLANQUE the right to license
  24.  *  Daniel HAHLER's contributions to this file and the b2evolution project
  25.  *  under any OSI approved OSS license (http://www.opensource.org/licenses/).
  26.  *
  27.  *  PROGIDISTRI S.A.S. grants Francois PLANQUE the right to license
  28.  *  PROGIDISTRI S.A.S.'s contributions to this file and the b2evolution project
  29.  *  under any OSI approved OSS license (http://www.opensource.org/licenses/).
  30.  *  }}}
  31.  *
  32.  *  {@internal Below is a list of authors who have contributed to design/coding of this file: }}
  33.  * @author fplanque: Francois PLANQUE
  34.  * @author blueyed: Daniel HAHLER
  35.  * @author mbruneau: Marc BRUNEAU / PROGIDISTRI
  36.  *
  37.  * @package pond
  38.  */
  39. if!defined('QP_MAIN_INIT') ) die'Please, do not access this page directly.' );
  40.  
  41. /**
  42.  * Data Object Base Class
  43.  *
  44.  * This is typically an abstract class, useful only when derived.
  45.  *
  46.  * @version beta
  47.  * @abstract
  48.  *
  49.  * @package pond
  50.  */
  51. class DataObject
  52. {
  53.     /**
  54.      * Unique ID of object in database
  55.      *
  56.      * Please use get/set functions to read or write this param
  57.      *
  58.      * @var int 
  59.      * @access protected
  60.      */
  61.     var $ID = 0;  // This will be the ID in the DB
  62.  
  63.     /**#@+
  64.      * @access private
  65.      */
  66.     var $dbtablename;
  67.     var $dbprefix;
  68.     var $dbIDname;
  69.     var $datecreated_field;
  70.     var $datemodified_field;
  71.     var $creator_field;
  72.     var $lasteditor_field;
  73.     var $dbchanges array();
  74.     /**#@-*/
  75.  
  76.     /**
  77.      * Relations that may restrict deletion.
  78.      */
  79.     var $delete_restrictions = array();
  80.  
  81.     /**
  82.      * Relations that will cascade deletion.
  83.      */
  84.     var $delete_cascades = array();
  85.  
  86.  
  87.     /**
  88.      * Constructor
  89.      *
  90.      * @param string Name of table in database
  91.      * @param string Prefix of fields in the table
  92.      * @param string Name of the ID field (including prefix)
  93.      * @param string datetime field name
  94.      * @param string datetime field name
  95.      * @param string User ID field name
  96.      * @param string User ID field name
  97.      */
  98.     function DataObject$tablename$prefix ''$dbIDname 'ID'$datecreated_field ''$datemodified_field ''$creator_field ''$lasteditor_field '' )
  99.     {
  100.         $this->dbtablename        $tablename;
  101.         $this->dbprefix           $prefix;
  102.         $this->dbIDname           $dbIDname;
  103.         $this->datecreated_field  $datecreated_field;
  104.         $this->datemodified_field $datemodified_field;
  105.         $this->creator_field      $creator_field;
  106.         $this->lasteditor_field   $lasteditor_field;
  107.     }
  108.  
  109.  
  110.     /**
  111.      * Records a change that will need to be updated in the db
  112.      *
  113.      * @access protected
  114.      * @param string Name of parameter
  115.      * @param string DB field type ('string', 'number', 'date' )
  116.      * @param mixed Pointer to value of parameter - dh> pointer? So it should be a reference? Would make sense IMHO anyway.. fp> I just wonder why it's not already a reference... :@
  117.      */
  118.     function dbchange$dbfieldname$dbfieldtype$valuepointer // TODO: dh> value by reference? see above..
  119.     {
  120.         $this->dbchanges[$dbfieldname]['type'$dbfieldtype;
  121.         $this->dbchanges[$dbfieldname]['value'$valuepointer ;
  122.     }
  123.  
  124.  
  125.     /**
  126.      * Update the DB based on previously recorded changes
  127.      *
  128.      * @param boolean do we want to auto track the mod date?
  129.      * @return boolean true on success, false on failure to update, NULL if no update necessary
  130.      */
  131.     function dbupdate$auto_track_modification true )
  132.     {
  133.         global $DB$localtimenow$current_User;
  134.  
  135.         if$this->ID == debug_die'New object cannot be updated!' )}
  136.  
  137.         ifcount$this->dbchanges == )
  138.         {
  139.             return NULL;    // No changes!
  140.         }
  141.  
  142.         if$auto_track_modification )
  143.         // We wnat to track modification date and author automatically:
  144.             if!empty($this->datemodified_field) )
  145.             {    // We want to track modification date:
  146.                 $this->set_param$this->datemodified_field'date'date('Y-m-d H:i:s',$localtimenow) );
  147.             }
  148.             if!empty($this->lasteditor_field&& is_object($current_User) )
  149.             {    // We want to track last editor:
  150.                 // TODO: the current_User is not necessarily the last editor. Item::dbupdate() gets called after incrementing the view for example!
  151.                 // fplanque: this should be handled by set() deciding wether the setting changes the last editor or not
  152.                 $this->set_param$this->lasteditor_field'number'$current_User->ID );
  153.             }
  154.         }
  155.  
  156.         $sql_changes array();
  157.         foreach$this->dbchanges as $loop_dbfieldname => $loop_dbchange )
  158.         {
  159.             // Get changed value (we use eval() to allow constructs like $loop_dbchange['value'] = 'Group->get(\'ID\')'):
  160.             eval'$loop_value = $this->'.$loop_dbchange['value'].';' );
  161.             // Prepare matching statement:
  162.             ifis_null($loop_value) )
  163.             {
  164.                 $sql_changes[$loop_dbfieldname.' = NULL ';
  165.             }
  166.             else
  167.             {
  168.                 switch$loop_dbchange['type')
  169.                 {
  170.                     case 'date':
  171.                     case 'string':
  172.                         $sql_changes[$loop_dbfieldname." = '".$DB->escape$loop_value )."' ";
  173.                         break;
  174.  
  175.                     default:
  176.                         $sql_changes[$loop_dbfieldname." = ".$DB->null($loop_value).' ';
  177.                 }
  178.             }
  179.         }
  180.  
  181.         // Prepare full statement:
  182.         $sql "UPDATE $this->dbtablename SET "implode', '$sql_changes )"
  183.                          WHERE $this->dbIDname = $this->ID";
  184.  
  185.         if$DB->query$sql'DataObject::dbupdate()' ) )
  186.         {
  187.             return false;
  188.         }
  189.  
  190.         // Reset changes in object:
  191.         $this->dbchanges array();
  192.  
  193.         return true;
  194.     }
  195.  
  196.  
  197.     /**
  198.      * Insert object into DB based on previously recorded changes.
  199.      *
  200.      * @return boolean true on success
  201.      */
  202.     function dbinsert()
  203.     {
  204.         global $DB$localtimenow$current_User;
  205.  
  206.         if$this->ID != debug_die'Existing object cannot be inserted!' );
  207.  
  208.         if!empty($this->datecreated_field) )
  209.         {    // We want to track creation date:
  210.             $this->set_param$this->datecreated_field'date'date('Y-m-d H:i:s',$localtimenow) );
  211.         }
  212.         if!empty($this->datemodified_field) )
  213.         {    // We want to track modification date:
  214.             $this->set_param$this->datemodified_field'date'date('Y-m-d H:i:s',$localtimenow) );
  215.         }
  216.         if!empty($this->creator_field) )
  217.         {    // We want to track creator:
  218.             ifempty($this->creator_user_ID) )
  219.             {    // No creator assigned yet, use current user:
  220.                 $this->set_param$this->creator_field'number'$current_User->ID );
  221.             }
  222.         }
  223.         if!empty($this->lasteditor_field) )
  224.         {    // We want to track last editor:
  225.             ifempty($this->lastedit_user_ID) )
  226.             {    // No editor assigned yet, use current user:
  227.                 $this->set_param$this->lasteditor_field'number'$current_User->ID );
  228.             }
  229.         }
  230.  
  231.         $sql_fields array();
  232.         $sql_values array();
  233.         foreach$this->dbchanges as $loop_dbfieldname => $loop_dbchange )
  234.         {
  235.             // Get changed value (we use eval() to allow constructs like $loop_dbchange['value'] = 'Group->get(\'ID\')'):
  236.             eval'$loop_value = $this->'$loop_dbchange['value'].';' );
  237.             // Prepare matching statement:
  238.             $sql_fields[$loop_dbfieldname;
  239.             ifis_null($loop_value) )
  240.             {
  241.                 $sql_values['NULL';
  242.             }
  243.             else
  244.             {
  245.                 switch$loop_dbchange['type')
  246.                 {
  247.                     case 'date':
  248.                     case 'string':
  249.                         $sql_values[$DB->quote$loop_value );
  250.                         break;
  251.  
  252.                     default:
  253.                         $sql_values[$DB->null$loop_value );
  254.                 }
  255.             }
  256.         }
  257.  
  258.         // Prepare full statement:
  259.         $sql "INSERT INTO {$this->dbtablename} ("implode( ', ', $sql_fields )") VALUES ("implode( ', ', $sql_values )")";
  260.  
  261.         if( ! $DB->query$sql'DataObject::dbinsert()' ) )
  262.         {
  263.             return false;
  264.         }
  265.  
  266.         // store ID for newly created db record
  267.         $this->ID = $DB->insert_id;
  268.  
  269.         // Reset changes in object:
  270.         $this->dbchanges array();
  271.  
  272.         return true;
  273.     }
  274.  
  275.  
  276.     /**
  277.      * Inserts or Updates depending on object state.
  278.      *
  279.      * @uses dbinsert()
  280.      * @uses dbupdate()
  281.      * @return boolean true on success, false on failure
  282.      */
  283.     function dbsave()
  284.     {
  285.         if( $this->ID == )
  286.         {    // Object not serialized yet, let's insert!
  287.             return $this->dbinsert();
  288.         }
  289.         else
  290.         {    // Object already serialized, let's update!
  291.             return $this->dbupdate();
  292.         }
  293.     }
  294.  
  295.  
  296.     /**
  297.      * Delete object from DB.
  298.      *
  299.      * @return boolean true on success
  300.      */
  301.     function dbdelete()
  302.     {
  303.         global $DB, $Messages, $app_db_config;
  304.  
  305.         if( $this->ID == { debug_die( 'Non persistant object cannot be deleted!' ); }
  306.  
  307.         if( count($this->delete_cascades) )
  308.         {    // The are cascading deletes to be performed
  309.             // Start transaction:
  310.             $DB->begin();
  311.  
  312.             foreach$this->delete_cascades as $restriction )
  313.             {
  314.                 if( !isset( $app_db_config['aliases'][$restriction['table']] ) )
  315.                 {    // We have no declaration for this table, we consider we don't deal with this table in this app:
  316.                     continue;
  317.                 }
  318.  
  319.                 $DB->query'
  320.                     DELETE FROM '.$restriction['table'].'
  321.                     WHERE '.$restriction['fk'].' = '.$this->ID,
  322.                     'Cascaded delete' );
  323.             }
  324.         }
  325.  
  326.         // Delete this (main/parent) object:
  327.         $DB->query"
  328.             DELETE FROM $this->dbtablename
  329.             WHERE $this->dbIDname = $this->ID",
  330.             'Main delete' );
  331.  
  332.         ifcount($this->delete_cascades) )
  333.         {    // There were cascading deletes
  334.             // End transaction:
  335.             $DB->commit();
  336.         }
  337.  
  338.         // Just in case... remember this object has been deleted from DB!
  339.         $this->ID = 0;
  340.  
  341.         return true;
  342.     }
  343.  
  344.  
  345.     /**
  346.      * Check relations for restrictions or cascades
  347.      *
  348.      * @param string Which relation should be checked ('delete_restrictions' or 'delete_cascades')?
  349.      * @param string An array of foreign key checks to skip.
  350.      * @param array  An array of callbacks used to display more information about a relation.
  351.      *               - Array keys (string): Foreign key this callback should apply to.
  352.      *               - Array values: Arrays with the following keys:
  353.      *                 - 'cb' (callback): Callback to call. Should take one array argument, which
  354.      *                                    will contain the following keys:
  355.      *                                      - 'fk': The foreign key.
  356.      *                                      - 'table': The SQL table for this relation.
  357.      *                                      - 'msg': A format string -- a base message to be displayed.
  358.      *                                               It normally shows the number of results.
  359.      *                                      - 'id':  The ID of this object.
  360.      */
  361.     function check_relations( $what, $ignore = array(), $verbose_callbacks = array() )
  362.     {
  363.         global $DB, $Messages;
  364.  
  365.         foreach( $this->$what as $restriction )
  366.         {
  367.             if( in_array( $restriction['fk'], $ignore ) )
  368.             {    // Skip this relation check.
  369.                 continue;
  370.             }
  371.  
  372.             if( ! isset( $verbose_callbacks[$restriction['fk']] ) )
  373.             {    // We do not want to display detailed info, just the result count:
  374.                 $count = $DB->get_var(
  375.                     'SELECT COUNT(*)
  376.                        FROM '.$restriction['table'].'
  377.                       WHERE '.$restriction['fk'].' = '.$this->ID,
  378.                     00'restriction/cascade check' );
  379.                 if$count )
  380.                 {
  381.                     $Messages->addsprintf$restriction['msg']$count )'restrict' );
  382.                 }
  383.             }
  384.             else
  385.             {    // We want verbose information.
  386.                 // We just will call the callback, providing some
  387.                 // information about this object:
  388.                 call_user_func( $verbose_callbacks[$restriction['fk']]['cb'],
  389.                     array_merge( $restriction, array(
  390.                         'id' => $this->ID,
  391.                     ) ) );
  392.             }
  393.         }
  394.     }
  395.  
  396.  
  397.     /**
  398.      * Check relations for restrictions before deleting
  399.      *
  400.      * @param string
  401.      * @param array list of foreign keys to ignore
  402.      * @return boolean true if no restriction prevents deletion
  403.      */
  404.     function check_delete( $restrict_title, $ignore = array() )
  405.     {
  406.         global $Messages;
  407.  
  408.         // Check restrictions:
  409.         $this->check_relations'delete_restrictions'$ignore );
  410.  
  411.         if$Messages->count('restrict') )
  412.         {    // There are restrictions:
  413.             $Messages->head array(
  414.                     'container' => $restrict_title,
  415.                     'restrict' => T_('The following relations prevent deletion:')
  416.                 );
  417.             $Messages->foot =    T_('Please delete related objects before you proceed.');
  418.             return false;    // Can't delete
  419.         }
  420.  
  421.         return true;    // can delete
  422.     }
  423.  
  424.  
  425.     /**
  426.      * Displays form to confirm deletion of this object
  427.      *
  428.      * @param string Title for confirmation
  429.      * @param string "action" param value to use (hidden field)
  430.      * @param array Hidden keys (apart from "action")
  431.      * @param string most of the time we don't need a cancel action since we'll want to return to the default display
  432.      */
  433.     function confirm_delete( $confirm_title, $delete_action, $hiddens, $cancel_action = NULL )
  434.     {
  435.         global $Messages;
  436.  
  437.         $block_item_Widget = new Widget( 'block_item' );
  438.  
  439.         $block_item_Widget->title $confirm_title;
  440.         $block_item_Widget->disp_template_replaced'block_start' );
  441.  
  442.         $this->check_relations'delete_cascades' );
  443.  
  444.         if$Messages->count('restrict') )
  445.         {    // The will be cascading deletes, issue WARNING:
  446.             echo '<h3>'.T_('WARNING: Deleting this object will also delete:').'</h3>';
  447.             $Messages->display''''true'restrict'NULLNULLNULL );
  448.         }
  449.  
  450.         echo '<p class="warning">'.$confirm_title.'</p>';
  451.         echo '<p class="warning">'.T_('THIS CANNOT BE UNDONE!').'</p>';
  452.  
  453.         $Form = new Form( '', 'form_confirm', 'get', '' );
  454.  
  455.         $Form->begin_form'inline' );
  456.             $Form->hiddens_by_key$hiddens );
  457.             $Form->hidden'action'$delete_action );
  458.             $Form->hidden'confirm');
  459.             $Form->buttonarray'submit'''T_('I am sure!')'DeleteButton' ) );
  460.         $Form->end_form();
  461.  
  462.         $Form new Form'''form_cancel''get''' );
  463.  
  464.         $Form->begin_form'inline' );
  465.             $Form->hiddens_by_key$hiddens );
  466.             if!empty$cancel_action ) )
  467.             {
  468.                 $Form->hidden'action'$cancel_action );
  469.             }
  470.             $Form->buttonarray'submit'''T_('CANCEL')'CancelButton' ) );
  471.         $Form->end_form();
  472.  
  473.         $block_item_Widget->disp_template_replaced'block_end' );
  474.         return true;
  475.     }
  476.  
  477.  
  478.     /**
  479.      * Get a member param by its name
  480.      *
  481.      * @param mixed Name of parameter
  482.      * @return mixed Value of parameter
  483.      */
  484.     function get( $parname )
  485.     {
  486.         return $this->$parname;
  487.     }
  488.  
  489.  
  490.     /**
  491.      * Get a ready-to-display member param by its name
  492.      *
  493.      * Same as disp but don't echo
  494.      *
  495.      * @param string Name of parameter
  496.      * @param string Output format, see {@link format_to_output()}
  497.      */
  498.     function dget( $parname, $format = 'htmlbody' )
  499.     {
  500.         // Note: we call get again because of derived objects specific handlers !
  501.         return format_to_output( $this->get($parname)$format );
  502.     }
  503.  
  504.  
  505.     /**
  506.      * Display a member param by its name
  507.      *
  508.      * @param string Name of parameter
  509.      * @param string Output format, see {@link format_to_output()}
  510.      */
  511.     function disp( $parname, $format = 'htmlbody' )
  512.     {
  513.         // Note: we call get again because of derived objects specific handlers !
  514.         echo format_to_output( $this->get($parname)$format );
  515.     }
  516.  
  517.  
  518.     /**
  519.      * Set param value
  520.      *
  521.      * By default, all values will be considered strings
  522.      *
  523.      * @param string parameter name
  524.      * @param mixed parameter value
  525.      * @param boolean true to set to NULL if empty value
  526.      * @return boolean true, if a value has been set; false if it has not changed
  527.      */
  528.     function set( $parname, $parvalue, $make_null = false )
  529.     {
  530.         return $this->set_param$parname'string'$parvalue$make_null );
  531.     }
  532.  
  533.  
  534.     /**
  535.      * Set param value.
  536.      *
  537.      * @param string Name of parameter
  538.      * @param string DB field type ('string', 'number', 'date' )
  539.      * @param mixed Value of parameter
  540.      * @param boolean true to set to NULL if empty string value
  541.      * @return boolean true, if value has been set/changed, false if not.
  542.      */
  543.     function set_param( $parname, $fieldtype, $parvalue, $make_null = false )
  544.     {
  545.         global $Debuglog;
  546.  
  547.         $dbfield = $this->dbprefix.$parname;
  548.  
  549.         // Set value:
  550.         // fplanque: Note: I am changing the "make NULL" test to differentiate between 0 and NULL .
  551.         // There might be side effects. In this case it would be better to fix them before coming here.
  552.         // i-e: transform 0 to ''
  553.         $new_value ($make_null && ($parvalue === '')) NULL $parvalue;
  554.  
  555.         /* Tblue> Problem: All class member variables originating from the
  556.          *                 DB are strings (unless they were NULL in the DB,
  557.          *                 then they are set to NULL by the PHP MySQL
  558.          *                 extension).
  559.          *                 If we pass an integer or a double to this function,
  560.          *                 the corresponding member variable gets changed
  561.          *                 on every call, because its type is 'string' and
  562.          *                 we compare using the === operator. Using the
  563.          *                 == operator would be a bad idea, though, because
  564.          *                 somebody could pass a NULL value to this function.
  565.          *                 If the member variable then is set to 0, then
  566.          *                 0 equals NULL and the member variable does not
  567.          *                 get updated at all!
  568.          *                 Thus, using the === operator is correct.
  569.          *       Solution: If $fieldtype is 'number' and the type of the
  570.          *                 passed value is either integer or double, we
  571.          *                 convert it to a string (no data loss). The
  572.          *                 member variable and the passed value can then
  573.          *                 be correctly compared using the === operator.
  574.          *  fp> It would be nicer to convert numeric values to ints & floats at load time in class constructor  x=(int)$y->value  or sth.
  575.          * THIS IS EXPERIMENTAL! Feel free to revert if something does not
  576.          * work as expected.
  577.          */
  578.         if$fieldtype == 'number' && is_int$new_value || is_float$new_value ) ) )
  579.         {
  580.             settype( $new_value, 'string' );
  581.         }
  582.  
  583. /* >old
  584.         if( !isset($this->$parname) )
  585.         {    // This property has never been set before, set it to NULL now in order for tests to work:
  586.             $this->$parname = NULL;
  587.         }
  588.         /* blueyed>
  589.         TODO: there's a bug here: you cannot use set_param('foo', 'number', 0), if the $parname member
  590.               has not been set before or is null!!
  591.               What about just:
  592.               ( isset($this->$parname) && $this->$parname === $new_value )
  593.               This would also eliminate the isset() check from above.
  594.               IIRC you've once said here that '===' would be too expensive and I would misuse the DataObjects,
  595.               but IMHO what we have now is not much faster and buggy anyway..
  596.             fp> okay let's give it a try...
  597.         if( (!is_null($new_value) && $this->$parname == $new_value)
  598.             || (is_null($this->$parname) && is_null($new_value)) )
  599. <old */
  600.         if( (isset($this->$parname&& $this->$parname === $new_value)
  601.             || isset($this->$parname&& isset($new_value) ) )
  602.         {    // Value has not changed (we need 2 tests, for NULL and for NOT NULL value pairs)
  603.             $Debuglog->add$this->dbtablename.' object, already set to same value: '.$parname.'/'.$dbfield.' = '.var_export@$this->$parnametrue )'dataobjects' );
  604.  
  605.             return false;
  606.         }
  607.         else
  608.         {
  609.             // Set the value in the object:
  610.             $Debuglog->add$this->dbtablename.' object, setting param '
  611.                            .$parname.'/'.$dbfield.' to '.var_export$new_valuetrue )
  612.                            .' (old: '.isset$this->$parname var_export$this->$parnametrue 'NULL' )
  613.                            .')''dataobjects' );
  614.             $this->$parname $new_value;
  615.  
  616.             // Remember change for later db update:
  617.             $this->dbchange$dbfield$fieldtype$parname );
  618.  
  619.             return true;
  620.         }
  621.     }
  622.  
  623.  
  624.     /**
  625.      * Set a parameter from a Request form value.
  626.      *
  627.      * @param string Dataobject parameter name
  628.      * @param string Request parameter name (NULL means to use Dataobject param name with its prefix)
  629.      * @param boolean true to set to NULL if empty string value
  630.      * @return boolean true, if value has been set/changed, false if not.
  631.      */
  632.     function set_from_Request( $parname, $var = NULL, $make_null = false )
  633.     {
  634.         if( empty($var) )
  635.         {
  636.             $var = $this->dbprefix.$parname;
  637.         }
  638.  
  639.         return $this->set$parnameget_param($var)$make_null );
  640.     }
  641.  
  642.  
  643.     /**
  644.      * Template function: Displays object ID.
  645.      */
  646.     function ID()
  647.     {
  648.         echo $this->ID;
  649.     }
  650.  
  651.     /**
  652.      * Create icon with dataobject history
  653.      */
  654.     function history_info_icon()
  655.     {
  656.         $history = array();
  657.  
  658.         $UserCache = & get_Cache( 'UserCache' );
  659.  
  660.         // HANDLE CREATOR STUFF
  661.         if( !empty($this->creator_field&& !empty($this->{$this->creator_field}) )
  662.         {    // We have a creator:
  663.             $creator_User = & $UserCache->get_by_ID$this->{$this->creator_field);
  664.  
  665.             if!empty($this->datecreated_field&& !empty($this->{$this->datecreated_field}) )
  666.             {    // We also have a create date:
  667.                 $history[0] = sprintf( T_('Created on %s by %s'), mysql2localedate( $this->{$this->datecreated_field),
  668.                     $creator_User->dget('preferredname') );
  669.             }
  670.             else
  671.             {    // We only have a cretaor:
  672.                 $history[0] = sprintf( T_('Created by %s'), $creator_User->dget('preferredname') );
  673.             }
  674.         }
  675.         elseif( !empty($this->datecreated_field&& !empty($this->{$this->datecreated_field}) )
  676.         {    // We only have a create date:
  677.             $history[0] = sprintf( T_('Created on %s'), mysql2localedate( $this->{$this->datecreated_field) );
  678.         }
  679.  
  680.         // HANDLE LAST UPDATE STUFF
  681.         if( !empty($this->lasteditor_field&& !empty($this->{$this->lasteditor_field}) )
  682.         {    // We have a creator:
  683.             $creator_User = & $UserCache->get_by_ID$this->{$this->lasteditor_field);
  684.  
  685.             if!empty($this->datemodified_field&& !empty($this->{$this->datemodified_field}) )
  686.             {    // We also have a create date:
  687.                 $history[1] = sprintf( T_('Last mod on %s by %s'), mysql2localedate( $this->{$this->datemodified_field),
  688.                     $creator_User->dget('preferredname') );
  689.             }
  690.             else
  691.             {    // We only have a cretaor:
  692.                 $history[1] = sprintf( T_('Last mod by %s'), $creator_User->dget('preferredname') );
  693.             }
  694.         }
  695.         elseif( !empty($this->datemodified_field&& !empty($this->{$this->datemodified_field}) )
  696.         {    // We only have a create date:
  697.             $history[1] = sprintf( T_('Last mod on %s'), mysql2localedate( $this->{$this->datemodified_field) );
  698.         }
  699.  
  700.         return get_icon( 'history', $what = 'imgtag', array( 'title'=>implode( ' - ', $history ) ), true );
  701.     }
  702. }
  703.