]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/adodb/adodb.inc.php
Cleanup of special PageList column types
[SourceForge/phpwiki.git] / lib / WikiDB / adodb / adodb.inc.php
1 <?php 
2 /** 
3  * @version V1.71 18 Jan 2001 (c) 2000, 2001 John Lim (jlim@natsoft.com.my). All rights reserved.
4  * Released under both BSD license and Lesser GPL library license. 
5  * Whenever there is any discrepancy between the two licenses, 
6  * the BSD license will take precedence. 
7  *
8  * Set tabs to 4 for best viewing.
9  * 
10  * Latest version is available at http://php.weblogs.com
11  * 
12  * This is the main include file for ADODB.
13  * It has all the generic functionality of ADODB. 
14  * Database specific drivers are stored in the adodb-*.inc.php files.
15  *
16  * Requires PHP4.01pl2 or later because it uses include_once
17 */
18 /**
19  * Included with PhpWiki, which uses for now only the mysql-specific backend, 
20  * instead of going through this main adodb.inc.php library.
21  * Initial port by Lawrence Akka. See lib/WikiDB/ADODB.php
22  */
23
24 if (!defined('_ADODB_LAYER')) {
25         define('_ADODB_LAYER',1);
26 rcs_id('$Id: adodb.inc.php,v 1.3 2004-04-06 20:00:10 rurban Exp $');
27         
28 //==============================================================================
29 // CONSTANT DEFINITIONS
30 //==============================================================================
31
32         define('ADODB_BAD_RS','<p>Bad $rs in %s. Connection or SQL invalid. Try using $connection->debug=true;</p>');
33         
34         define('ADODB_FETCH_DEFAULT',0);
35         define('ADODB_FETCH_NUM',1);
36         define('ADODB_FETCH_ASSOC',2);
37         define('ADODB_FETCH_BOTH',3);
38         
39         // allow [ ] @ and . in table names
40         define('ADODB_TABLE_REGEX','([]0-9a-z_\.\@\[-]*)');
41         
42         if (!defined('ADODB_PREFETCH_ROWS')) define('ADODB_PREFETCH_ROWS',10);
43
44         /** 
45          * Set ADODB_DIR to the directory where this file resides...
46          * This constant was formerly called $ADODB_RootPath
47          */
48         if (!defined('ADODB_DIR')) define('ADODB_DIR',dirname(__FILE__));
49         
50 //==============================================================================
51 // GLOBAL VARIABLES
52 //==============================================================================
53
54         GLOBAL 
55                 $ADODB_vers,            // database version
56                 $ADODB_Database,        // last database driver used
57                 $ADODB_COUNTRECS,       // count number of records returned - slows down query
58                 $ADODB_CACHE_DIR,       // directory to cache recordsets
59                 $ADODB_FETCH_MODE;      // DEFAULT, NUM, ASSOC or BOTH. Default follows native driver default...
60         
61 //==============================================================================
62 // GLOBAL SETUP
63 //==============================================================================
64         
65         $ADODB_FETCH_MODE = ADODB_FETCH_DEFAULT;
66         
67         if (!isset($ADODB_CACHE_DIR)) {
68                 $ADODB_CACHE_DIR = '/tmp';
69         } else {
70                 // do not accept url based paths, eg. http:/ or ftp:/
71                 if (strpos($ADODB_CACHE_DIR,':/') !== false) 
72                         die("Illegal \$ADODB_CACHE_DIR");
73         }
74         
75 //==============================================================================
76 // CHANGE NOTHING BELOW UNLESS YOU ARE CODING
77 //==============================================================================
78
79         
80         // Initialize random number generator for randomizing cache flushes
81         srand(((double)microtime())*1000000);
82         
83         /**
84          * Name of last database driver loaded into memory. Set by ADOLoadCode().
85          */
86         $ADODB_Database = '';
87         
88         /**
89          * ADODB version as a string.
90          */
91         $ADODB_vers = 'V1.71 18 Jan 2001 (c) 2000, 2001 John Lim (jlim@natsoft.com.my). All rights reserved. Released BSD & LGPL.';
92
93         /**
94          * Determines whether recordset->RecordCount() is used. 
95          * Set to false for highest performance -- RecordCount() will always return -1 then
96          * for databases that provide "virtual" recordcounts...
97          */
98         $ADODB_COUNTRECS = true; 
99
100 //==============================================================================
101 // CLASS ADOFieldObject
102 //==============================================================================
103
104         /**
105          * Helper class for FetchFields -- holds info on a column
106          */
107         class ADOFieldObject { 
108                 var $name = '';
109                 var $max_length=0;
110                 var $type="";
111
112                 // additional fields by dannym... (danny_milo@yahoo.com)
113                 var $not_null = false; 
114                 // actually, this has already been built-in in the postgres, fbsql AND mysql module? ^-^
115                 // so we can as well make not_null standard (leaving it at "false" does not harm anyways)
116
117                 var $has_default = false; // this one I have done only in mysql and postgres for now ... 
118                         // others to come (dannym)
119                 var $default_value; // default, if any, and supported. Check has_default first.
120         }
121         
122         
123 //==============================================================================
124 // CLASS ADOConnection
125 //==============================================================================
126         
127         /**
128          * Connection object. For connecting to databases, and executing queries.
129          */ 
130         class ADOConnection {
131         /*
132          * PUBLIC VARS 
133          */
134         var $dataProvider = 'native';
135         var $databaseType = '';         // RDBMS currently in use, eg. odbc, mysql, mssql                                       
136         var $database = '';                     // Name of database to be used. 
137         var $host = '';                         // The hostname of the database server  
138         var $user = '';                         // The username which is used to connect to the database server. 
139         var $password = '';             // Password for the username
140         var $debug = false;             // if set to true will output sql statements
141         var $maxblobsize = 8000;        // maximum size of blobs or large text fields -- some databases die otherwise like foxpro
142         var $concat_operator = '+'; // default concat operator -- change to || for Oracle/Interbase     
143         var $fmtDate = "'Y-m-d'";       // used by DBDate() as the default date format used by the database
144         var $fmtTimeStamp = "'Y-m-d, h:i:s A'"; // used by DBTimeStamp as the default timestamp fmt.
145         var $true = '1';                        // string that represents TRUE for a database
146         var $false = '0';                       // string that represents FALSE for a database
147         var $replaceQuote = "\\'";      // string to use to replace quotes
148     var $hasInsertID = false;   // supports autoincrement ID?
149     var $hasAffectedRows = false;       // supports affected rows for update/delete?
150     var $autoCommit = true; 
151         var $charSet=false;             // character set to use - only for interbase
152         var $metaTablesSQL = '';
153         var $hasTop = false;            // support mssql/access SELECT TOP 10 * FROM TABLE
154         var $hasLimit = false;          // support pgsql/mysql SELECT * FROM TABLE LIMIT 10
155         var $readOnly = false;          // this is a readonly database - used by phpLens
156         var $hasMoveFirst = false;  // has ability to run MoveFirst(), scrolling backwards
157         var $hasGenID = false;          // can generate sequences using GenID();
158         var $genID = 0;                         // sequence id used by GenID();
159         var $raiseErrorFn = false;      // error function to call
160         var $upperCase = false;         // uppercase function to call for searching/where
161         var $isoDates = false; // accepts dates in ISO format
162         
163         /*
164          * PRIVATE VARS
165          */
166         var $_connectionID      = false;        // The returned link identifier whenever a successful database connection is made.      */
167                 
168         var $_errorMsg = '';            // A variable which was used to keep the returned last error message.  The value will
169                                         //then returned by the errorMsg() function      
170                                                 
171         var $_queryID = false;          // This variable keeps the last created result link identifier.         */
172         
173         var $_isPersistentConnection = false;   // A boolean variable to state whether its a persistent connection or normal connection.        */
174         
175         var $_bindInputArray = false; // set to true if ADOConnection.Execute() permits binding of array parameters.
176         
177         /**
178          * Constructor
179          */
180         function ADOConnection()                        
181         {
182                 die('Virtual Class -- cannot instantiate');
183         }
184         
185
186         /**
187          * Connect to database
188          *
189          * @param [argHostname]         Host to connect to
190          * @param [argUsername]         Userid to login
191          * @param [argPassword]         Associated password
192          * @param [argDatabaseName]     database
193          *
194          * @return true or false
195          */       
196         function Connect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") {
197                 if ($argHostname != "") $this->host = $argHostname;
198                 if ($argUsername != "") $this->user = $argUsername;
199                 if ($argPassword != "") $this->password = $argPassword; // not stored for security reasons
200                 if ($argDatabaseName != "") $this->database = $argDatabaseName;         
201                 
202                 $this->_isPersistentConnection = false; 
203                 if ($fn = $this->raiseErrorFn) {
204                         if ($this->_connect($this->host, $this->user, $this->password, $this->database)) return true;
205                         $err = $this->ErrorMsg();
206                         if (empty($err)) $err = "Connection error to server '$argHostname' with user '$argUsername'";
207                         $fn($this->databaseType,'CONNECT',$this->ErrorNo(),$err,$this->host,$this->database);
208                 } else 
209                         if ($this->_connect($this->host, $this->user, $this->password, $this->database)) return true;
210
211                 if ($this->debug) print $this->host.': '.$this->ErrorMsg().'<br>';
212                 
213                 return false;
214         }       
215         
216
217         /**
218          * Establish persistent connect to database
219          *
220          * @param [argHostname]         Host to connect to
221          * @param [argUsername]         Userid to login
222          * @param [argPassword]         Associated password
223          * @param [argDatabaseName]     database
224          *
225          * @return return true or false
226          */     
227         function PConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "")
228         {
229                 if ($argHostname != "") $this->host = $argHostname;
230                 if ($argUsername != "") $this->user = $argUsername;
231                 if ($argPassword != "") $this->password = $argPassword;
232                 if ($argDatabaseName != "") $this->database = $argDatabaseName;         
233                         
234                 $this->_isPersistentConnection = true;  
235                 
236                 if ($fn = $this->raiseErrorFn) {
237                         if ($this->_pconnect($this->host, $this->user, $this->password, $this->database)) return true;
238                         $err = $this->ErrorMsg();
239                         if (empty($err)) $err = "Connection error to server '$argHostname' with user '$argUsername'";
240                         $fn($this->databaseType,'PCONNECT',$this->ErrorNo(),$err,$this->host,$this->database);
241                 } else 
242                         if ($this->_pconnect($this->host, $this->user, $this->password, $this->database)) return true;
243
244                 if ($this->debug) print $this->host.': '.$this->ErrorMsg().'<br>';
245                 
246                 return false;
247         }
248
249         
250         /**
251          * Should prepare the sql statement and return the stmt resource.
252          * For databases that do not support this, we return the $sql. To ensure
253          * compatibility with databases that do not support prepare:
254          *
255          *   $stmt = $db->Prepare("insert into table (id, name) values (?,?)");
256          *   $db->Execute($stmt,array(1,'Jill')) or die('insert failed');
257          *   $db->Execute($stmt,array(2,'Joe')) or die('insert failed');
258          *
259          * @param sql   SQL to send to database
260          *
261          * @return return TRUE or FALSE, or the $sql.
262          *
263          */     
264         function Prepare($sql)
265         {
266                 return $sql;
267         }
268
269         
270         /**
271         * PEAR DB Compat - do not use internally. 
272         */
273         function Quote($s)
274         {
275                 return $this->qstr($s);
276         }
277
278         
279         /**
280         * PEAR DB Compat - do not use internally. 
281         */
282         function ErrorNative()
283     {
284         return $this->ErrorNo();
285     }
286
287         
288    /**
289         * PEAR DB Compat - do not use internally. 
290         */
291     function nextId($seq_name)
292         {
293                 return $this->GenID($seq_name);
294         }
295
296         
297         /**
298         * PEAR DB Compat - do not use internally. 
299         *
300         * Appears that the fetch modes for NUMERIC and ASSOC for PEAR DB and ADODB 
301         * are the same numeric values!
302         */
303         function SetFetchMode($mode)
304         {
305         global $ADODB_FETCH_MODE;
306                 $ADODB_FETCH_MODE = $mode;
307         }
308         
309
310         /**
311         * PEAR DB Compat - do not use internally. 
312         */
313         function &Query($sql, $inputarr=false)
314         {
315                 $rs = &$this->Execute($sql, $inputarr);
316                 if (!$rs && defined('ADODB_PEAR')) return ADODB_PEAR_Error();
317                 return $rs;
318         }
319
320         
321         /**
322         * PEAR DB Compat - do not use internally
323         */
324         function &LimitQuery($sql, $offset, $count)
325         {
326                 $rs = &$this->SelectLimit($sql, $count, $offset); // swap 
327                 if (!$rs && defined('ADODB_PEAR')) return ADODB_PEAR_Error();
328                 return $rs;
329         }
330
331         
332         /**
333         * PEAR DB Compat - do not use internally
334         */
335         function Disconnect()
336         {
337                 return $this->Close();
338         }
339
340         
341         /**
342          * Execute SQL 
343          *
344          * @param sql           SQL statement to execute
345          * @param [inputarr]    holds the input data to bind to. Null elements will be set to null.
346          * @param [arg3]        reserved for john lim for future use
347          * @return              RecordSet or false
348          */
349         function &Execute($sql,$inputarr=false,$arg3=false) 
350         {
351                 if (!$this->_bindInputArray && $inputarr) {
352                         $sqlarr = explode('?',$sql);
353                         $sql = '';
354                         $i = 0;
355                         foreach($inputarr as $v) {
356
357                                 $sql .= $sqlarr[$i];
358                                 // from Ron Baldwin <ron.baldwin@sourceprose.com>
359                                 // Only quote string types      
360                                 if (gettype($v) == 'string')
361                                         $sql .= "'".$v."'";
362                                 else if ($v === null)
363                                         $sql .= 'NULL';
364                                 else
365                                         $sql .= $v;
366                                 $i += 1;
367         
368                         }
369                         $sql .= $sqlarr[$i];
370                         if ($i+1 != sizeof($sqlarr))    
371                                 print "Input Array does not match ?: ".htmlspecialchars($sql);
372                         $inputarr = false;
373                 }
374                 
375                 if ($this->debug) {
376                         $ss = '';
377                         if ($inputarr) {
378                                 foreach ($inputarr as $kk => $vv)  {
379                                         if (is_string($vv) && strlen($vv)>64) $vv = substr($vv,0,64).'...';
380                                         $ss .= "($kk=>'$vv') ";
381                                 }
382                                 $ss = "[ $ss ]";
383                         }
384                         print "<hr>($this->databaseType): ".htmlspecialchars($sql)." &nbsp; <code>$ss</code><hr>";
385                         $this->_queryID = $this->_query($sql,$inputarr,$arg3);
386
387                         if ($this->databaseType == 'mssql') { // ErrorNo is a slow function call in mssql, and not reliable
388                                 if($this->ErrorMsg()) {
389                                         $err = $this->ErrorNo();
390                                         if ($err) print $err.': '.$this->ErrorMsg().'<br>';
391                                 }
392                         } else 
393                                 if (!$this->_queryID) print $this->ErrorNo().': '.$this->ErrorMsg().'<br>';
394                                 
395                 } else 
396                         $this->_queryID =@$this->_query($sql,$inputarr,$arg3);
397                 
398                 if ($this->_queryID === false) {
399                         if ($fn = $this->raiseErrorFn) {
400                                 $fn($this->databaseType,'EXECUTE',$this->ErrorNo(),$this->ErrorMsg(),$sql,$inputarr);
401                         }
402                         return false;
403                 } else if ($this->_queryID === true){
404                         $rs = new ADORecordSet_empty();
405                         return $rs;
406                 }
407                 $rsclass = "ADORecordSet_".$this->databaseType;
408                 $rs = new $rsclass($this->_queryID); // &new not supported by older PHP versions
409                 $rs->connection = &$this; // Pablo suggestion
410                 $rs->Init();
411                 //$this->_insertQuery(&$rs); PHP4 handles closing automatically
412
413                 if (is_string($sql)) $rs->sql = $sql;
414                 return $rs;
415         }
416
417         
418         /**
419          * Generates a sequence id and stores it in $this->genID;
420          * GenID is only available if $this->hasGenID = true;
421          *
422          * @seqname             name of sequence to use
423          * @startID             if sequence does not exist, start at this ID
424          * @return              0 if not supported, otherwise a sequence id
425          */
426         
427         function GenID($seqname='adodbseq',$startID=1)
428         {
429                 if (!$this->hasGenID) {
430                         return 0; // formerly returns false pre 1.60
431                 }
432                 
433                 $getnext = sprintf($this->_genIDSQL,$seqname);
434                 $rs = @$this->Execute($getnext);
435                 if (!$rs) {
436                         $u = strtoupper($seqname);
437                         $createseq = $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID));
438                         $rs = $this->Execute($getnext);
439                 }
440                 if ($rs && !$rs->EOF) $this->genID = (integer) reset($rs->fields);
441                 else $this->genID = 0; // false
442                 
443                 if ($rs) $rs->Close();
444                 
445                 return $this->genID;
446         }
447         
448         
449         /**
450          * @return  the last inserted ID. Not all databases support this.
451          */ 
452         function Insert_ID()
453         {
454                 if ($this->hasInsertID) return $this->_insertid();
455                 if ($this->debug) print '<p>Insert_ID error</p>';
456                 return false;
457         }
458     
459         
460     /**
461          * Portable Insert ID. Pablo Roca <pabloroca@mvps.org>
462          *
463          * @return  the last inserted ID. All databases support this. But aware possible
464          * problems in multiuser environments. Heavy test this before deploying.
465          */ 
466         function PO_Insert_ID($table="", $id="") 
467                 {
468            if ($this->hasInsertID){
469                return $this->Insert_ID();
470            } else {
471                return $this->GetOne("SELECT MAX($id) FROM $table");
472            }
473         }       
474         
475                 
476      /**
477          * @return  # rows affected by UPDATE/DELETE
478          */ 
479         function Affected_Rows()
480         {
481                 if ($this->hasAffectedRows) {
482                        $val = $this->_affectedrows();
483                        return ($val < 0) ? false : $val;
484                 }
485                         
486                 if ($this->debug) print '<p>Affected_Rows error</p>';
487                 return false;
488         }
489         
490         
491     /**
492          * @return  the last error message
493          */
494         function ErrorMsg()
495         {
496                 return '!! '.strtoupper($this->dataProvider.' '.$this->databaseType).': '.$this->_errorMsg;
497         }
498         
499         
500         /**
501          * @return the last error number. Normally 0 means no error.
502          */
503         function ErrorNo() 
504         {
505                 return ($this->_errorMsg) ? -1 : 0;
506         }
507         
508         
509         /**
510          * @returns an array with the primary key columns in it.
511          */
512         function MetaPrimaryKeys($table)
513         {
514                 return false;
515         }
516         
517         
518         /**
519          * Choose a database to connect to. Many databases do not support this.
520          *
521          * @param dbName        is the name of the database to select
522          * @return              true or false
523          */
524         function SelectDB($dbName) 
525         {return false;}
526         
527         
528         /**
529         * Will select, getting rows from $offset (1-based), for $nrows. 
530         * This simulates the MySQL "select * from table limit $offset,$nrows" , and
531         * the PostgreSQL "select * from table limit $nrows offset $offset". Note that
532         * MySQL and PostgreSQL parameter ordering is the opposite of the other.
533         * eg. 
534         *  SelectLimit('select * from table',3); will return rows 1 to 3 (1-based)
535         *  SelectLimit('select * from table',3,2); will return rows 3 to 5 (1-based)
536         *
537         * Uses SELECT TOP for Microsoft databases, and FIRST_ROWS CBO hint for Oracle 8+
538         * BUG: Currently SelectLimit fails with $sql with LIMIT or TOP clause already set
539         *
540         * @param sql
541         * @param [offset]       is the row to start calculations from (1-based)
542         * @param [rows]         is the number of rows to get
543         * @param [inputarr]     array of bind variables
544         * @param [arg3]         is a private parameter only used by jlim
545         * @param [secs2cache]           is a private parameter only used by jlim
546         * @return               the recordset ($rs->databaseType == 'array')
547         */
548         function &SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$arg3=false,$secs2cache=0)
549         {
550                 if ($this->hasTop && $nrows > 0) {
551                 // suggested by Reinhard Balling. Access requires top after distinct 
552                 
553                         if ($offset <= 0) {
554                                 $sql = preg_replace(
555                                 '/(^select[\\t\\n ]*(distinctrow|distinct)?)/i','\\1 top '.$nrows.' ',$sql);
556                                 
557                                         if ($secs2cache>0) return $this->CacheExecute($secs2cache, $sql,$inputarr,$arg3);
558                                         else return $this->Execute($sql,$inputarr,$arg3);
559                         } else {
560                                 $nrows += $offset;
561                                 $sql = preg_replace(
562                                 '/(^select[\\t\\n ]*(distinctrow|distinct)?)/i','\\1 top '.$nrows.' ',$sql);
563                                 $nrows = -1;
564                         }
565          
566                 }
567                 if ($secs2cache>0) $rs = &$this->CacheExecute($secs2cache,$sql,$inputarr,$arg3);
568                 else $rs = &$this->Execute($sql,$inputarr,$arg3);
569                 if ($rs && !$rs->EOF) {
570                         return $this->_rs2rs($rs,$nrows,$offset);
571                 }
572                 //print_r($rs);
573                 return $rs;
574         }
575         
576         
577         /**
578         * Convert recordset to an array recordset
579         * input recordset's cursor should be at beginning, and
580         * old $rs will be closed.
581         *
582         * @param rs                     the recordset to copy
583         * @param [nrows]        number of rows to retrieve (optional)
584         * @param [offset]       offset by number of rows (optional)
585         * @return                       the new recordset
586         */
587         function &_rs2rs(&$rs,$nrows=-1,$offset=-1)
588         {
589                 
590                 $arr = &$rs->GetArrayLimit($nrows,$offset);
591                 $flds = array();
592                 for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++)
593                         $flds[] = &$rs->FetchField($i);
594                 $rs->Close();
595                 
596                 $rs2 = new ADORecordSet_array();
597                 $rs2->connection = &$this;
598                 $rs2->InitArrayFields($arr,$flds);
599                 return $rs2;
600         }
601         
602         
603         /**
604         * Return first element of first row of sql statement. Recordset is disposed
605         * for you.
606         *
607         * @param sql                    SQL statement
608         * @param [inputarr]             input bind array
609         */
610         function GetOne($sql,$inputarr=false)
611         {
612                 $ret = false;
613                 $rs = $this->Execute($sql,$inputarr);
614                 if ($rs) {              
615                         if (!$rs->EOF) $ret = reset($rs->fields);
616                         $rs->Close();
617                 } 
618                 
619                 return $ret;
620         }
621         
622         
623         /**
624         * Return all rows. Compat with PEAR DB
625         *
626         * @param sql                    SQL statement
627         * @param [inputarr]             input bind array
628         */
629         function &GetAll($sql,$inputarr=false)
630         {
631                 $rs = $this->Execute($sql,$inputarr);
632                 if (!$rs) 
633                         if (defined('ADODB_PEAR')) return ADODB_PEAR_Error();
634                         else return false;
635                 return $rs->GetArray();
636         }
637         
638         
639         /**
640         * Return one row of sql statement. Recordset is disposed for you.
641         *
642         * @param sql                    SQL statement
643         * @param [inputarr]             input bind array
644         */
645         function GetRow($sql,$inputarr=false)
646         {
647                 $rs = $this->Execute($sql,$inputarr);
648                 if ($rs) {
649                         $arr = false;
650                         if (!$rs->EOF) $arr = $rs->fields;
651                         $rs->Close();
652                         return $arr;
653                 }
654                 return false;
655         }
656         
657         
658         
659         /**
660         * Will select, getting rows from $offset (1-based), for $nrows. 
661         * This simulates the MySQL "select * from table limit $offset,$nrows" , and
662         * the PostgreSQL "select * from table limit $nrows offset $offset". Note that
663         * MySQL and PostgreSQL parameter ordering is the opposite of the other.
664         * eg. 
665         *  CacheSelectLimit(15,'select * from table',3); will return rows 1 to 3 (1-based)
666         *  CacheSelectLimit(15,'select * from table',3,2); will return rows 3 to 5 (1-based)
667         *
668         * BUG: Currently CacheSelectLimit fails with $sql with LIMIT or TOP clause already set
669         *
670         * @param secs2cache     seconds to cache data, set to 0 to force query
671         * @param sql
672         * @param [offset]       is the row to start calculations from (1-based)
673         * @param [nrows]        is the number of rows to get
674         * @param [inputarr]     array of bind variables
675         * @param [arg3]         is a private parameter only used by jlim
676         * @return               the recordset ($rs->databaseType == 'array')
677         */
678         function &CacheSelectLimit($secs2cache,$sql,$nrows=-1,$offset=-1,$inputarr=false, $arg3=false)
679     {
680                 return $this->SelectLimit($sql,$nrows,$offset,$inputarr,$arg3,$secs2cache);
681         }
682         
683         
684         function CacheFlush($sql)
685         {
686                 $f = $this->_gencachename($sql);
687                 adodb_write_file($f,''); // is adodb_write_file needed?
688                 @unlink($f);
689         }
690         
691         
692         function _gencachename($sql)
693         {
694         global $ADODB_CACHE_DIR;
695         
696                 return $ADODB_CACHE_DIR.'/adodb_'.md5($sql.$this->databaseType.$this->database.$this->user).'.cache';
697         }
698         
699         
700         /**
701          * Execute SQL, caching recordsets.
702          *
703          * @param secs2cache    seconds to cache data, set to 0 to force query
704          * @param sql           SQL statement to execute
705          * @param [inputarr]    holds the input data  to bind to
706          * @param [arg3]        reserved for john lim for future use
707          * @return              RecordSet or false
708          */
709         function &CacheExecute($secs2cache,$sql,$inputarr=false,$arg3=false)
710         {
711                 include_once(ADODB_DIR.'/adodb-csvlib.inc.php');
712                 // cannot cache if $inputarr set
713                 if ($inputarr) return $this->Execute($sql, $inputarr, $arg3); 
714                 
715                 $md5file = $this->_gencachename($sql);
716                 $err = '';
717                 
718                 if ($secs2cache > 0)$rs = &csv2rs($md5file,$err,$secs2cache);
719                 else {
720                         $err='Timeout 1';
721                         $rs = false;
722                 }
723                 
724                 if (!$rs) {
725                 // no cached rs found
726                         if ($this->debug) print " $md5file cache failure: $err<br>";
727                         $rs = &$this->Execute($sql,$inputarr,$arg3);
728                         if ($rs) {
729                                 $eof = $rs->EOF;
730                                 $rs = &$this->_rs2rs($rs);
731                                 $txt = &rs2csv($rs,false,$sql);
732                                 
733                                 if (!adodb_write_file($md5file,$txt,$this->debug) && $this->debug) print ' Cache write error<br>';
734                                 if ($rs->EOF && !$eof) {
735                                         $rs = &csv2rs($md5file,$err);           
736                                         $rs->connection = &$this; // Pablo suggestion
737                                 }  
738                                 
739                         } else
740                                 @unlink($md5file);
741                 } else { 
742                 // ok, set cached object found
743                         $rs->connection = &$this; // Pablo suggestion
744                         if ($this->debug){ 
745                         $ttl = $rs->timeCreated + $secs2cache - time();
746                         print " $md5file success ttl=$ttl<br>";
747                         }
748                 }
749                 return $rs;
750         }
751         
752         
753     /**
754          * Generates an Update Query based on an existing recordset.
755          * $arrFields is an associative array of fields with the value
756          * that should be assigned.
757          *
758          * Note: This function should only be used on a recordset
759          *       that is run against a single table.
760          *
761          * "Jonathan Younger" <jyounger@unilab.com>
762          */
763         function GetUpdateSQL(&$rs, $arrFields,$forceUpdate=false,$magicq=false)
764         {
765                 include_once(ADODB_DIR.'/adodb-lib.inc.php');
766                 return _adodb_getupdatesql($this,$rs,$arrFields,$forceUpdate,$magicq);
767         }
768
769
770         /**
771          * Generates an Insert Query based on an existing recordset.
772          * $arrFields is an associative array of fields with the value
773          * that should be assigned.
774          *
775          * Note: This function should only be used on a recordset
776          *       that is run against a single table.
777          */
778         function GetInsertSQL(&$rs, $arrFields,$magicq=false)
779         {       
780                 include_once(ADODB_DIR.'/adodb-lib.inc.php');
781                 return _adodb_getinsertsql($this,$rs,$arrFields,$magicq);
782         }
783         
784
785         /**
786         * Usage:
787         *       UpdateBlob('TABLE', 'COLUMN', $var, 'ID=1', 'BLOB');
788         *       
789         *       $blobtype supports 'BLOB' and 'CLOB'
790         *
791         *       $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
792         *       $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
793         */
794         function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
795         {
796                 $sql = "UPDATE $table SET $column=".$this->qstr($val)." where $where";
797                 $rs = $this->Execute($sql);
798                 
799                 $rez = !empty($rs);
800                 if ($rez) $rs->Close();
801                 return $rez;
802         }
803         
804         
805         /**
806         * Usage:
807         *       UpdateBlob('TABLE', 'COLUMN', $var, 'ID=1', 'CLOB');
808         *
809         *       $conn->Execute('INSERT INTO clobtable (id, clobcol) VALUES (1, null)');
810         *       $conn->UpdateClob('clobtable','clobcol',$clob,'id=1');
811         */
812         function UpdateClob($table,$column,$val,$where)
813         {
814                 return $this->UpdateBlob($table,$column,$val,$where,'CLOB');
815         }
816         
817         
818         /**
819         * not used - will probably remove in future
820         */
821         function BlankRecordSet($id=false)
822         {
823                 $rsclass = "ADORecordSet_".$this->databaseType;
824                 return new $rsclass($id);
825         }
826         
827         
828         /**
829          *  @meta       contains the desired type, which could be...
830          *      C for character. You will have to define the precision yourself.
831          *      X for teXt. For unlimited character lengths.
832          *      B for Binary
833          *  F for floating point, with no need to define scale and precision
834          *      N for decimal numbers, you will have to define the (scale, precision) yourself
835          *      D for date
836          *      T for timestamp
837          *      L for logical/Boolean
838          *      I for integer
839          *      R for autoincrement counter/integer
840          *  and if you want to use double-byte, add a 2 to the end, like C2 or X2.
841          * 
842          *
843          * @return the actual type of the data or false if no such type available
844         */
845         function ActualType($meta)
846         {
847                 switch($meta) {
848                 case 'C':
849                 case 'X':
850                         return 'VARCHAR';
851                 case 'B':
852                         
853                 case 'D':
854                 case 'T':
855                 case 'L':
856                 
857                 case 'R':
858                         
859                 case 'I':
860                 case 'N':
861                         return false;
862                 }
863         }
864         
865         
866         /*
867         * Maximum size of C field
868         */
869         function CharMax()
870         {
871                 return 255; // make it conservative if not defined
872         }
873         
874         
875         /*
876         * Maximum size of X field
877         */
878         function TextMax()
879         {
880                 return 4000; // make it conservative if not defined
881         }
882         
883         
884         /**
885          * Close Connection
886          */
887         function Close() 
888         {
889                 return $this->_close();
890                 
891                 // "Simon Lee" <simon@mediaroad.com> reports that persistent connections need 
892                 // to be closed too!
893                 //if ($this->_isPersistentConnection != true) return $this->_close();
894                 //else return true;     
895         }
896         
897         
898         /**
899          * Begin a Transaction. Must be followed by CommitTrans() or RollbackTrans().
900          *
901          * @return true if succeeded or false if database does not support transactions
902          */
903         function BeginTrans() {return false;}
904         
905         
906         /**
907          * If database does not support transactions, always return true as data always commited
908          *
909          * @return true/false.
910          */
911         function CommitTrans() 
912         { return true;}
913         
914         
915         /**
916          * If database does not support transactions, rollbacks always fail, so return false
917          *
918          * @return true/false.
919          */
920         function RollbackTrans() 
921         { return false;}
922
923
924     /**
925          * return the databases that the driver can connect to. 
926          * Some databases will return an empty array.
927          *
928          * @return an array of database names.
929          */
930         function &MetaDatabases() 
931                 {return false;}
932         
933         /**
934          * @return  array of tables for current database.
935          */ 
936     function &MetaTables() 
937         {
938                 if ($this->metaTablesSQL) {
939                         $rs = $this->Execute($this->metaTablesSQL);
940                         if ($rs === false) return false;
941                         $arr = $rs->GetArray();
942                         $arr2 = array();
943                         for ($i=0; $i < sizeof($arr); $i++) {
944                                 $arr2[] = $arr[$i][0];
945                         }
946                         $rs->Close();
947                         return $arr2;
948                 }
949                 return false;
950         }
951         
952         
953         /**
954          * List columns in a database as an array of ADOFieldObjects. 
955          * See top of file for definition of object.
956          *
957          * @params table        table name to query
958          *
959          * @return  array of ADOFieldObjects for current table.
960          */ 
961     function &MetaColumns($table) 
962         {
963         global $ADODB_FETCH_MODE;
964         
965                 if (!empty($this->metaColumnsSQL)) {
966                         $save = $ADODB_FETCH_MODE;
967                         $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
968                         $rs = $this->Execute(sprintf($this->metaColumnsSQL,strtoupper($table)));
969                         $ADODB_FETCH_MODE = $save;
970                         if ($rs === false) return false;
971
972                         $retarr = array();
973                         while (!$rs->EOF) { //print_r($rs->fields);
974                                 $fld = new ADOFieldObject();
975                                 $fld->name = $rs->fields[0];
976                                 $fld->type = $rs->fields[1];
977                                 $fld->max_length = $rs->fields[2];
978                                 $retarr[strtoupper($fld->name)] = $fld; 
979                                 
980                                 $rs->MoveNext();
981                         }
982                         $rs->Close();
983                         return $retarr; 
984                 }
985                 return false;
986         }
987       
988                 
989         /**
990          * Different SQL databases used different methods to combine strings together.
991          * This function provides a wrapper. 
992          * 
993          * @param s     variable number of string parameters
994          *
995          * Usage: $db->Concat($str1,$str2);
996          * 
997          * @return concatenated string
998          */      
999         function Concat()
1000         {       
1001                 $arr = func_get_args();
1002                 return implode($this->concat_operator, $arr);
1003         }
1004         
1005         
1006         /**
1007          * Converts a date "d" to a string that the database can understand.
1008          *
1009          * @param d     a date in Unix date time format.
1010          *
1011          * @return  date string in database date format
1012          */
1013         function DBDate($d)
1014         {
1015         // note that we are limited to 1970 to 2038
1016                 if (empty($d) && $d !== 0) return 'null';
1017
1018                 if (is_string($d)) 
1019                         if ($this->isoDates) return "'$d'";
1020                         else $d = ADORecordSet::UnixDate($d);
1021                         
1022                 return date($this->fmtDate,$d);
1023         }
1024         
1025         
1026         /**
1027          * Converts a timestamp "ts" to a string that the database can understand.
1028          *
1029          * @param ts    a timestamp in Unix date time format.
1030          *
1031          * @return  timestamp string in database timestamp format
1032          */
1033         function DBTimeStamp($ts)
1034         {
1035                 if (empty($ts) && $ts !== 0) return 'null';
1036
1037                 if (is_string($ts)) 
1038                         if ($this->isoDates) return "'$ts'";
1039                         else $ts = ADORecordSet::UnixTimeStamp($ts);
1040                 return date($this->fmtTimeStamp,$ts);
1041         }
1042         
1043         
1044         /**
1045          * Converts a timestamp "ts" to a string that the database can understand.
1046          * An example is  $db->qstr("Don't bother",magic_quotes_runtime());
1047          * 
1048          * @param s                     the string to quote
1049          * @param [magic_quotes]        if $s is GET/POST var, set to get_magic_quotes_gpc().
1050          *                              This undoes the stupidity of magic quotes for GPC.
1051          *
1052          * @return  quoted string to be sent back to database
1053          */
1054         function qstr($s,$magic_quotes=false)
1055         {       
1056         $nofixquotes=false;
1057                 if (!$magic_quotes) {
1058                 
1059                         if ($this->replaceQuote[0] == '\\'){
1060                                 $s = str_replace('\\','\\\\',$s);
1061                         }
1062                         return  "'".str_replace("'",$this->replaceQuote,$s)."'";
1063                 }
1064                 
1065                 // undo magic quotes for "
1066                 $s = str_replace('\\"','"',$s);
1067                 
1068                 if ($this->replaceQuote == "\\'")  // ' already quoted, no need to change anything
1069                         return "'$s'";
1070                 else {// change \' to '' for sybase/mssql
1071                         $s = str_replace('\\\\','\\',$s);
1072                         return "'".str_replace("\\'",$this->replaceQuote,$s)."'";
1073                 }
1074         }
1075
1076         
1077         /**
1078         * Will select the supplied $page number from a recordset, given that it is paginated in pages of 
1079         * $nrows rows per page. It also saves two boolean values saying if the given page is the first 
1080         * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination.
1081         *
1082         * See readme.htm#ex8 for an example of usage.
1083         *
1084         * @param sql
1085         * @param nrows          is the number of rows per page to get
1086         * @param page           is the page number to get (1-based)
1087         * @param [inputarr]     array of bind variables
1088         * @param [arg3]         is a private parameter only used by jlim
1089         * @param [secs2cache]           is a private parameter only used by jlim
1090         * @return               the recordset ($rs->databaseType == 'array')
1091         *
1092         * NOTE: phpLens uses a different algorithm and does not use PageExecute().
1093         *
1094         */
1095         function &PageExecute($sql, $nrows, $page, $inputarr=false, $arg3=false, $secs2cache=0) 
1096         {
1097                 include_once(ADODB_DIR.'/adodb-lib.inc.php');
1098                 return _adodb_pageexecute($this, $sql, $nrows, $page, $inputarr, $arg3, $secs2cache);
1099
1100         }
1101         
1102                 
1103         /**
1104         * Will select the supplied $page number from a recordset, given that it is paginated in pages of 
1105         * $nrows rows per page. It also saves two boolean values saying if the given page is the first 
1106         * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination.
1107         *
1108         * @param secs2cache     seconds to cache data, set to 0 to force query
1109         * @param sql
1110         * @param nrows          is the number of rows per page to get
1111         * @param page           is the page number to get (1-based)
1112         * @param [inputarr]     array of bind variables
1113         * @param [arg3]         is a private parameter only used by jlim
1114         * @return               the recordset ($rs->databaseType == 'array')
1115         */
1116         function &CachePageExecute($secs2cache, $sql, $nrows, $page,$inputarr=false, $arg3=false) {
1117                 include_once(ADODB_DIR.'/adodb-lib.inc.php');
1118                 return _adodb_pageexecute($this, $sql, $nrows, $page, $inputarr, $arg3,$secs2cache);
1119         }
1120
1121 } // end class ADOConnection
1122         
1123         
1124         
1125 //==============================================================================
1126 // CLASS ADOFetchObj
1127 //==============================================================================
1128                 
1129         /**
1130         * Internal placeholder for record objects. Used by ADORecordSet->FetchObj().
1131         */
1132         class ADOFetchObj {
1133         };
1134         
1135 //==============================================================================
1136 // CLASS ADORecordSet_empty
1137 //==============================================================================
1138         
1139         /**
1140         * Lightweight recordset when there are no records to be returned
1141         */
1142         class ADORecordSet_empty
1143         {
1144                 var $dataProvider = 'empty';
1145                 var $EOF = true;
1146                 var $_numOfRows = 0;
1147                 var $fields = false;
1148                 var $connection = false;
1149                 function RowCount() {return 0;}
1150                 function RecordCount() {return 0;}
1151                 function Close(){return true;}
1152         }
1153         
1154 //==============================================================================
1155 // CLASS ADORecordSet
1156 //==============================================================================
1157         
1158         /**
1159          * RecordSet class that represents the dataset returned by the database.
1160          * To keep memory overhead low, this class holds only the current row in memory.
1161          * No prefetching of data is done, so the RecordCount() can return -1 ( which
1162          * means recordcount not known).
1163          */
1164         class ADORecordSet {
1165         /*
1166          * public variables     
1167          */
1168         var $dataProvider = "native";
1169         var $fields = false;    // holds the current row data
1170         var $blobSize = 64;     // any varchar/char field this size or greater is treated as a blob
1171                                                         // in other words, we use a text area for editting.
1172         var $canSeek = false;   // indicates that seek is supported
1173         var $sql;                               // sql text
1174         var $EOF = false;               /* Indicates that the current record position is after the last record in a Recordset object. */
1175         
1176         var $emptyTimeStamp = '&nbsp;'; // what to display when $time==0
1177         var $emptyDate = '&nbsp;'; // what to display when $time==0
1178         var $debug = false;
1179         var $timeCreated=0;     // datetime in Unix format rs created -- for cached recordsets
1180
1181         var $bind = false;              // used by Fields() to hold array - should be private?
1182         var $fetchMode;                 // default fetch mode
1183         var $connection = false; // the parent connection
1184         /*
1185          *      private variables       
1186          */
1187         var $_numOfRows = -1;   
1188         var $_numOfFields = -1; 
1189         var $_queryID = -1;     /* This variable keeps the result link identifier.      */
1190         var $_currentRow = -1;  /* This variable keeps the current row in the Recordset.        */
1191         var $_closed = false;   /* has recordset been closed */
1192         var $_inited = false;   /* Init() should only be called once */
1193         var $_obj;              /* Used by FetchObj */
1194         var $_names;
1195         
1196         var $_currentPage = -1; /* Added by Iván Oliva to implement recordset pagination */
1197         var $_atFirstPage = false;      /* Added by Iván Oliva to implement recordset pagination */
1198         var $_atLastPage = false;       /* Added by Iván Oliva to implement recordset pagination */
1199
1200         
1201         /**
1202          * Constructor
1203          *
1204          * @param queryID       this is the queryID returned by ADOConnection->_query()
1205          *
1206          */
1207         function ADORecordSet(&$queryID) 
1208         {
1209                 $this->_queryID = $queryID;
1210         }
1211         
1212         
1213         function Init()
1214         {
1215                 if ($this->_inited) return;
1216                 $this->_inited = true;
1217                 
1218                 if ($this->_queryID) @$this->_initrs();
1219                 else {
1220                         $this->_numOfRows = 0;
1221                         $this->_numOfFields = 0;
1222                 }
1223                 if ($this->_numOfRows != 0 && $this->_numOfFields && $this->_currentRow == -1) {
1224                         $this->_currentRow = 0;
1225                         $this->EOF = ($this->_fetch() === false);
1226                 } else 
1227                         $this->EOF = true;
1228         }
1229         
1230         
1231         /**
1232          * Generate a <SELECT> string from a recordset, and return the string.
1233          * If the recordset has 2 cols, we treat the 1st col as the containing 
1234          * the text to display to the user, and 2nd col as the return value. Default
1235          * strings are compared with the FIRST column.
1236          *
1237          * @param name                  name of <SELECT>
1238          * @param [defstr]              the value to hilite. Use an array for multiple hilites for listbox.
1239          * @param [blank1stItem]        true to leave the 1st item in list empty
1240          * @param [multiple]            true for listbox, false for popup
1241          * @param [size]                #rows to show for listbox. not used by popup
1242          * @param [selectAttr]          additional attributes to defined for <SELECT>.
1243          *                              useful for holding javascript onChange='...' handlers.
1244          & @param [compareFields0]      when we have 2 cols in recordset, we compare the defstr with 
1245          *                              column 0 (1st col) if this is true. This is not documented.
1246          *
1247          * @return HTML
1248          *
1249          * changes by glen.davies@cce.ac.nz to support multiple hilited items
1250          */
1251          
1252         function GetMenu($name,$defstr='',$blank1stItem=true,$multiple=false,
1253                         $size=0, $selectAttr='',$compareFields0=true)
1254         {
1255                 include_once(ADODB_DIR.'/adodb-lib.inc.php');
1256                 return _adodb_getmenu($this, $name,$defstr,$blank1stItem,$multiple,
1257                         $size, $selectAttr,$compareFields0);
1258         }
1259         
1260         /**
1261          * Generate a <SELECT> string from a recordset, and return the string.
1262          * If the recordset has 2 cols, we treat the 1st col as the containing 
1263          * the text to display to the user, and 2nd col as the return value. Default
1264          * strings are compared with the SECOND column.
1265          *
1266          */
1267         function GetMenu2($name,$defstr='',$blank1stItem=true,$multiple=false,$size=0, $selectAttr='')  
1268         {
1269                 include_once(ADODB_DIR.'/adodb-lib.inc.php');
1270                 return _adodb_getmenu($this,$name,$defstr,$blank1stItem,$multiple,
1271                         $size, $selectAttr,false);
1272         }
1273
1274
1275         /**
1276          * return recordset as a 2-dimensional array.
1277          *
1278          * @param [nRows]  is the number of rows to return. -1 means every row.
1279          *
1280          * @return an array indexed by the rows (0-based) from the recordset
1281          */
1282         function &GetArray($nRows = -1) 
1283         {
1284                 $results = array();
1285                 $cnt = 0;
1286                 while (!$this->EOF && $nRows != $cnt) {
1287                         $results[$cnt++] = $this->fields;
1288                         $this->MoveNext();
1289                 }
1290                 
1291                 return $results;
1292         }
1293         /**
1294          * return recordset as a 2-dimensional array. 
1295          * Helper function for ADOConnection->SelectLimit()
1296          *
1297          * @param offset        is the row to start calculations from (1-based)
1298          * @param [nrows]       is the number of rows to return
1299          *
1300          * @return an array indexed by the rows (0-based) from the recordset
1301          */
1302         function &GetArrayLimit($nrows,$offset=-1) 
1303         {
1304                 if ($offset <= 0) return $this->GetArray($nrows);
1305                 $this->Move($offset);
1306                 
1307                 $results = array();
1308                 $cnt = 0;
1309                 while (!$this->EOF && $nrows != $cnt) {
1310                         $results[$cnt++] = $this->fields;
1311                         $this->MoveNext();
1312                 }
1313                 
1314                 return $results;
1315         }
1316         
1317         
1318         /**
1319          * Synonym for GetArray() for compatibility with ADO.
1320          *
1321          * @param [nRows]  is the number of rows to return. -1 means every row.
1322          *
1323          * @return an array indexed by the rows (0-based) from the recordset
1324          */
1325         function &GetRows($nRows = -1) 
1326         {
1327                 return $this->GetArray($nRows);
1328         }
1329         
1330         /**
1331          * return whole recordset as a 2-dimensional associative array if there are more than 2 columns. 
1332          * The first column is treated as the key and is not included in the array. 
1333          * If there is only 2 columns, it will return a 1 dimensional array of key-value pairs unless
1334          * $force_array == true.
1335          *
1336          * @param [force_array] has only meaning if we have 2 data columns. If false, a 1 dimensional
1337          *      array is returned, otherwise a 2 dimensional array is returned. If this sounds confusing,
1338          *      read the source.
1339          *
1340          * @return an associative array indexed by the first column of the array, 
1341          *      or false if the  data has less than 2 cols.
1342          */
1343         function &GetAssoc($force_array = false) {
1344                 $cols = $this->_numOfFields;
1345                 if ($cols < 2) {
1346                         return false;
1347                 }
1348                 $numIndex = isset($this->fields[0]);
1349                 $results = array();
1350                 if ($cols > 2 || $force_array) {
1351                         if ($numIndex) {
1352                                 while (!$this->EOF) {
1353                                 $results[trim($this->fields[0])] = array_slice($this->fields, 1);
1354                                 $this->MoveNext();
1355                                 }
1356                         } else {
1357                                 while (!$this->EOF) {
1358                                         $results[trim(reset($this->fields))] = array_slice($this->fields, 1);
1359                                         $this->MoveNext();
1360                                 }
1361                         }
1362                 } else {
1363                         // return scalar values
1364                         if ($numIndex) {
1365                                 while (!$this->EOF) {
1366                                 // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string
1367                                         $results[trim(($this->fields[0]))] = $this->fields[1];
1368                                         $this->MoveNext();
1369                                 }
1370                         } else {
1371                                 while (!$this->EOF) {
1372                                 // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string
1373                                         $v1 = trim(reset($this->fields));
1374                                         $v2 = ''.next($this->fields); 
1375                                         $results[$v1] = $v2;
1376                                         $this->MoveNext();
1377                                 }
1378                         }
1379                 }
1380                 return $results; 
1381         }
1382         
1383         
1384         /**
1385          *
1386          * @param v     is the character timestamp in YYYY-MM-DD hh:mm:ss format
1387          * @param fmt   is the format to apply to it, using date()
1388          *
1389          * @return a timestamp formated as user desires
1390          */
1391         function UserTimeStamp($v,$fmt='Y-m-d H:i:s')
1392         {
1393                 $tt = $this->UnixTimeStamp($v);
1394                 // $tt == -1 if pre 1970
1395                 if (($tt === false || $tt == -1) && $v != false) return $v;
1396                 if ($tt == 0) return $this->emptyTimeStamp;
1397                 
1398                 return date($fmt,$tt);
1399         }
1400         
1401         
1402     /**
1403          * @param v     is the character date in YYYY-MM-DD format
1404          * @param fmt   is the format to apply to it, using date()
1405          *
1406          * @return a date formated as user desires
1407          */
1408         function UserDate($v,$fmt='Y-m-d')
1409         {
1410                 $tt = $this->UnixDate($v);
1411                 // $tt == -1 if pre 1970
1412                 if (($tt === false || $tt == -1) && $v != false) return $v;
1413                 else if ($tt == 0) return $this->emptyDate;
1414                 else if ($tt == -1) { // pre-1970
1415                 }
1416                 return date($fmt,$tt);
1417         
1418         }
1419         
1420         
1421         /**
1422          * @param $v is a date string in YYYY-MM-DD format
1423          *
1424          * @return date in unix timestamp format, or 0 if before 1970, or false if invalid date format
1425          */
1426         function UnixDate($v)
1427         {
1428                 if (!preg_match( "|([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})|", 
1429                         $v, $rr)) return false;
1430                         
1431                 if ($rr[1] <= 1970) return 0;
1432                 // h-m-s-MM-DD-YY
1433                 return mktime(0,0,0,$rr[2],$rr[3],$rr[1]);
1434         }
1435         
1436
1437         /**
1438          * @param $v is a timestamp string in YYYY-MM-DD HH-NN-SS format
1439          *
1440          * @return date in unix timestamp format, or 0 if before 1970, or false if invalid date format
1441          */
1442         function UnixTimeStamp($v)
1443         {
1444                 if (!preg_match( 
1445                         "|([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})[ -]?(([0-9]{1,2}):?([0-9]{1,2}):?([0-9]{1,2}))?|", 
1446                         $v, $rr)) return false;
1447                 if ($rr[1] <= 1970 && $rr[2]<= 1) return 0;
1448         
1449                 // h-m-s-MM-DD-YY
1450                 return  @mktime($rr[4],$rr[5],$rr[6],$rr[2],$rr[3],$rr[1]);
1451         }
1452         
1453         
1454         /**
1455         * PEAR DB Compat - do not use internally
1456         */
1457         function Free()
1458         {
1459                 return $this->Close();
1460         }
1461         
1462         
1463         /**
1464         * PEAR DB compat, number of rows
1465         */
1466         function NumRows()
1467         {
1468                 return $this->_numOfRows;
1469         }
1470         
1471         
1472         /**
1473         * PEAR DB compat, number of cols
1474         */
1475         function NumCols()
1476         {
1477                 return $this->_numOfCols;
1478         }
1479         
1480         /**
1481         * Fetch a row, returning false if no more rows. 
1482         * This is PEAR DB compat mode.
1483         *
1484         * @return false or array containing the current record
1485         */
1486         function &FetchRow()
1487         {
1488                 if ($this->EOF) return false;
1489                 $arr = $this->fields;
1490                 $this->MoveNext();
1491                 return $arr;
1492         }
1493         
1494         
1495         /**
1496         * Fetch a row, returning PEAR_Error if no more rows. 
1497         * This is PEAR DB compat mode.
1498         *
1499         * @return DB_OK or error object
1500         */
1501         function FetchInto(&$arr)
1502         {
1503                 if ($this->EOF) return new PEAR_Error('EOF',-1);;
1504                 $arr = $this->fields;
1505                 $this->MoveNext();
1506                 return 1; // DB_OK
1507         }
1508         
1509         
1510         /**
1511          * Move to the first row in the recordset. Many databases do NOT support this.
1512          *
1513          * @return true or false
1514          */
1515         function MoveFirst() 
1516         {
1517                 if ($this->_currentRow == 0) return true;
1518                 return $this->Move(0);                  
1519         }                       
1520
1521         
1522         /**
1523          * Move to the last row in the recordset. 
1524          *
1525          * @return true or false
1526          */
1527         function MoveLast() 
1528         {
1529                 if ($this->_numOfRows >= 0) return $this->Move($this->_numOfRows-1);
1530                 while (!$this->EOF) $this->MoveNext();
1531                 return true;
1532         }
1533         
1534         
1535         /**
1536          * Move to next record in the recordset.
1537          *
1538          * @return true if there still rows available, or false if there are no more rows (EOF).
1539          */
1540         function MoveNext() 
1541         {
1542                 if (!$this->EOF) {
1543                         $this->_currentRow++;
1544                         if ($this->_fetch()) return true;
1545                 }
1546                 $this->EOF = true;
1547                 /* -- tested error handling when scrolling cursor -- seems useless.
1548                 $conn = $this->connection;
1549                 if ($conn && $conn->raiseErrorFn && ($errno = $conn->ErrorNo())) {
1550                         $fn = $conn->raiseErrorFn;
1551                         $fn($conn->databaseType,'MOVENEXT',$errno,$conn->ErrorMsg().' ('.$this->sql.')',$conn->host,$conn->database);
1552                 }
1553                 */
1554                 return false;
1555         }       
1556         
1557         /**
1558          * Random access to a specific row in the recordset. Some databases do not support
1559          * access to previous rows in the databases (no scrolling backwards).
1560          *
1561          * @param rowNumber is the row to move to (0-based)
1562          *
1563          * @return true if there still rows available, or false if there are no more rows (EOF).
1564          */
1565         function Move($rowNumber = 0) 
1566         {
1567                 if ($rowNumber == $this->_currentRow) return true;
1568                 if ($rowNumber > $this->_numOfRows)
1569                 if ($this->_numOfRows != -1) $rowNumber = $this->_numOfRows-1;
1570    
1571         if ($this->canSeek) {
1572                 if ($this->_seek($rowNumber)) {
1573                                 $this->_currentRow = $rowNumber;
1574                                 if ($this->_fetch()) {
1575                                         $this->EOF = false;     
1576                                    //  $this->_currentRow += 1;                 
1577                                         return true;
1578                                 }
1579                         } else 
1580                                 return false;
1581         } else {
1582             if ($rowNumber < $this->_currentRow) return false;
1583             while (! $this->EOF && $this->_currentRow < $rowNumber) {
1584                                 $this->_currentRow++;
1585                 if (!$this->_fetch()) $this->EOF = true;
1586                         }
1587             return !($this->EOF);
1588         }
1589                 
1590                 $this->fields = null;   
1591                 $this->EOF = true;
1592                 return false;
1593         }
1594         
1595                 
1596         /**
1597          * Get the value of a field in the current row by column name.
1598          * Will not work if ADODB_FETCH_MODE is set to ADODB_FETCH_NUM.
1599          * 
1600          * @param colname  is the field to access
1601          *
1602          * @return the value of $colname column
1603          */
1604         function Fields($colname)
1605         {
1606                 return $this->fields[$colname];
1607         }
1608         
1609         
1610   /**
1611    * Use associative array to get fields array for databases that do not support
1612    * associative arrays. Submitted by Paolo S. Asioli paolo.asioli@libero.it
1613    *
1614    * If you don't want uppercase cols, set $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC
1615    * before you execute your SQL statement, and access $rs->fields['col'] directly.
1616    */
1617         function &GetRowAssoc($upper=true)
1618         {
1619          
1620                 if (!$this->bind) {
1621                         $this->bind = array();
1622                         for ($i=0; $i < $this->_numOfFields; $i++) {
1623                                 $o = $this->FetchField($i);
1624                                 $this->bind[($upper) ? strtoupper($o->name) : strtolower($o->name)] = $i;
1625                         }
1626                 }
1627                 
1628                 $record = array();
1629                 foreach($this->bind as $k => $v) {
1630             $record[$k] = $this->fields[$v];
1631         }
1632
1633         return $record;
1634     }
1635         
1636         
1637         /**
1638          * Clean up recordset
1639          *
1640          * @return true or false
1641          */
1642         function Close() 
1643         {
1644                 // free connection object - this seems to globally free the object
1645                 // and not merely the reference, so don't do this...
1646                 // $this->connection = false; 
1647                 if (!$this->_closed) {
1648                         $this->_closed = true;
1649                         return $this->_close();         
1650                 } else
1651                         return true;
1652         }
1653         
1654         /**
1655          * synonyms RecordCount and RowCount    
1656          *
1657          * @return the number of rows or -1 if this is not supported
1658          */
1659         function RecordCount() {return $this->_numOfRows;}
1660         
1661         
1662         /**
1663          * synonyms RecordCount and RowCount    
1664          *
1665          * @return the number of rows or -1 if this is not supported
1666          */
1667         function RowCount() {return $this->_numOfRows;} 
1668         
1669
1670          /**
1671          * Portable RecordCount. Pablo Roca <pabloroca@mvps.org>
1672          *
1673      * @return  the number of records from a previous SELECT. All databases support this.
1674          *
1675          * But aware possible problems in multiuser environments. For better speed the table
1676          * must be indexed by the condition. Heavy test this before deploying.
1677      */ 
1678     function PO_RecordCount($table="", $condition="") {
1679         
1680         $lnumrows = $this->_numOfRows;
1681         // the database doesn't support native recordcount, so we do a workaround
1682         if ($lnumrows == -1 && $this->connection) {
1683             IF ($table) {
1684                 if ($condition) $condition = " WHERE " . $condition; 
1685                 $resultrows = &$this->connection->Execute("SELECT COUNT(*) FROM $table $condition");
1686                 if ($resultrows) $lnumrows = reset($resultrows->fields);
1687             }
1688         }
1689         return $lnumrows;
1690     }
1691         
1692         /**
1693          * @return the current row in the recordset. If at EOF, will return the last row. 0-based.
1694          */
1695         function CurrentRow() {return $this->_currentRow;}
1696         
1697         /**
1698          * synonym for CurrentRow -- for ADO compat
1699          *
1700          * @return the current row in the recordset. If at EOF, will return the last row. 0-based.
1701          */
1702         function AbsolutePosition() {return $this->_currentRow;}
1703         
1704         /**
1705          * @return the number of columns in the recordset. Some databases will set this to 0
1706          * if no records are returned, others will return the number of columns in the query.
1707          */
1708         function FieldCount() {return $this->_numOfFields;}   
1709
1710
1711         /**
1712          * Get the ADOFieldObject of a specific column.
1713          *
1714          * @param fieldoffset   is the column position to access(0-based).
1715          *
1716          * @return the ADOFieldObject for that column, or false.
1717          */
1718         function &FetchField($fieldoffset) 
1719         {
1720                 // must be defined by child class
1721         }       
1722         
1723         /**
1724         * Return the fields array of the current row as an object for convenience.
1725         * 
1726         * @param $isupper to set the object property names to uppercase
1727         *
1728         * @return the object with the properties set to the fields of the current row
1729         */
1730         function &FetchObject($isupper=true)
1731         {
1732                 if (empty($this->_obj)) {
1733                         $this->_obj = new ADOFetchObj();
1734                         $this->_names = array();
1735                         for ($i=0; $i <$this->_numOfFields; $i++) {
1736                                 $f = $this->FetchField($i);
1737                                 $this->_names[] = $f->name;
1738                         }
1739                 }
1740                 $i = 0;
1741                 $o = &$this->_obj;
1742                 for ($i=0; $i <$this->_numOfFields; $i++) {
1743                         $name = $this->_names[$i];
1744                         if ($isupper) $n = strtoupper($name);
1745                         else $n = $name;
1746                         
1747                         $o->$n = $this->Fields($name);
1748                 }
1749                 return $o;
1750         }
1751         /**
1752         * Return the fields array of the current row as an object for convenience.
1753         * 
1754         * @param $isupper to set the object property names to uppercase
1755         *
1756         * @return the object with the properties set to the fields of the current row,
1757         *       or false if EOF
1758         *
1759         * Fixed bug reported by tim@orotech.net
1760         */
1761         function &FetchNextObject($isupper=true)
1762         {
1763                 $o = false;
1764                 if ($this->_numOfRows != 0 && !$this->EOF) {
1765                         $o = $this->FetchObject($isupper);      
1766                         $this->_currentRow++;
1767                         if ($this->_fetch()) return $o;
1768                 }
1769                 $this->EOF = true;
1770                 return $o;
1771         }
1772         
1773         /**
1774          * Get the metatype of the column. This is used for formatting. This is because
1775          * many databases use different names for the same type, so we transform the original
1776          * type to our standardised version which uses 1 character codes:
1777          *
1778          * @param t  is the type passed in. Normally is ADOFieldObject->type.
1779          * @param len is the maximum length of that field. This is because we treat character
1780          *      fields bigger than a certain size as a 'B' (blob).
1781          * @param fieldobj is the field object returned by the database driver. Can hold
1782          *      additional info (eg. primary_key for mysql).
1783          * 
1784          * @return the general type of the data: 
1785          *      C for character < 200 chars
1786          *      X for teXt (>= 200 chars)
1787          *      B for Binary
1788          *      N for numeric floating point
1789          *      D for date
1790          *      T for timestamp
1791          *      L for logical/Boolean
1792          *      I for integer
1793          *      R for autoincrement counter/integer
1794          * 
1795          *
1796         */
1797         function MetaType($t,$len=-1,$fieldobj=false)
1798         {
1799                 switch (strtoupper($t)) {
1800                 case 'VARCHAR':
1801                 case 'VARCHAR2':
1802                 case 'CHAR':
1803                 case 'STRING':
1804                 case 'C':
1805                 case 'NCHAR':
1806                 case 'NVARCHAR':
1807                 case 'VARYING':
1808                 case 'BPCHAR':
1809                         if (!empty($this)) if ($len <= $this->blobSize) return 'C';
1810                         else if ($len <= 250) return 'C';
1811                 
1812                 case 'LONGCHAR':
1813                 case 'TEXT':
1814                 case 'M':
1815                 case 'X':
1816                 case 'CLOB':
1817                 case 'NCLOB':
1818                 case 'LONG':
1819                         return 'X';
1820                 
1821                 case 'BLOB':
1822                 case 'NTEXT':
1823                 case 'BINARY':
1824                 case 'VARBINARY':
1825                 case 'LONGBINARY':
1826                 case 'B':
1827                         return 'B';
1828                         
1829                 case 'DATE':
1830                 case 'D':
1831                         return 'D';
1832                 
1833                 
1834                 case 'TIME':
1835                 case 'TIMESTAMP':
1836                 case 'DATETIME':
1837                 case 'T':
1838                         return 'T';
1839                 
1840                 case 'BOOLEAN': 
1841                 case 'BIT':
1842                 case 'L':
1843                         return 'L';
1844                         
1845                 case 'COUNTER':
1846                 case 'R':
1847                         return 'R';
1848                         
1849                 case 'INT':
1850                 case 'INTEGER':
1851                 case 'SHORT':
1852                 case 'TINYINT':
1853                 case 'SMALLINT':
1854                 case 'I':
1855                         if (!empty($fieldobj->primary_key)) return 'R';
1856                         return 'I';
1857                         
1858                 default: return 'N';
1859                 }
1860         }
1861         
1862         function _close() {}
1863         
1864         /**
1865          * set/returns the current recordset page when paginating
1866          */
1867         function AbsolutePage($page=-1)
1868         {
1869                 if ($page != -1) $this->_currentPage = $page;
1870                 return $this->_currentPage;
1871         }
1872         
1873         /**
1874          * set/returns the status of the atFirstPage flag when paginating
1875          */
1876         function AtFirstPage($status=false)
1877         {
1878                 if ($status != false) $this->_atFirstPage = $status;
1879                 return $this->_atFirstPage;
1880         }
1881         
1882         /**
1883          * set/returns the status of the atLastPage flag when paginating
1884          */
1885         function AtLastPage($status=false)
1886         {
1887                 if ($status != false) $this->_atLastPage = $status;
1888                 return $this->_atLastPage;
1889         }
1890 } // end class ADORecordSet
1891         
1892 //==============================================================================
1893 // CLASS ADORecordSet_array
1894 //==============================================================================
1895         
1896         /**
1897          * This class encapsulates the concept of a recordset created in memory
1898          * as an array. This is useful for the creation of cached recordsets.
1899          * 
1900          * Note that the constructor is different from the standard ADORecordSet
1901          */
1902         
1903         class ADORecordSet_array extends ADORecordSet
1904         {
1905                 var $databaseType = "array";
1906         
1907                 var $_array;    // holds the 2-dimensional data array
1908                 var $_types;    // the array of types of each column (C B I L M)
1909                 var $_colnames; // names of each column in array
1910                 var $_skiprow1; // skip 1st row because it holds column names
1911                 var $_fieldarr; // holds array of field objects
1912                 var $canSeek = true;
1913                 var $affectedrows = false;
1914                 var $insertid = false;
1915                 var $sql = '';
1916                 /**
1917                  * Constructor
1918                  *
1919                  */
1920                 function ADORecordSet_array($fakeid=1)
1921                 {
1922                         $this->ADORecordSet($fakeid); // fake queryID
1923                 }
1924                 
1925                 
1926                 /**
1927                  * Setup the Array. Later we will have XML-Data and CSV handlers
1928                  *
1929                  * @param array         is a 2-dimensional array holding the data.
1930                  *                      The first row should hold the column names 
1931                  *                      unless paramter $colnames is used.
1932                  * @param typearr       holds an array of types. These are the same types 
1933                  *                      used in MetaTypes (C,B,L,I,N).
1934                  * @param [colnames]    array of column names. If set, then the first row of
1935                  *                      $array should not hold the column names.
1936                  */
1937                 function InitArray(&$array,$typearr,$colnames=false)
1938                 {
1939                         $this->_array = $array;
1940                         $this->_types = &$typearr;      
1941                         if ($colnames) {
1942                                 $this->_skiprow1 = false;
1943                                 $this->_colnames = $colnames;
1944                         } else $this->_colnames = $array[0];
1945                         
1946                         $this->Init();
1947                 }
1948                 /**
1949                  * Setup the Array and datatype file objects
1950                  *
1951                  * @param array         is a 2-dimensional array holding the data.
1952                  *                      The first row should hold the column names 
1953                  *                      unless paramter $colnames is used.
1954                  * @param fieldarr      holds an array of ADOFieldObject's.
1955                  */
1956                 function InitArrayFields(&$array,&$fieldarr)
1957                 {
1958                         $this->_array = &$array;
1959                         $this->_skiprow1= false;
1960                         if ($fieldarr) {
1961                                 $this->_fieldobjects = &$fieldarr;
1962                         } 
1963                         
1964                         $this->Init();
1965                 }
1966                 
1967                 function _initrs()
1968                 {
1969                         $this->_numOfRows =  sizeof($this->_array);
1970                         if ($this->_skiprow1) $this->_numOfRows -= 1;
1971                 
1972                         $this->_numOfFields =(isset($this->_fieldobjects)) ?
1973                                  sizeof($this->_fieldobjects):sizeof($this->_types);
1974                 }
1975                 
1976                 /* Use associative array to get fields array */
1977                 function Fields($colname)
1978                 {
1979                         if (!$this->bind) {
1980                                 $this->bind = array();
1981                                 for ($i=0; $i < $this->_numOfFields; $i++) {
1982                                         $o = $this->FetchField($i);
1983                                         $this->bind[strtoupper($o->name)] = $i;
1984                                 }
1985                         }
1986                         
1987                          return $this->fields[$this->bind[strtoupper($colname)]];
1988                 }
1989                 
1990                 function &FetchField($fieldOffset = -1) 
1991                 {
1992                         if (isset($this->_fieldobjects)) {
1993                                 return $this->_fieldobjects[$fieldOffset];
1994                         }
1995                         $o =  new ADOFieldObject();
1996                         $o->name = $this->_colnames[$fieldOffset];
1997                         $o->type =  $this->_types[$fieldOffset];
1998                         $o->max_length = -1; // length not known
1999                         
2000                         return $o;
2001                 }
2002                         
2003                 function _seek($row)
2004                 {
2005                         return true;
2006                 }
2007                 
2008                 function _fetch()
2009                 {
2010                         $pos = $this->_currentRow;
2011                         
2012                         if ($this->_skiprow1) {
2013                                 if ($this->_numOfRows <= $pos-1) return false;
2014                                 $pos += 1;
2015                         } else {
2016                                 if ($this->_numOfRows <= $pos) return false;
2017                         }
2018                         
2019                         $this->fields = $this->_array[$pos];
2020                         return true;
2021                 }
2022                 
2023                 function _close() 
2024                 {
2025                         return true;    
2026                 }
2027         
2028         } // ADORecordSet_array
2029
2030 //==============================================================================
2031 // HELPER FUNCTIONS
2032 //==============================================================================
2033         
2034     /**
2035          * Synonym for ADOLoadCode.
2036          *
2037          * @deprecated
2038          */
2039         function ADOLoadDB($dbType) 
2040         { 
2041                 return ADOLoadCode($dbType);
2042         }
2043         
2044     /**
2045          * Load the code for a specific database driver
2046          */
2047     function ADOLoadCode($dbType) 
2048         {
2049         GLOBAL $ADODB_Database;
2050         
2051                 if (!$dbType) return false;
2052                 $ADODB_Database = strtolower($dbType);
2053                 switch ($ADODB_Database) {
2054                         case 'maxsql': $ADODB_Database = 'mysqlt'; break;
2055                         case 'pgsql': $ADODB_Database = 'postgres7'; break;
2056                 }
2057                 include_once(ADODB_DIR."/adodb-$ADODB_Database.inc.php");               
2058                 return true;            
2059         }
2060
2061         /**
2062          * synonym for ADONewConnection for people who cannot remember the correct name
2063          */
2064         function &NewADOConnection($db='')
2065         {
2066                 return ADONewConnection($db);
2067         }
2068         
2069         /**
2070          * Instantiate a new Connection class for a specific database driver.
2071          *
2072          * @param [db]  is the database Connection object to create. If undefined,
2073          *      use the last database driver that was loaded by ADOLoadCode().
2074          *
2075          * @return the freshly created instance of the Connection class.
2076          */
2077         function &ADONewConnection($db='')
2078         {
2079         GLOBAL $ADODB_Database;
2080         
2081                 if ($db) {
2082                         if ($ADODB_Database != $db) ADOLoadCode($db);
2083                 } else { 
2084                         if (!empty($ADODB_Database)) ADOLoadCode($ADODB_Database);
2085                         else print "<p>ADONewConnection: No database driver defined</p>";
2086                 }
2087                 
2088                 $cls = 'ADODB_'.$ADODB_Database;
2089                 $obj = new $cls();
2090                 if (defined('ADODB_ERROR_HANDLER')) {
2091                         $obj->raiseErrorFn = ADODB_ERROR_HANDLER;
2092                 }
2093                 return $obj;
2094         }
2095         
2096         /**
2097         * Save a file $filename and its $contents (normally for caching) with file locking
2098         */
2099         function adodb_write_file($filename, $contents,$debug=false)
2100         { 
2101         # http://www.php.net/bugs.php?id=9203 Bug that flock fails on Windows
2102         # So to simulate locking, we assume that rename is an atomic operation.
2103         # First we delete $filename, then we create a $tempfile write to it and 
2104         # rename to the desired $filename. If the rename works, then we successfully 
2105         # modified the file exclusively.
2106         # What a stupid need - having to simulate locking.
2107         # Risks:
2108         # 1. $tempfile name is not unique -- very very low
2109         # 2. unlink($filename) fails -- ok, rename will fail
2110         # 3. adodb reads stale file because unlink fails -- ok, $rs timeout occurs
2111         # 4. another process creates $filename between unlink() and rename() -- ok, rename() fails and  cache updated
2112                 if (strpos(strtoupper(PHP_OS),'WIN') !== false) {
2113                         // skip the decimal place
2114                         $mtime = substr(str_replace(' ','_',microtime()),2); 
2115                         // unlink will let some latencies develop, so uniqid() is more random
2116                         @unlink($filename);
2117                         // getmypid() actually returns 0 on Win98 - never mind!
2118                         $tmpname = $filename.uniqid($mtime).getmypid();
2119                         if (!($fd = fopen($tmpname,'a'))) return false;
2120                         $ok = ftruncate($fd,0);                 
2121                         if (!fwrite($fd,$contents)) $ok = false;
2122                         fclose($fd);
2123                         chmod($tmpname,0644);
2124                         if (!@rename($tmpname,$filename)) {
2125                                 unlink($tmpname);
2126                                 $ok = false;
2127                         }
2128                         if ($debug && !$ok) print " Rename $tmpname ".($ok? 'ok' : 'failed')." <br>";
2129                         return $ok;
2130                 }
2131                 if (!($fd = fopen($filename, 'a'))) return false;
2132                 if (flock($fd, LOCK_EX) && ftruncate($fd, 0)) {
2133                         $ok = fwrite( $fd, $contents );
2134                         fclose($fd);
2135                         chmod($filename,0644);
2136                 }else {
2137                         fclose($fd);
2138                         if ($debug)print " Failed acquiring lock for $filename<br>";
2139                         $ok = false;
2140                 }
2141         
2142                 return $ok;
2143         }
2144
2145 } // defined
2146
2147 // For emacs users
2148 // Local Variables:
2149 // mode: php
2150 // tab-width: 4
2151 // c-basic-offset: 4
2152 // c-hanging-comment-ender-p: nil
2153 // indent-tabs-mode: nil
2154 // End:
2155 ?>