| [ Index ] |
|
Code source de vtiger CRM 5.0.2 |
1 <?php 2 /* 3 V4.90 8 June 2006 (c) 2006 John Lim (jlim#natsoft.com.my). All rights reserved. 4 5 This is a version of the ADODB driver for DB2. It uses the 'ibm_db2' PECL extension for PHP 6 (http://pecl.php.net/package/ibm_db2), which in turn requires DB2 V8.2.2. 7 8 Tested with PHP 5.1.1 and Apache 2.0.55 on Windows XP SP2. 9 10 This file was ported from "adodb-odbc.inc.php" by Larry Menard, "larry.menard@rogers.com". 11 I ripped out what I believed to be a lot of redundant or obsolete code, but there are 12 probably still some remnants of the ODBC support in this file; I'm relying on reviewers 13 of this code to point out any other things that can be removed. 14 */ 15 16 // security - hide paths 17 if (!defined('ADODB_DIR')) die(); 18 19 define("_ADODB_DB2_LAYER", 2 ); 20 21 /*-------------------------------------------------------------------------------------- 22 --------------------------------------------------------------------------------------*/ 23 24 25 class ADODB_db2 extends ADOConnection { 26 var $databaseType = "db2"; 27 var $fmtDate = "'Y-m-d'"; 28 var $concat_operator = '||'; 29 30 var $sysTime = 'CURRENT TIME'; 31 var $sysDate = 'CURRENT DATE'; 32 var $sysTimeStamp = 'CURRENT TIMESTAMP'; 33 34 var $fmtTimeStamp = "'Y-m-d-H.i.s'"; 35 #var $fmtTimeStamp = "'Y-m-d, h:i:sA'"; 36 var $replaceQuote = "''"; // string to use to replace quotes 37 var $dataProvider = "db2"; 38 var $hasAffectedRows = true; 39 40 var $binmode = DB2_BINARY; 41 42 var $useFetchArray = false; // setting this to true will make array elements in FETCH_ASSOC mode case-sensitive 43 // breaking backward-compat 44 var $_bindInputArray = false; 45 var $_genSeqSQL = "create table %s (id integer)"; 46 var $_autocommit = true; 47 var $_haserrorfunctions = true; 48 var $_lastAffectedRows = 0; 49 var $uCaseTables = true; // for meta* functions, uppercase table names 50 var $hasInsertID = true; 51 52 function _insertid() 53 { 54 return ADOConnection::GetOne('VALUES IDENTITY_VAL_LOCAL()'); 55 } 56 57 function ADODB_db2() 58 { 59 $this->_haserrorfunctions = ADODB_PHPVER >= 0x4050; 60 } 61 62 // returns true or false 63 function _connect($argDSN, $argUsername, $argPassword, $argDatabasename) 64 { 65 global $php_errormsg; 66 67 if (!function_exists('db2_connect')) { 68 ADOConnection::outp("Warning: The old ODBC based DB2 driver has been renamed 'odbc_db2'. This ADOdb driver calls PHP's native db2 extension."); 69 return null; 70 } 71 // This needs to be set before the connect(). 72 // Replaces the odbc_binmode() call that was in Execute() 73 ini_set('ibm_db2.binmode', $this->binmode); 74 75 if ($argDatabasename) { 76 $this->_connectionID = db2_connect($argDatabasename,$argUsername,$argPassword); 77 } else { 78 $this->_connectionID = db2_connect($argDSN,$argUsername,$argPassword); 79 } 80 if (isset($php_errormsg)) $php_errormsg = ''; 81 82 // For db2_connect(), there is an optional 4th arg. If present, it must be 83 // an array of valid options. So far, we don't use them. 84 85 $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; 86 if (isset($this->connectStmt)) $this->Execute($this->connectStmt); 87 88 return $this->_connectionID != false; 89 } 90 91 // returns true or false 92 function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename) 93 { 94 global $php_errormsg; 95 96 if (!function_exists('db2_connect')) return null; 97 98 // This needs to be set before the connect(). 99 // Replaces the odbc_binmode() call that was in Execute() 100 ini_set('ibm_db2.binmode', $this->binmode); 101 102 if (isset($php_errormsg)) $php_errormsg = ''; 103 $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; 104 105 if ($argDatabasename) { 106 $this->_connectionID = db2_pconnect($argDatabasename,$argUsername,$argPassword); 107 } else { 108 $this->_connectionID = db2_pconnect($argDSN,$argUsername,$argPassword); 109 } 110 if (isset($php_errormsg)) $php_errormsg = ''; 111 112 $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; 113 if ($this->_connectionID && $this->autoRollback) @db2_rollback($this->_connectionID); 114 if (isset($this->connectStmt)) $this->Execute($this->connectStmt); 115 116 return $this->_connectionID != false; 117 } 118 119 120 // Format date column in sql string given an input format that understands Y M D 121 function SQLDate($fmt, $col=false) 122 { 123 // use right() and replace() ? 124 if (!$col) $col = $this->sysDate; 125 $s = ''; 126 127 $len = strlen($fmt); 128 for ($i=0; $i < $len; $i++) { 129 if ($s) $s .= $this->concat_operator; 130 $ch = $fmt[$i]; 131 switch($ch) { 132 case 'Y': 133 case 'y': 134 $s .= "char(year($col))"; 135 break; 136 case 'M': 137 $s .= "substr(monthname($col),1,3)"; 138 break; 139 case 'm': 140 $s .= "right(digits(month($col)),2)"; 141 break; 142 case 'D': 143 case 'd': 144 $s .= "right(digits(day($col)),2)"; 145 break; 146 case 'H': 147 case 'h': 148 if ($col != $this->sysDate) $s .= "right(digits(hour($col)),2)"; 149 else $s .= "''"; 150 break; 151 case 'i': 152 case 'I': 153 if ($col != $this->sysDate) 154 $s .= "right(digits(minute($col)),2)"; 155 else $s .= "''"; 156 break; 157 case 'S': 158 case 's': 159 if ($col != $this->sysDate) 160 $s .= "right(digits(second($col)),2)"; 161 else $s .= "''"; 162 break; 163 default: 164 if ($ch == '\\') { 165 $i++; 166 $ch = substr($fmt,$i,1); 167 } 168 $s .= $this->qstr($ch); 169 } 170 } 171 return $s; 172 } 173 174 175 function ServerInfo() 176 { 177 178 if (!empty($this->host) && ADODB_PHPVER >= 0x4300) { 179 $dsn = strtoupper($this->host); 180 $first = true; 181 $found = false; 182 183 if (!function_exists('db2_data_source')) return false; 184 185 while(true) { 186 187 $rez = @db2_data_source($this->_connectionID, 188 $first ? SQL_FETCH_FIRST : SQL_FETCH_NEXT); 189 $first = false; 190 if (!is_array($rez)) break; 191 if (strtoupper($rez['server']) == $dsn) { 192 $found = true; 193 break; 194 } 195 } 196 if (!$found) return ADOConnection::ServerInfo(); 197 if (!isset($rez['version'])) $rez['version'] = ''; 198 return $rez; 199 } else { 200 return ADOConnection::ServerInfo(); 201 } 202 } 203 204 205 function CreateSequence($seqname='adodbseq',$start=1) 206 { 207 if (empty($this->_genSeqSQL)) return false; 208 $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname)); 209 if (!$ok) return false; 210 $start -= 1; 211 return $this->Execute("insert into $seqname values($start)"); 212 } 213 214 var $_dropSeqSQL = 'drop table %s'; 215 function DropSequence($seqname) 216 { 217 if (empty($this->_dropSeqSQL)) return false; 218 return $this->Execute(sprintf($this->_dropSeqSQL,$seqname)); 219 } 220 221 /* 222 This algorithm is not very efficient, but works even if table locking 223 is not available. 224 225 Will return false if unable to generate an ID after $MAXLOOPS attempts. 226 */ 227 function GenID($seq='adodbseq',$start=1) 228 { 229 // if you have to modify the parameter below, your database is overloaded, 230 // or you need to implement generation of id's yourself! 231 $MAXLOOPS = 100; 232 while (--$MAXLOOPS>=0) { 233 $num = $this->GetOne("select id from $seq"); 234 if ($num === false) { 235 $this->Execute(sprintf($this->_genSeqSQL ,$seq)); 236 $start -= 1; 237 $num = '0'; 238 $ok = $this->Execute("insert into $seq values($start)"); 239 if (!$ok) return false; 240 } 241 $this->Execute("update $seq set id=id+1 where id=$num"); 242 243 if ($this->affected_rows() > 0) { 244 $num += 1; 245 $this->genID = $num; 246 return $num; 247 } 248 } 249 if ($fn = $this->raiseErrorFn) { 250 $fn($this->databaseType,'GENID',-32000,"Unable to generate unique id after $MAXLOOPS attempts",$seq,$num); 251 } 252 return false; 253 } 254 255 256 function ErrorMsg() 257 { 258 if ($this->_haserrorfunctions) { 259 if ($this->_errorMsg !== false) return $this->_errorMsg; 260 if (empty($this->_connectionID)) return @db2_errormsg(); 261 return @db2_errormsg($this->_connectionID); 262 } else return ADOConnection::ErrorMsg(); 263 } 264 265 function ErrorNo() 266 { 267 268 if ($this->_haserrorfunctions) { 269 if ($this->_errorCode !== false) { 270 // bug in 4.0.6, error number can be corrupted string (should be 6 digits) 271 return (strlen($this->_errorCode)<=2) ? 0 : $this->_errorCode; 272 } 273 274 if (empty($this->_connectionID)) $e = @db2_error(); 275 else $e = @db2_error($this->_connectionID); 276 277 // bug in 4.0.6, error number can be corrupted string (should be 6 digits) 278 // so we check and patch 279 if (strlen($e)<=2) return 0; 280 return $e; 281 } else return ADOConnection::ErrorNo(); 282 } 283 284 285 286 function BeginTrans() 287 { 288 if (!$this->hasTransactions) return false; 289 if ($this->transOff) return true; 290 $this->transCnt += 1; 291 $this->_autocommit = false; 292 return db2_autocommit($this->_connectionID,false); 293 } 294 295 function CommitTrans($ok=true) 296 { 297 if ($this->transOff) return true; 298 if (!$ok) return $this->RollbackTrans(); 299 if ($this->transCnt) $this->transCnt -= 1; 300 $this->_autocommit = true; 301 $ret = db2_commit($this->_connectionID); 302 db2_autocommit($this->_connectionID,true); 303 return $ret; 304 } 305 306 function RollbackTrans() 307 { 308 if ($this->transOff) return true; 309 if ($this->transCnt) $this->transCnt -= 1; 310 $this->_autocommit = true; 311 $ret = db2_rollback($this->_connectionID); 312 db2_autocommit($this->_connectionID,true); 313 return $ret; 314 } 315 316 function MetaPrimaryKeys($table) 317 { 318 global $ADODB_FETCH_MODE; 319 320 if ($this->uCaseTables) $table = strtoupper($table); 321 $schema = ''; 322 $this->_findschema($table,$schema); 323 324 $savem = $ADODB_FETCH_MODE; 325 $ADODB_FETCH_MODE = ADODB_FETCH_NUM; 326 $qid = @db2_primarykeys($this->_connectionID,'',$schema,$table); 327 328 if (!$qid) { 329 $ADODB_FETCH_MODE = $savem; 330 return false; 331 } 332 $rs = new ADORecordSet_db2($qid); 333 $ADODB_FETCH_MODE = $savem; 334 335 if (!$rs) return false; 336 337 $arr =& $rs->GetArray(); 338 $rs->Close(); 339 $arr2 = array(); 340 for ($i=0; $i < sizeof($arr); $i++) { 341 if ($arr[$i][3]) $arr2[] = $arr[$i][3]; 342 } 343 return $arr2; 344 } 345 346 347 348 function &MetaTables($ttype=false) 349 { 350 global $ADODB_FETCH_MODE; 351 352 $savem = $ADODB_FETCH_MODE; 353 $ADODB_FETCH_MODE = ADODB_FETCH_NUM; 354 $qid = db2_tables($this->_connectionID); 355 356 $rs = new ADORecordSet_db2($qid); 357 358 $ADODB_FETCH_MODE = $savem; 359 if (!$rs) { 360 $false = false; 361 return $false; 362 } 363 364 $arr =& $rs->GetArray(); 365 366 $rs->Close(); 367 $arr2 = array(); 368 369 if ($ttype) { 370 $isview = strncmp($ttype,'V',1) === 0; 371 } 372 for ($i=0; $i < sizeof($arr); $i++) { 373 if (!$arr[$i][2]) continue; 374 $type = $arr[$i][3]; 375 if ($ttype) { 376 if ($isview) { 377 if (strncmp($type,'V',1) === 0) $arr2[] = $arr[$i][2]; 378 } else if (strncmp($type,'SYS',3) !== 0) $arr2[] = $arr[$i][2]; 379 } else if (strncmp($type,'SYS',3) !== 0) $arr2[] = $arr[$i][2]; 380 } 381 return $arr2; 382 } 383 384 /* 385 See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2datetime_data_type_changes.asp 386 / SQL data type codes / 387 #define SQL_UNKNOWN_TYPE 0 388 #define SQL_CHAR 1 389 #define SQL_NUMERIC 2 390 #define SQL_DECIMAL 3 391 #define SQL_INTEGER 4 392 #define SQL_SMALLINT 5 393 #define SQL_FLOAT 6 394 #define SQL_REAL 7 395 #define SQL_DOUBLE 8 396 #if (DB2VER >= 0x0300) 397 #define SQL_DATETIME 9 398 #endif 399 #define SQL_VARCHAR 12 400 401 402 / One-parameter shortcuts for date/time data types / 403 #if (DB2VER >= 0x0300) 404 #define SQL_TYPE_DATE 91 405 #define SQL_TYPE_TIME 92 406 #define SQL_TYPE_TIMESTAMP 93 407 408 #define SQL_UNICODE (-95) 409 #define SQL_UNICODE_VARCHAR (-96) 410 #define SQL_UNICODE_LONGVARCHAR (-97) 411 */ 412 function DB2Types($t) 413 { 414 switch ((integer)$t) { 415 case 1: 416 case 12: 417 case 0: 418 case -95: 419 case -96: 420 return 'C'; 421 case -97: 422 case -1: //text 423 return 'X'; 424 case -4: //image 425 return 'B'; 426 427 case 9: 428 case 91: 429 return 'D'; 430 431 case 10: 432 case 11: 433 case 92: 434 case 93: 435 return 'T'; 436 437 case 4: 438 case 5: 439 case -6: 440 return 'I'; 441 442 case -11: // uniqidentifier 443 return 'R'; 444 case -7: //bit 445 return 'L'; 446 447 default: 448 return 'N'; 449 } 450 } 451 452 function &MetaColumns($table) 453 { 454 global $ADODB_FETCH_MODE; 455 456 $false = false; 457 if ($this->uCaseTables) $table = strtoupper($table); 458 $schema = ''; 459 $this->_findschema($table,$schema); 460 461 $savem = $ADODB_FETCH_MODE; 462 $ADODB_FETCH_MODE = ADODB_FETCH_NUM; 463 464 $colname = "%"; 465 $qid = db2_columns($this->_connectionID, "", $schema, $table, $colname); 466 if (empty($qid)) return $false; 467 468 $rs =& new ADORecordSet_db2($qid); 469 $ADODB_FETCH_MODE = $savem; 470 471 if (!$rs) return $false; 472 $rs->_fetch(); 473 474 $retarr = array(); 475 476 /* 477 $rs->fields indices 478 0 TABLE_QUALIFIER 479 1 TABLE_SCHEM 480 2 TABLE_NAME 481 3 COLUMN_NAME 482 4 DATA_TYPE 483 5 TYPE_NAME 484 6 PRECISION 485 7 LENGTH 486 8 SCALE 487 9 RADIX 488 10 NULLABLE 489 11 REMARKS 490 */ 491 while (!$rs->EOF) { 492 if (strtoupper(trim($rs->fields[2])) == $table && (!$schema || strtoupper($rs->fields[1]) == $schema)) { 493 $fld = new ADOFieldObject(); 494 $fld->name = $rs->fields[3]; 495 $fld->type = $this->DB2Types($rs->fields[4]); 496 497 // ref: http://msdn.microsoft.com/library/default.asp?url=/archive/en-us/dnaraccgen/html/msdn_odk.asp 498 // access uses precision to store length for char/varchar 499 if ($fld->type == 'C' or $fld->type == 'X') { 500 if ($rs->fields[4] <= -95) // UNICODE 501 $fld->max_length = $rs->fields[7]/2; 502 else 503 $fld->max_length = $rs->fields[7]; 504 } else 505 $fld->max_length = $rs->fields[7]; 506 $fld->not_null = !empty($rs->fields[10]); 507 $fld->scale = $rs->fields[8]; 508 $retarr[strtoupper($fld->name)] = $fld; 509 } else if (sizeof($retarr)>0) 510 break; 511 $rs->MoveNext(); 512 } 513 $rs->Close(); //-- crashes 4.03pl1 -- why? 514 515 if (empty($retarr)) $retarr = false; 516 return $retarr; 517 } 518 519 function Prepare($sql) 520 { 521 if (! $this->_bindInputArray) return $sql; // no binding 522 $stmt = db2_prepare($this->_connectionID,$sql); 523 if (!$stmt) { 524 // we don't know whether db2 driver is parsing prepared stmts, so just return sql 525 return $sql; 526 } 527 return array($sql,$stmt,false); 528 } 529 530 /* returns queryID or false */ 531 function _query($sql,$inputarr=false) 532 { 533 GLOBAL $php_errormsg; 534 if (isset($php_errormsg)) $php_errormsg = ''; 535 $this->_error = ''; 536 537 if ($inputarr) { 538 if (is_array($sql)) { 539 $stmtid = $sql[1]; 540 } else { 541 $stmtid = db2_prepare($this->_connectionID,$sql); 542 543 if ($stmtid == false) { 544 $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; 545 return false; 546 } 547 } 548 549 if (! db2_execute($stmtid,$inputarr)) { 550 if ($this->_haserrorfunctions) { 551 $this->_errorMsg = db2_errormsg(); 552 $this->_errorCode = db2_error(); 553 } 554 return false; 555 } 556 557 } else if (is_array($sql)) { 558 $stmtid = $sql[1]; 559 if (!db2_execute($stmtid)) { 560 if ($this->_haserrorfunctions) { 561 $this->_errorMsg = db2_errormsg(); 562 $this->_errorCode = db2_error(); 563 } 564 return false; 565 } 566 } else 567 $stmtid = db2_exec($this->_connectionID,$sql); 568 569 $this->_lastAffectedRows = 0; 570 if ($stmtid) { 571 if (@db2_num_fields($stmtid) == 0) { 572 $this->_lastAffectedRows = db2_num_rows($stmtid); 573 $stmtid = true; 574 } else { 575 $this->_lastAffectedRows = 0; 576 } 577 578 if ($this->_haserrorfunctions) { 579 $this->_errorMsg = ''; 580 $this->_errorCode = 0; 581 } else 582 $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; 583 } else { 584 if ($this->_haserrorfunctions) { 585 $this->_errorMsg = db2_stmt_errormsg(); 586 $this->_errorCode = db2_stmt_error(); 587 } else 588 $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; 589 } 590 return $stmtid; 591 } 592 593 /* 594 Insert a null into the blob field of the table first. 595 Then use UpdateBlob to store the blob. 596 597 Usage: 598 599 $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)'); 600 $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1'); 601 */ 602 function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') 603 { 604 return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false; 605 } 606 607 // returns true or false 608 function _close() 609 { 610 $ret = @db2_close($this->_connectionID); 611 $this->_connectionID = false; 612 return $ret; 613 } 614 615 function _affectedrows() 616 { 617 return $this->_lastAffectedRows; 618 } 619 620 } 621 622 /*-------------------------------------------------------------------------------------- 623 Class Name: Recordset 624 --------------------------------------------------------------------------------------*/ 625 626 class ADORecordSet_db2 extends ADORecordSet { 627 628 var $bind = false; 629 var $databaseType = "db2"; 630 var $dataProvider = "db2"; 631 var $useFetchArray; 632 633 function ADORecordSet_db2($id,$mode=false) 634 { 635 if ($mode === false) { 636 global $ADODB_FETCH_MODE; 637 $mode = $ADODB_FETCH_MODE; 638 } 639 $this->fetchMode = $mode; 640 641 $this->_queryID = $id; 642 } 643 644 645 // returns the field object 646 function &FetchField($offset = -1) 647 { 648 $o= new ADOFieldObject(); 649 $o->name = @db2_field_name($this->_queryID,$offset); 650 $o->type = @db2_field_type($this->_queryID,$offset); 651 $o->max_length = db2_field_width($this->_queryID,$offset); 652 if (ADODB_ASSOC_CASE == 0) $o->name = strtolower($o->name); 653 else if (ADODB_ASSOC_CASE == 1) $o->name = strtoupper($o->name); 654 return $o; 655 } 656 657 /* Use associative array to get fields array */ 658 function Fields($colname) 659 { 660 if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname]; 661 if (!$this->bind) { 662 $this->bind = array(); 663 for ($i=0; $i < $this->_numOfFields; $i++) { 664 $o = $this->FetchField($i); 665 $this->bind[strtoupper($o->name)] = $i; 666 } 667 } 668 669 return $this->fields[$this->bind[strtoupper($colname)]]; 670 } 671 672 673 function _initrs() 674 { 675 global $ADODB_COUNTRECS; 676 $this->_numOfRows = ($ADODB_COUNTRECS) ? @db2_num_rows($this->_queryID) : -1; 677 $this->_numOfFields = @db2_num_fields($this->_queryID); 678 // some silly drivers such as db2 as/400 and intersystems cache return _numOfRows = 0 679 if ($this->_numOfRows == 0) $this->_numOfRows = -1; 680 } 681 682 function _seek($row) 683 { 684 return false; 685 } 686 687 // speed up SelectLimit() by switching to ADODB_FETCH_NUM as ADODB_FETCH_ASSOC is emulated 688 function &GetArrayLimit($nrows,$offset=-1) 689 { 690 if ($offset <= 0) { 691 $rs =& $this->GetArray($nrows); 692 return $rs; 693 } 694 $savem = $this->fetchMode; 695 $this->fetchMode = ADODB_FETCH_NUM; 696 $this->Move($offset); 697 $this->fetchMode = $savem; 698 699 if ($this->fetchMode & ADODB_FETCH_ASSOC) { 700 $this->fields =& $this->GetRowAssoc(ADODB_ASSOC_CASE); 701 } 702 703 $results = array(); 704 $cnt = 0; 705 while (!$this->EOF && $nrows != $cnt) { 706 $results[$cnt++] = $this->fields; 707 $this->MoveNext(); 708 } 709 710 return $results; 711 } 712 713 714 function MoveNext() 715 { 716 if ($this->_numOfRows != 0 && !$this->EOF) { 717 $this->_currentRow++; 718 719 $this->fields = @db2_fetch_array($this->_queryID); 720 if ($this->fields) { 721 if ($this->fetchMode & ADODB_FETCH_ASSOC) { 722 $this->fields =& $this->GetRowAssoc(ADODB_ASSOC_CASE); 723 } 724 return true; 725 } 726 } 727 $this->fields = false; 728 $this->EOF = true; 729 return false; 730 } 731 732 function _fetch() 733 { 734 735 $this->fields = db2_fetch_array($this->_queryID); 736 if ($this->fields) { 737 if ($this->fetchMode & ADODB_FETCH_ASSOC) { 738 $this->fields =& $this->GetRowAssoc(ADODB_ASSOC_CASE); 739 } 740 return true; 741 } 742 $this->fields = false; 743 return false; 744 } 745 746 function _close() 747 { 748 return @db2_free_result($this->_queryID); 749 } 750 751 } 752 ?>
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
| Généré le : Sun Feb 25 10:22:19 2007 | par Balluche grâce à PHPXref 0.7 |