| [ Index ] |
|
Code source de b2evolution 2.1.0-beta |
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-2007 by Francois PLANQUE - {@link http://fplanque.net/} 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,v 1.1 2007/06/25 10:58:56 fplanque Exp $ 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 /**#@+ 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 // echo '<br />DB change on :'.$dbfieldname; 121 $this->dbchanges[$dbfieldname]['type'] = $dbfieldtype; 122 $this->dbchanges[$dbfieldname]['value'] = $valuepointer ; 123 } 124 125 126 /** 127 * Update the DB based on previously recorded changes 128 * 129 * @return boolean true on success, false on failure to update, NULL if no update necessary 130 */ 131 function dbupdate() 132 { 133 global $DB, $localtimenow, $current_User; 134 135 if( $this->ID == 0 ) { debug_die( 'New object cannot be updated!' ); } 136 137 if( count( $this->dbchanges ) == 0 ) 138 { 139 return NULL; // No changes! 140 } 141 142 if( !empty($this->datemodified_field) ) 143 { // We want to track modification date: 144 $this->set_param( $this->datemodified_field, 'date', date('Y-m-d H:i:s',$localtimenow) ); 145 } 146 if( !empty($this->lasteditor_field) && is_object($current_User) ) 147 { // We want to track last editor: 148 // TODO: the current_User is not necessarily the last editor. Item::dbupdate() gets called after incrementing the view for example! 149 // fplanque: this should be handled by set() deciding wether the setting changes the last editor or not 150 $this->set_param( $this->lasteditor_field, 'number', $current_User->ID ); 151 } 152 153 154 $sql_changes = array(); 155 foreach( $this->dbchanges as $loop_dbfieldname => $loop_dbchange ) 156 { 157 // Get changed value (we use eval() to allow constructs like $loop_dbchange['value'] = 'Group->get(\'ID\')'): 158 eval( '$loop_value = $this->'.$loop_dbchange['value'].';' ); 159 // Prepare matching statement: 160 if( is_null($loop_value) ) 161 { 162 $sql_changes[] = $loop_dbfieldname.' = NULL '; 163 } 164 else 165 { 166 switch( $loop_dbchange['type'] ) 167 { 168 case 'date': 169 case 'string': 170 $sql_changes[] = $loop_dbfieldname." = '".$DB->escape( $loop_value )."' "; 171 break; 172 173 default: 174 $sql_changes[] = $loop_dbfieldname." = ".$DB->null($loop_value).' '; 175 } 176 } 177 } 178 179 // Prepare full statement: 180 $sql = "UPDATE $this->dbtablename SET ". implode( ', ', $sql_changes ). " 181 WHERE $this->dbIDname = $this->ID"; 182 //echo $sql; 183 184 if( ! $DB->query( $sql, 'DataObject::dbupdate()' ) ) 185 { 186 return false; 187 } 188 189 // Reset changes in object: 190 $this->dbchanges = array(); 191 192 return true; 193 } 194 195 196 /** 197 * Insert object into DB based on previously recorded changes. 198 * 199 * @return boolean true on success 200 */ 201 function dbinsert() 202 { 203 global $DB, $localtimenow, $current_User; 204 205 if( $this->ID != 0 ) die( 'Existing object cannot be inserted!' ); 206 207 if( !empty($this->datecreated_field) ) 208 { // We want to track creation date: 209 $this->set_param( $this->datecreated_field, 'date', date('Y-m-d H:i:s',$localtimenow) ); 210 } 211 if( !empty($this->datemodified_field) ) 212 { // We want to track modification date: 213 $this->set_param( $this->datemodified_field, 'date', date('Y-m-d H:i:s',$localtimenow) ); 214 } 215 if( !empty($this->creator_field) ) 216 { // We want to track creator: 217 if( empty($this->creator_user_ID) ) 218 { // No creator assigned yet, use current user: 219 $this->set_param( $this->creator_field, 'number', $current_User->ID ); 220 } 221 } 222 if( !empty($this->lasteditor_field) ) 223 { // We want to track last editor: 224 if( empty($this->lastedit_user_ID) ) 225 { // No editor assigned yet, use current user: 226 $this->set_param( $this->lasteditor_field, 'number', $current_User->ID ); 227 } 228 } 229 230 $sql_fields = array(); 231 $sql_values = array(); 232 foreach( $this->dbchanges as $loop_dbfieldname => $loop_dbchange ) 233 { 234 // Get changed value (we use eval() to allow constructs like $loop_dbchange['value'] = 'Group->get(\'ID\')'): 235 eval( '$loop_value = $this->'. $loop_dbchange['value'].';' ); 236 // Prepare matching statement: 237 $sql_fields[] = $loop_dbfieldname; 238 if( is_null($loop_value) ) 239 { 240 $sql_values[] = 'NULL'; 241 } 242 else 243 { 244 switch( $loop_dbchange['type'] ) 245 { 246 case 'date': 247 case 'string': 248 $sql_values[] = $DB->quote( $loop_value ); 249 break; 250 251 default: 252 $sql_values[] = $DB->null( $loop_value ); 253 } 254 } 255 } 256 257 // Prepare full statement: 258 $sql = "INSERT INTO {$this->dbtablename} (". implode( ', ', $sql_fields ). ") VALUES (". implode( ', ', $sql_values ). ")"; 259 // echo $sql; 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 == 0 ) 286 { // Object not serialized yet, let's insert! 287 // echo 'INSERT'; 288 return $this->dbinsert(); 289 } 290 else 291 { // Object already serialized, let's update! 292 // echo 'UPDATE'; 293 return $this->dbupdate(); 294 } 295 } 296 297 298 /** 299 * Delete object from DB. 300 * 301 * @return boolean true on success 302 */ 303 function dbdelete() 304 { 305 global $DB, $Messages, $db_config; 306 307 if( $this->ID == 0 ) { debug_die( 'Non persistant object cannot be deleted!' ); } 308 309 if( count($this->delete_cascades) ) 310 { // The are cascading deletes to be performed 311 312 // Start transaction: 313 $DB->begin(); 314 315 foreach( $this->delete_cascades as $restriction ) 316 { 317 if( !isset( $db_config['aliases'][$restriction['table']] ) ) 318 { // We have no declaration for this table, we consider we don't deal with this table in this app: 319 continue; 320 } 321 322 $DB->query( ' 323 DELETE FROM '.$restriction['table'].' 324 WHERE '.$restriction['fk'].' = '.$this->ID, 325 'Cascaded delete' ); 326 } 327 } 328 329 // Delete this (main/parent) object: 330 $DB->query( " 331 DELETE FROM $this->dbtablename 332 WHERE $this->dbIDname = $this->ID", 333 'Main delete' ); 334 335 if( count($this->delete_cascades) ) 336 { // There were cascading deletes 337 338 // End transaction: 339 $DB->commit(); 340 } 341 342 // Just in case... remember this object has been deleted from DB! 343 $this->ID = 0; 344 345 return true; 346 } 347 348 349 /** 350 * Check relations for restrictions or cascades 351 */ 352 function check_relations( $what, $ignore = array() ) 353 { 354 global $DB, $Messages; 355 356 foreach( $this->$what as $restriction ) 357 { 358 if( !in_array( $restriction['fk'], $ignore ) ) 359 { 360 $count = $DB->get_var( 361 'SELECT COUNT(*) 362 FROM '.$restriction['table'].' 363 WHERE '.$restriction['fk'].' = '.$this->ID, 364 0, 0, 'restriction/cascade check' ); 365 if( $count ) 366 { 367 $Messages->add( sprintf( $restriction['msg'], $count ), 'restrict' ); 368 } 369 } 370 } 371 } 372 373 374 /** 375 * Check relations for restrictions before deleting 376 * 377 * @param string 378 * @param array list of foreign keys to ignore 379 * @return boolean true if no restriction prevents deletion 380 */ 381 function check_delete( $restrict_title, $ignore = array() ) 382 { 383 global $Messages; 384 385 // Check restrictions: 386 $this->check_relations( 'delete_restrictions', $ignore ); 387 388 if( $Messages->count('restrict') ) 389 { // There are restrictions: 390 $Messages->head = array( 391 'container' => $restrict_title, 392 'restrict' => T_('The following relations prevent deletion:') 393 ); 394 $Messages->foot = T_('Please delete related objects before you proceed.'); 395 return false; // Can't delete 396 } 397 398 return true; // can delete 399 } 400 401 402 /** 403 * Displays form to confirm deletion of this object 404 * 405 * @param string Title for confirmation 406 * @param string "action" param value to use (hidden field) 407 * @param array Hidden keys (apart from "action") 408 * @param string most of the time we don't need a cancel action since we'll want to return to the default display 409 */ 410 function confirm_delete( $confirm_title, $delete_action, $hiddens, $cancel_action = NULL ) 411 { 412 global $Messages; 413 414 // No restrictions, ask for confirmation: 415 echo '<div class="panelinfo">'; 416 echo '<h2>'.$confirm_title.'</h2>'; 417 418 $this->check_relations( 'delete_cascades' ); 419 420 if( $Messages->count('restrict') ) 421 { // The will be cascading deletes, issue WARNING: 422 echo '<h3>'.T_('WARNING: Deleting this object will also delete:').'</h3>'; 423 $Messages->display( '', '', true, 'restrict', NULL, NULL, NULL ); 424 } 425 426 echo '<h3>'.T_('THIS CANNOT BE UNDONE!').'</h3>'; 427 428 $Form = & new Form( '', 'form_confirm', 'get', '' ); 429 430 $Form->begin_form( 'inline' ); 431 $Form->hiddens_by_key( $hiddens ); 432 $Form->hidden( 'action', $delete_action ); 433 $Form->hidden( 'confirm', 1 ); 434 $Form->button( array( 'submit', '', T_('I am sure!'), 'DeleteButton' ) ); 435 $Form->end_form(); 436 437 $Form = & new Form( '', 'form_cancel', 'get', '' ); 438 439 $Form->begin_form( 'inline' ); 440 $Form->hiddens_by_key( $hiddens ); 441 if( !empty( $cancel_action ) ) 442 { 443 $Form->hidden( 'action', $cancel_action ); 444 } 445 $Form->button( array( 'submit', '', T_('CANCEL'), 'CancelButton' ) ); 446 $Form->end_form(); 447 448 echo '</div>'; 449 return true; 450 } 451 452 453 /** 454 * Get a member param by its name 455 * 456 * @param mixed Name of parameter 457 * @return mixed Value of parameter 458 */ 459 function get( $parname ) 460 { 461 return $this->$parname; 462 } 463 464 465 /** 466 * Get a ready-to-display member param by its name 467 * 468 * Same as disp but don't echo 469 * 470 * @param string Name of parameter 471 * @param string Output format, see {@link format_to_output()} 472 */ 473 function dget( $parname, $format = 'htmlbody' ) 474 { 475 // Note: we call get again because of derived objects specific handlers ! 476 return format_to_output( $this->get($parname), $format ); 477 } 478 479 480 /** 481 * Display a member param by its name 482 * 483 * @param string Name of parameter 484 * @param string Output format, see {@link format_to_output()} 485 */ 486 function disp( $parname, $format = 'htmlbody' ) 487 { 488 // Note: we call get again because of derived objects specific handlers ! 489 echo format_to_output( $this->get($parname), $format ); 490 } 491 492 493 /** 494 * Set param value 495 * 496 * By default, all values will be considered strings 497 * 498 * @param string parameter name 499 * @param mixed parameter value 500 * @param boolean true to set to NULL if empty value 501 * @return boolean true, if a value has been set; false if it has not changed 502 */ 503 function set( $parname, $parvalue, $make_null = false ) 504 { 505 return $this->set_param( $parname, 'string', $parvalue, $make_null ); 506 } 507 508 509 /** 510 * Set param value. 511 * 512 * @param string Name of parameter 513 * @param string DB field type ('string', 'number', 'date' ) 514 * @param mixed Value of parameter 515 * @param boolean true to set to NULL if empty string value 516 * @return boolean true, if value has been set/changed, false if not. 517 */ 518 function set_param( $parname, $fieldtype, $parvalue, $make_null = false ) 519 { 520 global $Debuglog; 521 522 $dbfield = $this->dbprefix.$parname; 523 524 // Set value: 525 // fplanque: Note: I am changing the "make NULL" test to differentiate between 0 and NULL . 526 // There might be side effects. In this case it would be better to fix them before coming here. 527 // i-e: transform 0 to '' 528 $new_value = ($make_null && ($parvalue === '')) ? NULL : $parvalue; 529 530 /* >old 531 if( !isset($this->$parname) ) 532 { // This property has never been set before, set it to NULL now in order for tests to work: 533 $this->$parname = NULL; 534 } 535 536 537 /* blueyed> 538 TODO: there's a bug here: you cannot use set_param('foo', 'number', 0), if the $parname member 539 has not been set before or is null!! 540 What about just: 541 ( isset($this->$parname) && $this->$parname === $new_value ) 542 This would also eliminate the isset() check from above. 543 IIRC you've once said here that '===' would be too expensive and I would misuse the DataObjects, 544 but IMHO what we have now is not much faster and buggy anyway.. 545 fp> okay let's give it a try... 546 if( (!is_null($new_value) && $this->$parname == $new_value) 547 || (is_null($this->$parname) && is_null($new_value)) ) 548 <old */ 549 if( (isset($this->$parname) && $this->$parname === $new_value) 550 || ( ! isset($this->$parname) && ! isset($new_value) ) ) 551 { // Value has not changed (we need 2 tests, for NULL and for NOT NULL value pairs) 552 $Debuglog->add( $this->dbtablename.' object, already set to same value: '.$parname.'/'.$dbfield.' = '.var_export( @$this->$parname, true ), 'dataobjects' ); 553 // echo '<br />'.$this->dbtablename.' object, already set to same value: '.$parname.'/'.$dbfield.' = '.$this->$parname; 554 555 return false; 556 } 557 else 558 { 559 // Set the value in the object: 560 // 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':'').')'; 561 $this->$parname = $new_value; 562 $Debuglog->add( $this->dbtablename.' object, setting param '.$parname.'/'.$dbfield.' to '.$this->$parname, 'dataobjects' ); 563 564 // Remember change for later db update: 565 $this->dbchange( $dbfield, $fieldtype, $parname ); 566 567 return true; 568 } 569 } 570 571 572 /** 573 * Set a parameter from a Request form value. 574 * 575 * @param string Dataobject parameter name 576 * @param string Request parameter name (NULL means to use Dataobject param name with its prefix) 577 * @param boolean true to set to NULL if empty string value 578 * @return boolean true, if value has been set/changed, false if not. 579 */ 580 function set_from_Request( $parname, $var = NULL, $make_null = false ) 581 { 582 if( empty($var) ) 583 { 584 $var = $this->dbprefix.$parname; 585 } 586 587 return $this->set( $parname, get_param($var), $make_null ); 588 } 589 590 591 /** 592 * Template function: Displays object ID. 593 */ 594 function ID() 595 { 596 echo $this->ID; 597 } 598 599 /** 600 * Create icon with dataobject history 601 */ 602 function history_info_icon() 603 { 604 $history = array(); 605 606 $UserCache = & get_Cache( 'UserCache' ); 607 608 // HANDLE CREATOR STUFF 609 if( !empty($this->creator_field) && !empty($this->{$this->creator_field}) ) 610 { // We have a creator: 611 $creator_User = & $UserCache->get_by_ID( $this->{$this->creator_field} ); 612 613 if( !empty($this->datecreated_field) && !empty($this->{$this->datecreated_field}) ) 614 { // We also have a create date: 615 $history[0] = sprintf( T_('Created on %s by %s'), mysql2localedate( $this->{$this->datecreated_field} ), 616 $creator_User->dget('preferredname') ); 617 } 618 else 619 { // We only have a cretaor: 620 $history[0] = sprintf( T_('Created by %s'), $creator_User->dget('preferredname') ); 621 } 622 } 623 elseif( !empty($this->datecreated_field) && !empty($this->{$this->datecreated_field}) ) 624 { // We only have a create date: 625 $history[0] = sprintf( T_('Created on %s'), mysql2localedate( $this->{$this->datecreated_field} ) ); 626 } 627 628 // HANDLE LAST UPDATE STUFF 629 if( !empty($this->lasteditor_field) && !empty($this->{$this->lasteditor_field}) ) 630 { // We have a creator: 631 $creator_User = & $UserCache->get_by_ID( $this->{$this->lasteditor_field} ); 632 633 if( !empty($this->datemodified_field) && !empty($this->{$this->datemodified_field}) ) 634 { // We also have a create date: 635 $history[1] = sprintf( T_('Last mod on %s by %s'), mysql2localedate( $this->{$this->datemodified_field} ), 636 $creator_User->dget('preferredname') ); 637 } 638 else 639 { // We only have a cretaor: 640 $history[1] = sprintf( T_('Last mod by %s'), $creator_User->dget('preferredname') ); 641 } 642 } 643 elseif( !empty($this->datemodified_field) && !empty($this->{$this->datemodified_field}) ) 644 { // We only have a create date: 645 $history[1] = sprintf( T_('Last mod on %s'), mysql2localedate( $this->{$this->datemodified_field} ) ); 646 } 647 648 return get_icon( 'history', $what = 'imgtag', array( 'title'=>implode( ' - ', $history ) ), true ); 649 } 650 } 651 652 653 654 /* 655 * $Log: _dataobject.class.php,v $ 656 * Revision 1.1 2007/06/25 10:58:56 fplanque 657 * MODULES (refactored MVC) 658 * 659 * Revision 1.26 2007/05/14 02:43:04 fplanque 660 * Started renaming tables. There probably won't be a better time than 2.0. 661 * 662 * Revision 1.25 2007/05/13 22:02:07 fplanque 663 * removed bloated $object_def 664 * 665 * Revision 1.24 2007/04/26 00:11:09 fplanque 666 * (c) 2007 667 * 668 * Revision 1.23 2007/01/07 23:37:26 fplanque 669 * doc cleanup 670 * 671 * Revision 1.22 2006/12/10 23:20:55 fplanque 672 * hum... 673 * 674 * Revision 1.21 2006/12/10 03:04:31 blueyed 675 * todo 676 * 677 * Revision 1.20 2006/11/24 18:27:24 blueyed 678 * Fixed link to b2evo CVS browsing interface in file docblocks 679 */ 680 ?>
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
| Généré le : Thu Nov 29 23:58:50 2007 | par Balluche grâce à PHPXref 0.7 |
|