b2evolution PHP Cross Reference Blogging Systems

Source: /inc/_core/model/dataobjects/_dataobject.class.php - 895 lines - 28209 bytes - Summary - Text - Print

Description: This file implements the abstract DataObject base class. This file is part of the evoCore framework - {@link http://evocore.net/} See also {@link http://sourceforge.net/projects/evocms/}.

   1  <?php
   2  /**
   3   * This file implements the abstract DataObject base class.
   4   *
   5   * This file is part of the evoCore framework - {@link http://evocore.net/}
   6   * See also {@link http://sourceforge.net/projects/evocms/}.
   7   *
   8   * @copyright (c)2003-2014 by Francois Planque - {@link http://fplanque.com/}
   9   * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
  10   * Parts of this file are copyright (c)2005-2006 by PROGIDISTRI - {@link http://progidistri.com/}.
  11   *
  12   * {@internal License choice
  13   * - If you have received this file as part of a package, please find the license.txt file in
  14   *   the same folder or the closest folder above for complete license terms.
  15   * - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/)
  16   *   then you must choose one of the following licenses before using the file:
  17   *   - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php
  18   *   - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php
  19   * }}
  20   *
  21   * {@internal Open Source relicensing agreement:
  22   * Daniel HAHLER grants Francois PLANQUE the right to license
  23   * Daniel HAHLER's contributions to this file and the b2evolution project
  24   * under any OSI approved OSS license (http://www.opensource.org/licenses/).
  25   *
  26   * PROGIDISTRI S.A.S. grants Francois PLANQUE the right to license
  27   * PROGIDISTRI S.A.S.'s contributions to this file and the b2evolution project
  28   * under any OSI approved OSS license (http://www.opensource.org/licenses/).
  29   * }}
  30   *
  31   * @package evocore
  32   *
  33   * {@internal Below is a list of authors who have contributed to design/coding of this file: }}
  34   * @author fplanque: Francois PLANQUE
  35   * @author blueyed: Daniel HAHLER
  36   * @author mbruneau: Marc BRUNEAU / PROGIDISTRI
  37   *
  38   * @version $Id: _dataobject.class.php 6136 2014-03-08 07:59:48Z manuel $
  39   */
  40  if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
  41  
  42  /**
  43   * Data Object Base Class
  44   *
  45   * This is typically an abstract class, useful only when derived.
  46   *
  47   * @package evocore
  48   * @version beta
  49   * @abstract
  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      var $allow_ID_insert = false;
  64  
  65      /**#@+
  66       * @access private
  67       */
  68      var $dbtablename;
  69      var $dbprefix;
  70      var $dbIDname;
  71      var $datecreated_field;
  72      var $datemodified_field;
  73      var $creator_field;
  74      var $lasteditor_field;
  75      var $dbchanges = array();
  76      /**#@-*/
  77  
  78      /**
  79       * Relations that may restrict deletion.
  80       */
  81      var $delete_restrictions = array();
  82  
  83      /**
  84       * Relations that will cascade deletion.
  85       */
  86      var $delete_cascades = array();
  87  
  88  
  89      /**
  90       * Constructor
  91       *
  92       * @param string Name of table in database
  93       * @param string Prefix of fields in the table
  94       * @param string Name of the ID field (including prefix)
  95       * @param string datetime field name
  96       * @param string datetime field name
  97       * @param string User ID field name
  98       * @param string User ID field name
  99       * @param string datetime field name
 100       */
 101  	function DataObject( $tablename, $prefix = '', $dbIDname = 'ID', $datecreated_field = '', $datemodified_field = '', $creator_field = '', $lasteditor_field = '' )
 102      {
 103          $this->dbtablename        = $tablename;
 104          $this->dbprefix           = $prefix;
 105          $this->dbIDname           = $dbIDname;
 106          $this->datecreated_field  = $datecreated_field;
 107          $this->datemodified_field = $datemodified_field;
 108          $this->creator_field      = $creator_field;
 109          $this->lasteditor_field   = $lasteditor_field;
 110      }
 111  
 112  
 113      /**
 114       * Records a change that will need to be updated in the db
 115       *
 116       * @access protected
 117       * @param string Name of parameter
 118       * @param string DB field type ('string', 'number', 'date', 'dbfield' )
 119       * @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... :@
 120       */
 121  	function dbchange( $dbfieldname, $dbfieldtype, $valuepointer ) // TODO: dh> value by reference? see above..
 122      {
 123          // echo '<br />DB change on :'.$dbfieldname;
 124          $this->dbchanges[$dbfieldname]['type'] = $dbfieldtype;
 125          $this->dbchanges[$dbfieldname]['value'] = $valuepointer ;
 126      }
 127  
 128  
 129      /**
 130       * Update the DB based on previously recorded changes
 131       *
 132       * @param boolean do we want to auto track the mod date?
 133       * @return boolean true on success, false on failure to update, NULL if no update necessary
 134       */
 135  	function dbupdate( $auto_track_modification = true )
 136      {
 137          global $DB, $Plugins, $localtimenow, $current_User;
 138  
 139          if( $this->ID == 0 ) { debug_die( 'New object cannot be updated!' ); }
 140  
 141          if( count( $this->dbchanges ) == 0 )
 142          {
 143              return NULL;    // No changes!
 144          }
 145  
 146          if( $auto_track_modification )
 147          { // We wnat to track modification date and author automatically:
 148              if( !empty($this->datemodified_field) )
 149              {    // We want to track modification date:
 150                  $this->set_param( $this->datemodified_field, 'date', date('Y-m-d H:i:s',$localtimenow) );
 151              }
 152              if( !empty($this->lasteditor_field) && is_object($current_User) )
 153              {    // We want to track last editor:
 154                  // TODO: the current_User is not necessarily the last editor. Item::dbupdate() gets called after incrementing the view for example!
 155                  // fplanque: this should be handled by set() deciding wether the setting changes the last editor or not
 156                  $this->set_param( $this->lasteditor_field, 'number', $current_User->ID );
 157              }
 158          }
 159  
 160          $sql_changes = array();
 161          foreach( $this->dbchanges as $loop_dbfieldname => $loop_dbchange )
 162          {
 163              if( $loop_dbchange['type'] == 'dbfield' )
 164              {    // Set to dbfield only:
 165                  $sql_changes[] = "`$loop_dbfieldname` = `".$loop_dbchange['value'].'` ';
 166                  continue;
 167              }
 168  
 169              // Get changed value (we use eval() to allow constructs like $loop_dbchange['value'] = 'Group->get(\'ID\')'):
 170              eval( '$loop_value = $this->'.$loop_dbchange['value'].';' );
 171              // Prepare matching statement:
 172              if( is_null($loop_value) )
 173              {
 174                  $sql_changes[] = $loop_dbfieldname.' = NULL ';
 175              }
 176              else
 177              {
 178                  switch( $loop_dbchange['type'] )
 179                  {
 180                      case 'date':
 181                      case 'string':
 182                          $sql_changes[] = $loop_dbfieldname." = '".$DB->escape( $loop_value )."' ";
 183                          break;
 184  
 185                      default:
 186                          $sql_changes[] = $loop_dbfieldname." = ".$DB->null($loop_value).' ';
 187                  }
 188              }
 189          }
 190  
 191          // Prepare full statement:
 192          $sql = "UPDATE $this->dbtablename SET ". implode( ', ', $sql_changes ). "
 193                           WHERE $this->dbIDname = $this->ID";
 194          //echo $sql;
 195  
 196          if( ! $DB->query( $sql, 'DataObject::dbupdate()' ) )
 197          {
 198              return false;
 199          }
 200  
 201          // Reset changes in object:
 202          $this->dbchanges = array();
 203  
 204          $Plugins->trigger_event( 'AfterObjectUpdate', $params = array( 'Object' => & $this, 'type' => get_class($this) ) );
 205  
 206          return true;
 207      }
 208  
 209  
 210      /**
 211       * Insert object into DB based on previously recorded changes.
 212       *
 213       * Note: DataObject does not require a matching *Cache object.
 214       * Therefore it will not try to update the Cache.
 215       * If something like that was needed, sth like *Cache->add() should be called.
 216       * ATTENTION: Any dbinsert should typically be followed by a 303 redirect. Updating the Cache before redirect is generally not needed.
 217       *
 218       * @return boolean true on success
 219       */
 220  	function dbinsert()
 221      {
 222          global $DB, $Plugins, $localtimenow, $current_User;
 223  
 224          if( $this->ID != 0 && !$this->allow_ID_insert )
 225          {
 226              die( 'Existing object/object with an ID cannot be inserted!' );
 227          }
 228  
 229          if( !empty($this->datecreated_field) )
 230          { // We want to track creation date:
 231              $this->set_param( $this->datecreated_field, 'date', date('Y-m-d H:i:s',$localtimenow) );
 232          }
 233          if( !empty($this->datemodified_field) )
 234          { // We want to track modification date:
 235              $this->set_param( $this->datemodified_field, 'date', date('Y-m-d H:i:s',$localtimenow) );
 236          }
 237          if( is_logged_in() )
 238          { // Assign user's ID only when user is logged in
 239              if( !empty($this->creator_field) )
 240              { // We want to track creator:
 241                  if( empty($this->creator_user_ID) )
 242                  { // No creator assigned yet, use current user:
 243                      $this->set_param( $this->creator_field, 'number', $current_User->ID );
 244                  }
 245              }
 246              if( !empty($this->lasteditor_field) )
 247              { // We want to track last editor:
 248                  if( empty($this->lastedit_user_ID) )
 249                  { // No editor assigned yet, use current user:
 250                      $this->set_param( $this->lasteditor_field, 'number', $current_User->ID );
 251                  }
 252              }
 253          }
 254  
 255          $sql_fields = array();
 256          $sql_values = array();
 257          foreach( $this->dbchanges as $loop_dbfieldname => $loop_dbchange )
 258          {
 259              // Get changed value (we use eval() to allow constructs like $loop_dbchange['value'] = 'Group->get(\'ID\')'):
 260              eval( '$loop_value = $this->'. $loop_dbchange['value'].';' );
 261              // Prepare matching statement:
 262              $sql_fields[] = $loop_dbfieldname;
 263              if( is_null($loop_value) )
 264              {
 265                  $sql_values[] = 'NULL';
 266              }
 267              else
 268              {
 269                  switch( $loop_dbchange['type'] )
 270                  {
 271                      case 'date':
 272                      case 'string':
 273                          $sql_values[] = $DB->quote( $loop_value );
 274                          break;
 275  
 276                      default:
 277                          $sql_values[] = $DB->null( $loop_value );
 278                  }
 279              }
 280          }
 281  
 282          // Prepare full statement:
 283          $sql = "INSERT INTO {$this->dbtablename} (". implode( ', ', $sql_fields ). ") VALUES (". implode( ', ', $sql_values ). ")";
 284          // echo $sql;
 285  
 286          if( ! $DB->query( $sql, 'DataObject::dbinsert()' ) )
 287          {
 288              return false;
 289          }
 290  
 291  
 292          if( !( $this->allow_ID_insert && $this->ID ) )
 293          {// store ID for newly created db record. Do not if allow_ID_insert is true and $this->ID is not 0
 294  
 295              $this->ID = $DB->insert_id;
 296          }
 297          // Reset changes in object:
 298          $this->dbchanges = array();
 299  
 300          if( !empty( $Plugins ) )
 301          {
 302              $Plugins->trigger_event( 'AfterObjectInsert', $params = array( 'Object' => & $this, 'type' => get_class($this) ) );
 303          }
 304  
 305          return true;
 306      }
 307  
 308  
 309      /**
 310       * Inserts or Updates depending on object state.
 311       *
 312       * @uses dbinsert()
 313       * @uses dbupdate()
 314       * @return boolean true on success, false on failure
 315       */
 316  	function dbsave()
 317      {
 318          if( $this->ID == 0 )
 319          {    // Object not serialized yet, let's insert!
 320              // echo 'INSERT';
 321              return $this->dbinsert();
 322          }
 323          else
 324          {    // Object already serialized, let's update!
 325              // echo 'UPDATE';
 326              return $this->dbupdate();
 327          }
 328      }
 329  
 330  
 331      /**
 332       * Delete object from DB.
 333       *
 334       * @return boolean true on success
 335       */
 336  	function dbdelete( $ignore_restrictions = array() )
 337      {
 338          global $DB, $Messages, $Plugins, $db_config;
 339  
 340          if( $this->ID == 0 ) { debug_die( 'Non persistant object cannot be deleted!' ); }
 341  
 342          if( count($this->delete_cascades) )
 343          {    // The are cascading deletes to be performed
 344  
 345              // Start transaction:
 346              $DB->begin();
 347  
 348              if( ! $this->check_delete( T_('Delete restriction error:'), $ignore_restrictions ) )
 349              { // Some restrictions still prevent deletion
 350                  // Note: This restrictions must be handled previously before dbdelete is called.
 351                  // If this code is executed it means there is an impelmentation issue and restricitons must be check there.
 352                  $DB->rollback();
 353                  return false;
 354              }
 355  
 356              foreach( $this->delete_cascades as $restriction )
 357              {
 358                  if( !isset( $db_config['aliases'][$restriction['table']] ) )
 359                  {    // We have no declaration for this table, we consider we don't deal with this table in this app:
 360                      continue;
 361                  }
 362  
 363                  // add more where condition
 364                  $more_restriction = '';
 365                  if( isset( $restriction['and_condition'] ) )
 366                  {
 367                      $more_restriction .= ' AND ( '.$restriction['and_condition'].' )';
 368                  }
 369  
 370                  $DB->query( '
 371                      DELETE FROM '.$restriction['table'].'
 372                      WHERE '.$restriction['fk'].' = '.$this->ID.$more_restriction,
 373                      'Cascaded delete' );
 374              }
 375          }
 376  
 377          // Delete this (main/parent) object:
 378          $DB->query( "
 379              DELETE FROM $this->dbtablename
 380              WHERE $this->dbIDname = $this->ID",
 381              'Main delete' );
 382  
 383          $Plugins->trigger_event( 'AfterObjectDelete', $params = array( 'Object' => & $this, 'type' => get_class($this) ) );
 384  
 385          if( count($this->delete_cascades) )
 386          {    // There were cascading deletes
 387  
 388              // End transaction:
 389              $DB->commit();
 390          }
 391  
 392          // Just in case... remember this object has been deleted from DB!
 393          $this->ID = 0;
 394  
 395          return true;
 396      }
 397  
 398  
 399      /**
 400       * Check existence of specified value in unique field.
 401       *
 402       * @param string Name of unique field  OR array of Names (for UNIQUE index with MULTIPLE fields)
 403       * @param mixed specified value        OR array of Values (for UNIQUE index with MULTIPLE fields)
 404       * @return int ID if value exists otherwise NULL/false
 405       */
 406  	function dbexists( $unique_fields, $values )
 407      {
 408          global $DB;
 409  
 410          if( is_array( $unique_fields ) && is_array( $values ) )
 411          {    // UNIQUE index consists of MULTIPLE fields
 412              $sql_where = array();
 413              foreach( $unique_fields as $i => $unique_field )
 414              {
 415                  $sql_where[] = $unique_field." = ".$DB->quote( $values[$i] );
 416              }
 417              $sql_where = implode( ' AND ', $sql_where );
 418          }
 419          else
 420          {    // UNIQUE index consists of ONE field
 421              $sql_where = $unique_fields." = ".$DB->quote( $values );
 422          }
 423  
 424          $sql = "SELECT $this->dbIDname
 425                            FROM $this->dbtablename
 426                         WHERE $sql_where
 427                             AND $this->dbIDname != $this->ID";
 428  
 429          return $DB->get_var( $sql );
 430      }
 431  
 432  
 433      /**
 434       * Check relations for restrictions or cascades.
 435       * @todo dh> Add link to affected items, e.g. items when trying to delete an attachment, where it gets used.
 436       * 
 437       * @return Messages object with the restriction messages
 438       */
 439  	function check_relations( $what, $ignore = array(), $addlink = false )
 440      {
 441          global $DB;
 442  
 443          $restriction_Messages = new Messages();
 444  
 445          foreach( $this->$what as $restriction )
 446          {
 447              if( !in_array( $restriction['fk'], $ignore ) )
 448              {
 449                  if( $addlink )
 450                  { // get linked objects and add a link
 451                      $link = '';
 452                      if( $addlink )
 453                      { // get link from derived class
 454                          $link = $this->get_restriction_link( $restriction );
 455                      }
 456                      // without restriction => don't display the message
 457                      if( $link != '' )
 458                      {
 459                          $restriction_Messages->add( $link );
 460                      }
 461                  }
 462                  else
 463                  { // count and show how many object is connected
 464                      $extra_condition = ( isset( $restriction['and_condition'] ) ) ? ' AND '.$restriction['and_condition'] : '';
 465                      $count = $DB->get_var(
 466                      'SELECT COUNT(*)
 467                         FROM '.$restriction['table'].'
 468                        WHERE '.$restriction['fk'].' = '.$this->ID.$extra_condition,
 469                      0, 0, 'restriction/cascade check' );
 470                      if( $count )
 471                      {
 472                          $restriction_Messages->add( sprintf( $restriction['msg'], $count ), 'error' );
 473                      }
 474                  }
 475              }
 476          }
 477  
 478          return $restriction_Messages;
 479      }
 480  
 481  
 482      /**
 483       * Check relations for restrictions before deleting
 484       *
 485       * @param string
 486       * @param array list of foreign keys to ignore
 487       * @return boolean true if no restriction prevents deletion
 488       */
 489  	function check_delete( $restrict_title, $ignore = array(), $addlink = false )
 490      {
 491          global $Messages;
 492  
 493          // Check restrictions:
 494          $restriction_Messages = $this->check_relations( 'delete_restrictions', $ignore, $addlink );
 495  
 496          if( $restriction_Messages->count() )
 497          {    // There are restrictions:
 498              $head = $restrict_title.' '.T_('The following relations prevent deletion:');
 499              $foot = T_('Please delete related objects before you proceed.');
 500              $final_message = $restriction_Messages->display( $head, $foot, false, false );
 501              $Messages->add( $final_message, 'error' );
 502              return false;    // Can't delete
 503          }
 504  
 505          return true;    // can delete
 506      }
 507  
 508  
 509      /**
 510       * Displays form to confirm deletion of this object
 511       *
 512       * @param string Title for confirmation
 513       * @param string crumb name
 514       * @param string "action" param value to use (hidden field)
 515       * @param array Hidden keys (apart from "action")
 516       * @param array Additional messages for restriction messages, array( '0' - message text, '1' - message type )
 517       */
 518  	function confirm_delete( $confirm_title, $crumb_name, $delete_action, $hiddens, $additional_messages = array() )
 519      {
 520          global $Messages;
 521  
 522          $block_item_Widget = new Widget( 'block_item' );
 523  
 524          $block_item_Widget->title = $confirm_title;
 525          $block_item_Widget->disp_template_replaced( 'block_start' );
 526  
 527          $restriction_Messages = $this->check_relations( 'delete_cascades' );
 528  
 529          if( !empty( $additional_messages ) )
 530          { // Initialaize additional messages
 531              foreach( $additional_messages as $additional_message )
 532              {
 533                  $restriction_Messages->add( $additional_message[0], $additional_message[1] );
 534              }
 535          }
 536  
 537          if( $restriction_Messages->count() )
 538          {    // The will be cascading deletes, issue WARNING:
 539              echo '<h3>'.T_('WARNING: Deleting this object will also delete:').'</h3>';
 540              $restriction_Messages->display( '', '' );
 541          }
 542  
 543          echo '<p class="warning">'.$confirm_title.'</p>';
 544          echo '<p class="warning">'.T_('THIS CANNOT BE UNDONE!').'</p>';
 545  
 546          $redirect_to = param( 'redirect_to', 'url', '' );
 547  
 548          $Form = new Form( '', 'form_confirm', 'get', '' );
 549  
 550          $Form->begin_form( 'inline' );
 551              $Form->add_crumb( $crumb_name );
 552              $Form->hiddens_by_key( $hiddens );
 553              $Form->hidden( 'action', $delete_action );
 554              $Form->hidden( 'confirm', 1 );
 555              $Form->hidden( 'redirect_to', $redirect_to );
 556              $Form->button( array( 'submit', '', T_('I am sure!'), 'DeleteButton' ) );
 557          $Form->end_form();
 558  
 559          $Form = new Form( $redirect_to, 'form_cancel', 'get', '' );
 560  
 561          $Form->begin_form( 'inline' );
 562              if( empty( $redirect_to ) )
 563              { // If redirect url is not defined we should go to current url after cancel action
 564                  $Form->hiddens_by_key( $hiddens );
 565              }
 566              $Form->button( array( 'submit', '', T_('CANCEL'), 'CancelButton' ) );
 567          $Form->end_form();
 568  
 569          $block_item_Widget->disp_template_replaced( 'block_end' );
 570          return true;
 571      }
 572  
 573  
 574      /**
 575       * Get a member param by its name
 576       *
 577       * @param mixed Name of parameter
 578       * @return mixed Value of parameter
 579       */
 580  	function get( $parname )
 581      {
 582          return $this->$parname;
 583      }
 584  
 585  
 586      /**
 587       * Get a ready-to-display member param by its name
 588       *
 589       * Same as disp but don't echo
 590       *
 591       * @param string Name of parameter
 592       * @param string Output format, see {@link format_to_output()}
 593       */
 594  	function dget( $parname, $format = 'htmlbody' )
 595      {
 596          // Note: we call get again because of derived objects specific handlers !
 597          return format_to_output( $this->get($parname), $format );
 598      }
 599  
 600  
 601      /**
 602       * Display a member param by its name
 603       *
 604       * @param string Name of parameter
 605       * @param string Output format, see {@link format_to_output()}
 606       */
 607  	function disp( $parname, $format = 'htmlbody' )
 608      {
 609          // Note: we call get again because of derived objects specific handlers !
 610          echo format_to_output( $this->get($parname), $format );
 611      }
 612  
 613  
 614      /**
 615       * Set param value
 616       *
 617       * By default, all values will be considered strings
 618       *
 619       * @param string parameter name
 620       * @param mixed parameter value
 621       * @param boolean true to set to NULL if empty value
 622       * @return boolean true, if a value has been set; false if it has not changed
 623       */
 624  	function set( $parname, $parvalue, $make_null = false )
 625      {
 626          return $this->set_param( $parname, 'string', $parvalue, $make_null );
 627      }
 628  
 629  
 630      /**
 631       * Set param value.
 632       *
 633       * @param string Name of parameter
 634       * @param string DB field type ('string', 'number', 'date' )
 635       * @param mixed Value of parameter
 636       * @param boolean true to set to NULL if empty string value
 637       * @return boolean true, if value has been set/changed, false if not.
 638       */
 639  	function set_param( $parname, $fieldtype, $parvalue, $make_null = false )
 640      {
 641          global $Debuglog;
 642  
 643          $dbfield = $this->dbprefix.$parname;
 644  
 645          // Set value:
 646          // fplanque: Note: I am changing the "make NULL" test to differentiate between 0 and NULL .
 647          // There might be side effects. In this case it would be better to fix them before coming here.
 648          // i-e: transform 0 to ''
 649          $new_value = ($make_null && ($parvalue === '')) ? NULL : $parvalue;
 650  
 651          /* Tblue> Problem: All class member variables originating from the
 652           *                 DB are strings (unless they were NULL in the DB,
 653           *                 then they are set to NULL by the PHP MySQL
 654           *                 extension).
 655           *                 If we pass an integer or a double to this function,
 656           *                 the corresponding member variable gets changed
 657           *                 on every call, because its type is 'string' and
 658           *                 we compare using the === operator. Using the
 659           *                 == operator would be a bad idea, though, because
 660           *                 somebody could pass a NULL value to this function.
 661           *                 If the member variable then is set to 0, then
 662           *                 0 equals NULL and the member variable does not
 663           *                 get updated at all!
 664           *                 Thus, using the === operator is correct.
 665           *       Solution: If $fieldtype is 'number' and the type of the
 666           *                 passed value is either integer or double, we
 667           *                 convert it to a string (no data loss). The
 668           *                 member variable and the passed value can then
 669           *                 be correctly compared using the === operator.
 670           *  fp> It would be nicer to convert numeric values to ints & floats at load time in class constructor  x=(int)$y->value  or sth.
 671           * THIS IS EXPERIMENTAL! Feel free to revert if something does not
 672           * work as expected.
 673           */
 674          if( $fieldtype == 'number' && ( is_int( $new_value ) || is_float( $new_value ) ) )
 675          {
 676              settype( $new_value, 'string' );
 677          }
 678  
 679          //$Debuglog->add( $this->dbtablename.' object; $fieldtype = '.$fieldtype.'; type of $this->'.$parname.' = '.gettype( @$this->$parname ).'; type of $new_value = '.gettype( $new_value ), 'dataobjects' );
 680  /* >old
 681          if( !isset($this->$parname) )
 682          {    // This property has never been set before, set it to NULL now in order for tests to work:
 683              $this->$parname = NULL;
 684          }
 685  
 686  
 687          /* blueyed>
 688          TODO: there's a bug here: you cannot use set_param('foo', 'number', 0), if the $parname member
 689                has not been set before or is null!!
 690                What about just:
 691                ( isset($this->$parname) && $this->$parname === $new_value )
 692                This would also eliminate the isset() check from above.
 693                IIRC you've once said here that '===' would be too expensive and I would misuse the DataObjects,
 694                but IMHO what we have now is not much faster and buggy anyway..
 695              fp> okay let's give it a try...
 696          if( (!is_null($new_value) && $this->$parname == $new_value)
 697              || (is_null($this->$parname) && is_null($new_value)) )
 698  <old */
 699          if( (isset($this->$parname) && $this->$parname === $new_value)
 700              || ( ! isset($this->$parname) && ! isset($new_value) ) )
 701          {    // Value has not changed (we need 2 tests, for NULL and for NOT NULL value pairs)
 702              $Debuglog->add( $this->dbtablename.' object, already set to same value: '.$parname.'/'.$dbfield.' = '.var_export( @$this->$parname, true ), 'dataobjects' );
 703              // echo '<br />'.$this->dbtablename.' object, already set to same value: '.$parname.'/'.$dbfield.' = '.$this->$parname;
 704  
 705              return false;
 706          }
 707          else
 708          {
 709              // Set the value in the object:
 710              // echo '<br/>'.$this->dbtablename.' object, setting param '.$parname.'/'.$dbfield.' to '.$new_value.(is_null($new_value)?' NULL':'').' (was:'.$this->$parname.(is_null($this->$parname)?' NULL':'').')';
 711              $this->$parname = $new_value;
 712              $Debuglog->add( $this->dbtablename.' object, setting param '.$parname.'/'.$dbfield.' to '.$this->$parname, 'dataobjects' );
 713  
 714              // Remember change for later db update:
 715              $this->dbchange( $dbfield, $fieldtype, $parname );
 716  
 717              return true;
 718          }
 719      }
 720  
 721  
 722      /**
 723       * Set a parameter from a Request form value.
 724       *
 725       * @param string Dataobject parameter name
 726       * @param string Request parameter name (NULL means to use Dataobject param name with its prefix)
 727       * @param boolean true to set to NULL if empty string value
 728       * @return boolean true, if value has been set/changed, false if not.
 729       */
 730  	function set_from_Request( $parname, $var = NULL, $make_null = false, $cleanup_function = NULL )
 731      {
 732          if( empty($var) )
 733          {
 734              $var = $this->dbprefix.$parname;
 735          }
 736  
 737          $value = get_param($var);
 738  
 739          if( !empty($cleanup_function) )
 740          {    //We want to apply a cleanup function
 741              $value = $cleanup_function($value);
 742              set_param($var, $value);
 743          }
 744  
 745          return $this->set( $parname, $value, $make_null );
 746      }
 747  
 748  
 749      /**
 750       * Set a string parameter from a Request form value.
 751       *
 752       * @param string Dataobject parameter name
 753       * @param boolean true to set to NULL if empty string value
 754       * @param string name of function used to clean up input
 755       * @param string name of fucntion used to validate input (TODO)
 756       * @return boolean true, if value is required
 757       */
 758  	function set_string_from_param( $parname, $required = false, $cleanup_function = NULL, $validation_function = NULL, $error_message = NULL )
 759      {
 760          $var = $this->dbprefix.$parname;
 761  
 762          $value = param( $var, 'string' );
 763  
 764          if( !empty($cleanup_function) )
 765          {    // We want to apply a cleanup function:
 766              $GLOBALS[$var] = $value = $cleanup_function( $value );
 767          }
 768  
 769          if( $required )
 770          {
 771              param_check_not_empty( $var );
 772          }
 773  
 774          if( $validation_function != NULL )
 775          {
 776              param_validate( $var, $validation_function, $required, $error_message );
 777          }
 778  
 779          return $this->set( $parname, $value, ! $required );
 780      }
 781  
 782  
 783      /**
 784       * Template function: Displays object ID.
 785       */
 786      function ID()
 787      {
 788          echo $this->ID;
 789      }
 790  
 791  
 792      /**
 793       * Generate help title text for action
 794       *
 795       * @param string action code: edit, delete, etc.
 796       * @return string translated help string
 797       */
 798  	function get_action_title( $action )
 799      {
 800          switch( $action )
 801          {
 802              case 'edit': return T_('Edit this object...');
 803              case 'copy': return T_('Duplicate this object...');
 804              case 'delete': return T_('Delete this object!');
 805              default:
 806                  return '';
 807          }
 808      }
 809  
 810  
 811      /**
 812       * Generate requested action icon depending on perm
 813       */
 814  	function action_icon( $action, $help_texts = array() )
 815      {
 816          if( ! $this->check_perm($action, false) )
 817          {    // permission denied:
 818              return '';
 819          }
 820  
 821          return action_icon( $this->get_action_title($action), $action,
 822                                    regenerate_url( 'action', $this->dbIDname.'='.$this->ID.'&amp;action='.$action
 823                                                                    .'&amp;'.url_crumb(strtolower(get_class($this))) ) );
 824      }
 825  
 826  
 827      /**
 828       * Generate requested action link depending on perm
 829       */
 830  	function action_link( $action, $link_text, $help_texts = array() )
 831      {
 832          if( ! $this->check_perm($action, false) )
 833          {    // permission denied:
 834              return '';
 835          }
 836  
 837          return '<a href="'.regenerate_url( 'action', $this->dbIDname.'='.$this->ID.'&amp;action='.$action )
 838                          .'" title="'.$this->get_action_title($action).'">'.$link_text.'</a>';
 839      }
 840  
 841  
 842      /**
 843       * Create icon with dataobject history
 844       */
 845  	function history_info_icon()
 846      {
 847          $history = array();
 848  
 849          $UserCache = & get_UserCache();
 850  
 851          // HANDLE CREATOR STUFF
 852          if( !empty($this->creator_field) && !empty($this->{$this->creator_field}) )
 853          {    // We have a creator:
 854              $creator_User = & $UserCache->get_by_ID( $this->{$this->creator_field} );
 855  
 856              if( !empty($this->datecreated_field) && !empty($this->{$this->datecreated_field}) )
 857              {    // We also have a create date:
 858                  $history[0] = sprintf( T_('Created on %s by %s'), mysql2localedate( $this->{$this->datecreated_field} ),
 859                      $creator_User->dget('preferredname') );
 860              }
 861              else
 862              {    // We only have a cretaor:
 863                  $history[0] = sprintf( T_('Created by %s'), $creator_User->dget('preferredname') );
 864              }
 865          }
 866          elseif( !empty($this->datecreated_field) && !empty($this->{$this->datecreated_field}) )
 867          {    // We only have a create date:
 868              $history[0] = sprintf( T_('Created on %s'), mysql2localedate( $this->{$this->datecreated_field} ) );
 869          }
 870  
 871          // HANDLE LAST UPDATE STUFF
 872          if( !empty($this->lasteditor_field) && !empty($this->{$this->lasteditor_field}) )
 873          {    // We have a creator:
 874              $creator_User = & $UserCache->get_by_ID( $this->{$this->lasteditor_field} );
 875  
 876              if( !empty($this->datemodified_field) && !empty($this->{$this->datemodified_field}) )
 877              {    // We also have a create date:
 878                  $history[1] = sprintf( T_('Last mod on %s by %s'), mysql2localedate( $this->{$this->datemodified_field} ),
 879                      $creator_User->dget('preferredname') );
 880              }
 881              else
 882              {    // We only have a cretaor:
 883                  $history[1] = sprintf( T_('Last mod by %s'), $creator_User->dget('preferredname') );
 884              }
 885          }
 886          elseif( !empty($this->datemodified_field) && !empty($this->{$this->datemodified_field}) )
 887          {    // We only have a create date:
 888              $history[1] = sprintf( T_('Last mod on %s'), mysql2localedate( $this->{$this->datemodified_field} ) );
 889          }
 890  
 891          return get_icon( 'history', 'imgtag', array( 'title'=>implode( ' - ', $history ) ), true );
 892      }
 893  }
 894  
 895  ?>

title

Description

title

Description

title

Description

title

title

Body