Location: PHPKode > scripts > dbff > dbff/dbff.php
<?php
/*  Name: dbff.php
	Author: Jerry Mattsson
	Version: 0.99 	See the Text-file, dbff.doc for Usage and License info.
	Created: March-2005, Updated july-Aug 2005, Performance changes May 2006
	Copyright(©) 2005 Jerry Mattsson, www.Timehole.com, Released under GPL2
        Oct-06 v0.97 fixed int,number,date check bug, added natsort variants,
           some function name changes, more array tests, error texts and encryption trace msg. /jm
        Oct-06 v0.98 Another date bug fixed, mktime gen err monthlist in parse,
           changed default logswitch to monthly, Added LCMP, "like compare" Case ignore begin/like search /jm
        Nov-06 v0.99 date2str accepts datetime without errors, More filetests and change filetime chk
           and reRead in read/write functions. Minor code cleanup.
*/
require_once 'dbff_errm.php';
$dbfferr    = '';
//$dbff_dir =  /// Set globally somewhere else if desired
  /// //////////////////////
 /// "DB" File functions //
/// //////////////////////
class dbff {
/* *********************************************************************************
Reads and writes file(s) in and out of an record array in a relational fashion.
Use it with care and at your own risk, do not blame me if it fails. I do not take any
responsible for any consequences or problems that might be caused by errors in this code.
Uses flatfile(s) tagged + delimiter/csv style, non quoted format, See Doc file for more info
  Public functions that can be used in the application is:
     read, insert, delete, update, commit, rollback, getRec and reRead, updByRn, delByRn, GetByRn
  Functions that needs a record definition:
     select, selectSort, reselect, aselect, selectGet, getSelectedRn,
     updateSelected, deleteSelected, getRnByKey
  Utility Functions: NN, PK, UK, FK, size, type, usage
********************************************************************************* */
   var $datetimefmt= 'Y-M-d H:i:s';/// DateTime "Display" format stored as yyyymmddHHMMSS, date() syntax
   var $datefmt    = 'Y-M-d';  /// Date "Display" format stored as yyyymmdd, date() syntax
   var $file       = NULL;     /// File name
   var $delimiter  = ';';      /// Field delimiter in file records (or good choice ... §)
   var $dir_delim  = '/';      /// Directory delimiter, linux !
   var $keyfld     = 0;        /// Key field number, Default = 0 if pk not defined
   var $recdef     = FALSE;    /// Optional record specification
   var $changelog  = TRUE;     /// Log all changes to log-file xxx_log ( + optional date str )
   var $logswitch  = 'M';     /// Set to m,w,d or h for a switch of logfile at month, week, day or hour
   var $tblname    = NULL;     /// Tablename (set if defined in record def )
   var $maxline    = 4096;     /// Max file length, greater than sum of all fields of biggest record
   var $maxlockwait= 10;       /// Max time to wait for a lock until fail ( seconds )
   var $tag        = 'R';      /// Record tag string
   var $rectype    = FALSE;    /// 2=Tag each rec with op and timestamp, 1=No timestamp, False=def =>2
   var $errstk     = array (); /// Stack of Error messages
   /// Variables below is maintined by the class functions
   var $comments   = array (); /// Comment lines in beginning of file
   var $recs       = array (); /// Holds all records of a file
   var $recmap     = array (); /// One element / record with status, O(riginal),I(nserted),U(pdated)
   var $tsrecs     = array (); /// Holds old timestamp for record in fmt I=1234567890
   var $rowcnt     = 0;        /// Record count in array "recs"
   var $lastInsRn  = NULL; 
   var $fp         = FALSE;    /// File pointer
   var $maxkey     = 0;        /// Max id/key for record in insert
   var $seqkey     = FALSE;    /// Set if PK has USAGE=SEQUENCE for auto incremented numeric key
   var $filetime   = NULL;     /// File modification time at read
   var $operation  = NULL;     /// INSERT or MIXED
   var $mode       = NULL;     /// Lock mode
   var $is_read    = FALSE;    /// File is read into array
   var $is_open    = FALSE;
   var $ctlcnt     = 0;        /// Count of Control Records

   /// Used if record def is used
   var $uc_recdef  = NULL;     /// Upper Case recdef, Set when recdef is parsed
   var $selrecs    = array (); /// Pointer array to selected records
   var $selptr     = array (); /// Pointer to next "select record" to get
   var $recflds    = array (); /// Upper case field names for table as fldname=>pos
   var $fldnames   = array (); /// Holds fieldnames as entered after record definition is parsed
   var $pkkeys     = array (); /// Inverted array for pk-values, points to rec in array
   var $ukkeys     = array (); /// As pkkeys but for uk and with fldname as added key
   var $trace      = FALSE;    /// Internal trace, prints trace stuff
   var $trace_level= 1;        /// Trace level, prints more or less trace stuff. 1(less)-3(more)
   var $month3name = array (); /// Locale short month names, jan, feb ...

   /// wrapper functions for an "external" encryption functions
   var $pw                   = '';
   var $crypt_class          = 'rc4crypt.php';
   var $is_encrypted         = FALSE;
   /// Use your favorite encryption or the gpl rc4 class from sourceforge.net,
   /// Assumes that there are one encrypt and one decrypt function in the class
   function scramble_init ($pwd) {
      if (empty($pwd)) return;
      if (!$this->is_encrypted) {
         require_once $this->crypt_class; // check for fail/exist and if return;
         $this->scramble     = new rc4crypt;
         $this->pw           = $pwd;
         $this->is_encrypted = TRUE;
      }
   }
   function scrambleData ($data) {
      if ($this->is_encrypted) return base64_encode($this->scramble->encrypt($this->pw,$data));
      return $data;
   }
   function descrambleData ($data) {
      if ($this->is_encrypted) return $this->scramble->decrypt($this->pw,base64_decode($data));
      return $data;
   }
   /// //////////////////////////////////////////////////
   /// //// RECORD DEFINITION DEPENDANT FUNCTIONS ///////
   /// //// Requires a record definition to work  ///////
   /// //////////////////////////////////////////////////
   function select ($fname=NULL, $value=NULL, $op=NULL) {
   /// Returns number of records found with a field name search, Requires a record definition
      if ($this->trace) $this->errm('T',802);
      if (!$this->is_read) $this->read();
      if (!$this->hasRecDef())   { $this->errm('E',105); return NULL; }
      $allrecs          = $this->selectAll();
      $this->selptr     = 0;
      if (is_null($fname)) {
         $this->selrecs = $allrecs; return count($allrecs); }  /// Select all records
      if (is_null($fno = $this->fldname2fldno($fname))) 
	 { $this->errm('E',103,$fname); return NULL; }
      if (is_null($value)) { $this->errm('E',104,$fname); return NULL; }
      $this->selrecs    = $this->searchRecs($fname, $value, $allrecs, $op);
      if ($this->selrecs==NULL) return 0;
      else                      return count($this->selrecs);
   } /// select

   function selectSort ($fname, $sort=NULL) {
   /// Sorts selected records, sort can be ASC or DESC
      if (is_null($fno = $this->fldname2fldno($fname))) { $this->errm('E',110,$fname); return FALSE; }
      if (($j=count($this->selrecs))==0) return TRUE;
      for ($i=0;$i<$j; $i++)  $r[$this->selrecs[$i]] = $this->recs[$this->selrecs[$i]][$fno];
      if (!is_null($sort)) $sort = strtoupper($sort);
      if     ($sort=='DESC')        arsort($r);
      elseif ($sort=='NATSORT')     natsort($r);
      elseif ($sort=='NATCASESORT') natcasesort($r);
      else                          asort($r);
      reset ($r);
      for ($i=0;$i<$j; $i++) { $q[$i] = key($r); next($r); }
      $this->selrecs = $q;
      $this->selptr  = 0;
      return TRUE;
   } /// selectSort

   function reselect ($fname, $value, $op=NULL) {
   /// "And function" select, selects from allready selected records, works as select
      if (is_null($this->selrecs)) { if ($this->trace) $this->errm('T',803);    return 0; }
      if (is_null($this->fldname2fldno($fname))) { $this->errm('E',121,$fname); return 0; }
      if ($this->trace) $in  = count($this->selrecs);
      $recs          = $this->searchRecs($fname, $value, $this->selrecs, $op);
      $out           = count($recs);
      $this->selrecs = $recs;
      $this->selptr  = 0;
      if ($this->trace) $this->errm('T',804,$in,$out,$value,$fname);
      if (is_null($this->selrecs)) return 0;
      else                         return $out;
   } /// reselect

   function aselect ($fname, $value, $op=NULL) {
   /// "Or function" select, selects from all records, and adds result set to selected records
      if (is_null($this->fldname2fldno($fname))) { $this->errm('E',130,$fname); return 0; }
      $allrecs = $this->selectAll();
      $recs    = $this->searchRecs($fname, $value, $allrecs, $op);
      $in      = count($this->selrecs);
      for ($i=0; $i<count($recs); $i++) {
         if ($in>0 && in_array($recs[$i],$this->selrecs)) continue; /// Already in set
         $this->selrecs[] = $recs[$i];                              /// Add found rec
      }
      $out           = count($this->selrecs);
      $this->selptr  = 0;
      if ($this->trace) $this->errm('T',806, $in, $out, $value, $fname);
      if (is_null($this->selrecs)) return 0;
      else                         return $out;
   } /// aselect

   function selectGet ($namedIdx=TRUE) {
   /// Returns "next" record from a previous select search or NULL
      $max = count($this->selrecs);
      if ($this->selptr >= $max || $max < 1) {
         $this->selptr   = 0;
         if ($this->trace) $this->errm('T',808);
         return NULL;
      }
      $rec = $this->formatRec ($this->selrecs[$this->selptr], $namedIdx);
      $this->selptr++;
      if ($this->trace) $this->errm('T',810,$this->selptr,NULL,NULL,NULL,2);
      return $rec;
   } /// selectGet

   function getSelectedRn () {
   /// Returns current record ptr from a previous select search or NULL
      $max = count($this->selrecs);
      if (is_null($this->selptr) || $this->selptr > $max || $max < 1) return NULL;
      $ptr = $this->selptr - 1;
      return $this->selrecs[$ptr];
   } /// getSelectedRn

   function getLastInsRec ($namedIdx=TRUE) {
   /// Returns last inserted record number or NULL
      return $this->getRecByRn ($this->lastInsRn, $namedIdx);
   } /// getLastInsRec

   function updateSelected ($fname, $value) {
   /// Updates all records selected with new value for fld, Returns no of records updated
      if (is_null($this->selrecs)) { $this->errm('E',140); return 0; }
      if (is_null($fno = $this->fldname2fldno($fname))) { $this->errm('E',141,$fname); return 0; }
      if (is_string($value)) $value = trim($value);
      for ($i=0; $i<count($this->selrecs); $i++) {
         $rec       = $this->recs[$this->selrecs[$i]];
         $rec[$fno] = $value;
         $this->updByRn ($this->selrecs[$i], $rec);
      }
      return $i;
   } /// updateSelected

   function deleteSelected () {
   /// Deletes all records selected, returns no of records deleted
      if (is_null($this->selrecs))                { $this->errm('E',150); return 0;  }
      for ($i=0; $i<count($this->selrecs); $i++)
	 if (!$this->delByRn($this->selrecs[$i])) { $this->errm('E',151);  return NULL; }
      return $i;
   } /// deleteSelected

   function selectAll () {
   /// Selects all records
      $r = array();
      if ($this->trace) $this->errm('T',812);
      for ($i=0; $i<$this->rowcnt; $i++) if ($this->recmap[$i]<>'D') $r[] = $i;
      if ($this->trace) $this->errm('T',814,count($r));
      return $r;
   }
   function getRnByKey ($value, $fno=NULL, $fname=NULL) {
      /// Is there a pk or uk to search on? Return recno if found.
      if (!is_null($fname)) $fn = $fname;
      else {
         if (is_null($fno)) $fn = $this->fldno2fldname($this->keyfld);
         else               $fn = $this->fldno2fldname($fno);
      }
      $i = NULL;
      if (is_string($value)) $value = trim($value);
      if ($this->pk($fn) || $this->uk($fn)) {
         if ($this->type($fn)=='DATE')
            if (!$value=$this->str2date($value)) return NULL;     /// InValid date
         if ($this->type($fn)=='DATETIME' || $this->type($fn)=='DATETIME')
            if (!$value=$this->str2datetime($value)) return NULL; /// InValid date
         //settype($value,gettype(current($this->pkkeys))); ?? what key/value ?
         if ($this->pk($fn) && isset($this->pkkeys[$value]))      $i = $this->pkkeys[$value];
         if ($this->uk($fn) && isset($this->ukkeys[$fn][$value])) $i = $this->ukkeys[$fn][$value];
         if ($this->trace && !is_null($i)) $this->errm('T',828,$fn);
         if (is_null($i) || $this->recmap[$i]=='D') return NULL;
         else                                       return $i;
      }
      return NULL;
   } /// getRnByKey

   /// Privates ///
   function searchRecs ($fname, $value, $selmap=NULL, $op=NULL) {
   /// Search fieldname for value, returns matching record(s) in array
      if ($this->trace) {
         if (empty($value)) $v = ' (NULL) ';
         else               $v = $value;
         $this->errm('T',816,$v,$fname,$op);
      }
      if (is_null($fno = $this->fldname2fldno($fname))) { $this->errm('E',111,$fname); return NULL; }
      if (is_string($value)) $value = trim($value);
      $op         = $this->chkOp($op);
      if (is_null($op)) return NULL;
      if ($this->isAttSet($fname,'UPPER')) $value = strtoupper($value);
      if ($this->isAttSet($fname,'LOWER')) $value = strtolower($value);
      if ($op =='EQUAL') { ///  and pk or fk
         $i = $this->getRnByKey($value, NULL, $fname);
         if (!is_null($i)) {
	    $recs[] = $i;
            if ($this->trace) $this->errm('T',818,$i);
	    return $recs;
	 }
      }
      $recs = $this->scanRecs($value, $fno, $selmap, $op);
      if ($this->trace) $this->errm('T',820,count($recs),count($selmap));
      return $recs;
   } /// searchRecs

   function parseRecDef () {
   /// Scans definition and makes all keywords upper case and stores field names
   global $dbff_dir;
   global $dir_delim;
   $new   = array ();
   $fname = "";
   $val   = NULL;
   if ($this->trace) $this->errm('T',822);
   if (substr($dbff_dir,strlen($dbff_dir)-1)==$dir_delim) $dir_delim = NULL;
   if ($this->hasRecDef()) return TRUE;
   if (!is_array($this->recdef)) { $this->errm('E',155); return FALSE; }
   // make locale short month names
   for ($i=1; $i<13; $i++)  $this->month3name[$i] = strtolower(date('M',mktime( 1,1,1,$i,1)));
   reset ($this->recdef); // unset ??
   while (list($fname, $val) = each ($this->recdef)) {
      if (!is_array($val)) {
         if (strtoupper($fname)=='TABLE_NAME') $this->tblname = strtoupper($val);
         if (strtoupper($fname)=='FILE_NAME')
	    if (isset($dbff_dir))    $this->file = $dbff_dir.$dir_delim.$val;
            else                     $this->file = $val;
         if (strtoupper($fname)=='MINSEQ') { $this->minseq  = $val; $this->maxkey = $val; }
         if (strtoupper($fname)=='MAXSEQ')   $this->maxseq  = $val;
         if (strtoupper($fname)=='PASSWORD')  {
            if ($this->trace) $this->errm('T',823);
            $this->scramble_init(trim($val));
	 }
         continue;
      }
      $new[$fname]      = $val;
      $this->fldnames[] = $fname;
   }
   if (isset($this->maxseq) && isset($this->minseq) && $this->maxseq < $this->minseq)
      $this->errm('E',550,$this->maxseq);
   $this->recdef          = $new;                /// replace with stripped, parsed def
   if (!isset($this->tblname)) $this->tblname = 0;
   $this->uc_recdef       = $this->cnv2uc($this->recdef);
   $this->recflds         = $this->cnv2uc($this->fldnames);
   $this->recflds = array_flip($this->recflds);  /// Inversed field list
   if (!is_null($pk = $this->getPk())) {         /// Find Pk
      $this->keyfld       = $pk[1];
      $atts               = $this->getAtts($pk[0]);
      if (isset($atts['PK']) && strcmp($atts['PK'],'SEQUENCE')==0) $this->seqkey = TRUE;
   }
   if ($this->trace) $this->errm('T',824,$this->keyfld);
   if ($this->trace && isset($this->maxseq)) $this->errm('T',865,$this->maxseq);
   if ($this->trace && isset($this->minseq)) $this->errm('T',866,$this->minseq);
   if ($this->trace && $this->hasRecDef()) {
      $str = NULL; foreach ($this->fldnames as $k=>$f) $str .= "$k=$f "; $this->errm('T',826,$str); }
   return TRUE;
   }

   function validateRec($vrec, $operation='INSERT', $currec=NULL) {
   /// Expects a record with "numeric keys", aka 0=>'value, ....
      if ($this->trace) $this->errm('T',830);
      if (!$this->hasRecDef() && !$this->parseRecDef() ) return FALSE;
      if (count($this->recdef) <> count($vrec)) {
         $this->errm('E',240,count($this->recdef),count($vrec));
	 return FALSE;
      }
      $i = 0;
      foreach ($this->uc_recdef as $fname => $val) {
         $type  = $this->type($fname);
         $fldsz = $this->size($fname);
         $atts  = $this->getAtts($fname);
         if (!is_array($val)) continue;   /// Skipp table prop Table_name, file_name, etc
         if (isset($atts['UPPER'])) $vrec[$i] = strtoupper($vrec[$i]);  /// To UpperCase
         if (isset($atts['LOWER'])) $vrec[$i] = strtolower($vrec[$i]);  /// To LowerCase
         if (isset($atts['DEFAULT']) && !$this->hasVarValue($vrec[$i]))
	    $vrec[$i] = $atts['DEFAULT']; /// Set the default value
	 if ($this->NN($fname) && !$this->hasVarValue($vrec[$i])) {
	    $this->errm('E',270,$fname); return FALSE;
         }
	 if ($this->PK($fname)) {         /// Primary key tests
	    if (!$this->hasVarValue($vrec[$i])) { $this->errm('E',242,$fname); return FALSE; }
	    if ( $operation=='INSERT' ) {
	      if (isset($this->pkkeys[$vrec[$i]])) {
	         $j = $this->pkkeys[$vrec[$i]];
	         if ($this->recmap[$j]<>'D') { $this->errm('E',244,$fname,$vrec[$i]); return FALSE; }
	      }
	      $this->pkkeys[$vrec[$i]] = $currec;  /// Pk, add to pk-vals
	    } else { /// $operation=='UPDATE') Is there is another key value ??
	      if ($vrec[$i]<>$this->recs[$currec][$i]) { $this->errm('E',246,$fname); return FALSE; }
	      if (isset($this->pkkeys[$vrec[$i]])) {
	         $j = $this->pkkeys[$vrec[$i]];
		 if (!$j==$currec || !$this->recmap[$j]=='D') {
                    $this->errm('E',248,$fname,$vrec[$i]); return FALSE; }
	      }
           }
	 }
	 if ($this->UK($fname)) {                /// Unique key tests
	    if (!$this->hasVarValue($vrec[$i])) { $this->errm('E',250,$fname); return FALSE; }
	    if ( $operation=='INSERT' ) {
	       if (isset($this->ukkeys[$fname][$vrec[$i]])) {
	          $j = $this->ukkeys[$fname][$vrec[$i]];
	          if ($this->recmap[$j]<>'D') { $this->errm('E',252,$fname,$vrec[$i]); return FALSE; }
	       }
	       $this->ukkeys[$fname][$vrec[$i]] = $currec;    /// Uk, add to uk-vals
	    } else { /// $operation=='UPDATE')  Is there is allready a key value?
	       if (isset($this->ukkeys[$fname][$vrec[$i]])) {
	          $j = $this->ukkeys[$fname][$vrec[$i]];
		  if (!$j==$currec || !$this->recmap[$j]=='D') {
                     $this->errm('E',254,$fname,$vrec[$i]); return FALSE; }
		  }
	          $this->ukkeys[$fname][$vrec[$i]] = $currec;
	    }
        }
	if ($this->hasVarValue($vrec[$i]) ) {               /// Anything to test ?
	   if ($type=='NUMBER') {
	      if (!is_numeric($vrec[$i])) { $this->errm('E',260,$fname,$vrec[$i]); return FALSE; }
	   } elseif ($type=='INT') {
              $s = trim($vrec[$i],'0123456789');
	      if (!empty($s) || floor($vrec[$i])<>$vrec[$i]) {
                 $this->errm('E',262,$fname,$vrec[$i]); return FALSE;
              }
	   } elseif ($type=='DATE') {
	      if (!$this->isDate($vrec[$i])) {
	         $newdt = $this->str2date($vrec[$i]);
	         if (is_null($newdt))  { $this->errm('E',264,$fname,$vrec[$i]); return FALSE; }
	         $vrec[$i] = $newdt;
	      }
	   } elseif ($type=='DATETIME') {
	      if (!$this->isDateTime($vrec[$i])) {
	         $newdt = $this->str2datetime($vrec[$i]);
	         if (is_null($newdt))  { $this->errm('E',265,$fname,$vrec[$i]); return FALSE; }
	         $vrec[$i] = $newdt;
              }
	   } elseif ($type=='EMAIL') {                      /// simple pattern check
	      $m = $vrec[$i];  // min hide@address.com
	      if (strlen($m)<7 || strlen(strstr($m,'@'))<6 || strlen(strstr($m,'.'))<3) {
		 $this->errm('E',266,$fname,$m); return FALSE; }
	   } elseif ($type=='BINARY') {                     /// Base 64 encoded
	         $vrec[$i] = chunk_split(base64_encode($vrec[$i]));
           }
	   /// Other tests
	    if (!is_null($fldsz) && $type<>'DATE' && $type<>'DATETIME')
               if (strlen($vrec[$i])>$fldsz) { $this->errm('E',272,$fname,$fldsz); return FALSE; }
            if (isset($atts['CHKMIN']) && $vrec[$i]<$atts['CHKMIN']) {
	       $this->errm('E',274,$fname,$atts['CHKMIN']); return FALSE;
            }
            if (isset($atts['CHKMAX']) && $vrec[$i]>$atts['CHKMAX']) {
	       $this->errm('E',276,$fname,$atts['CHKMAX']); return FALSE;
            }
            if (isset($atts['CHKMOD10']) && !chkMod10($atts['CHKMOD10'])) {
               $this->errm('E',280,$fname); return FALSE;
            }
            if (isset($atts['CHKFMT']) && sprintf($vrec[$i],$atts['CHKFMT'])<>$vrec[$i]) {
               $this->errm('E',282,$fname); return FALSE; /// Match num/string to printf format mask
            }
            if (isset($atts['CHKMINLEN']) && strlen($vrec[$i])<$atts['CHKMINLEN']) {
	        $this->errm('E',284,$fname,$atts['CHKMINLEN']); return FALSE;
            }
            if (isset($atts['CHKLIST'])) {
	       $found = FALSE;
	       if (is_array($atts['CHKLIST'])) {
                  while ( list ($subattribute, $subattval) = each ($atts['CHKLIST'])) {
		     if ($vrec[$i]==$subattval) { $found = TRUE; break; }
	          }
	          if (!$found) {
                      $this->errm('E',286,$fname,$vrec[$i], implode(', ', $atts['CHKLIST']));
                      return FALSE;
                 }
	       }
	    }
           /* 'CHKDATEFMT': /// $this->errm('E',288,$fname,$atts['CHKDATEFMT']); break; */
         }
         $i++;
      } // foreach
      if ($this->trace) $this->errm('T',831);
      return TRUE;
   } /// validateRec
   /// ////////////////////////////////////
   /// U T I L I T Y  F U N C T I O N S ///
   /// ////////////////////////////////////
   function formatRec ($recno, $namedIdx=TRUE) {
   /// Format and Convert record to output format with or without fldnames
      if (!$this->hasRecDef()) return $this->recs[$recno];
      if ($this->trace) $this->errm('T',832,$recno,NULL,NULL,NULL,2);
      $rec     = array ();
      reset ($this->recflds);
      while (list ($fn, $pos ) = each ($this->recflds)) {
         if (isset($this->recs[$recno][$pos])) $value = $this->recs[$recno][$pos];
         else $value = NULL;
         if ($namedIdx) $ix = $this->tblname.'.'.$fn;
         else           $ix = $pos;
         $type = $this->type($fn); 
         if     ($type=='DATE')     $rec[$ix] = $this->date2str($value);
         elseif ($type=='DATETIME') $rec[$ix] = $this->datetime2str($value);
         elseif ($type=='BINARY')   $rec[$ix] = base64_decode($value);
         else                       $rec[$ix] = $value;
      }
      // if ($this->trace) $this->errm('T',834);
      return $rec;
   }
   function hasRecDef () {
      if (is_null($this->uc_recdef)) return FALSE;
      return TRUE;
   }
   function isAttSet ($fname, $att) {      /// Case sensitive
      if (!is_null($a = $this->getAtts($fname)))  
         if (isset($a[$att])) return TRUE;
      return FALSE;
   }
   function getAtts ($fname) {
      if (!$this->hasRecDef()) return NULL;
      $fname  = $this->cnv2uc($fname); // case ??
      if (isset($this->uc_recdef[$fname])) return  $this->uc_recdef[$fname];
      return NULL;
   }
   function fldno2fldname ($fno) {
      if (!isset($this->fldnames[$fno])) { $this->errm('E',201, $fno); return NULL; }
      $fn = $this->fldnames[$fno];
      if (!is_null($fn)) return strtoupper($fn);
      return NULL;
   }
   function fldname2fldno ($fname) {
      if (!$this->hasRecDef()) return NULL;
      if (is_string($fname)) $fname = strtoupper($fname);
      else return NULL;
      if (isset($this->recflds[$fname])) return $this->recflds[$fname];
      $this->errm('E',202, $fname);
      return NULL;
   }
   function getPk () {
      if (!$this->hasRecDef()) return NULL;
      $i = 0;
      foreach ($this->uc_recdef  as $fname => $val) {
	 if (isset($val['PK'])) return array ($fname, $i);
         $i++;
      }
      return NULL;
   }
   function NN ($fname) {
      return ($this->isAttSet($fname,'CHKNN') || $this->isAttSet($fname,'PK'));
   }
   function PK ($fname) {
      return $this->isAttSet($fname,'PK');
   }
   function UK ($fname) {
      return $this->isAttSet($fname,'UK');
   }
   function FK ($fname) {   /// returns array with table name, field name or FALSE
      if (!is_null($a = $this->getAtts($fname)))
         if (isset($a['FK'])) return array ($a['FK'][0],$a['FK'][1]);
      return NULL;
   }
   function size ($fname) {
      if (!is_null($a = $this->getAtts($fname)))
         if (isset($a['SIZE'])) return $a['SIZE'];
      return NULL;
   }
   function type ($fname) {
      if (!is_null($a = $this->getAtts($fname)))
         if (isset($a['TYPE'])) return $a['TYPE'];
      return 'STRING';
   }
   function usage ($fname) {
      if (!is_null($a = $this->getAtts($fname)))
         if (isset($a['USAGE'])) return $a['USAGE'];
      return NULL;
   }
   function reGenKeys () {  /// Add Pk+UK
      if ($this->trace) $this->errm('T',836);
      $this->pkkeys = NULL;
      $this->ukkeys = NULL;
      if (!$this->hasRecDef()) return NULL;
      $j = 0;
      foreach ($this->uc_recdef as $fld_name => $value) {
	 if (isset($value['PK']))
	    for ($i=0; $i<$this->rowcnt; $i++)
	       if ($this->recmap[$i]<>'D') $this->pkkeys[$this->recs[$i][$j]]  = $i;
         if (isset($value['UK']))
	    for ($i=0; $i<$this->rowcnt; $i++)
	       if ($this->recmap[$i]<>'D') $this->ukkeys[$fld_name][$this->recs[$i][$j]] = $i;
	 $j++;
      }
      if ($this->trace) $this->errm('T',837);
   }
   function get_ctldata ($str) {
      if ($this->trace) $this->errm('T',863);
      $rec = $this->line2rec($str);
      foreach ( $rec as $pos => $val) {
         if (strstr($val,'minseq=')) $this->minseq = substr($val,7);
         if (strstr($val,'maxseq=')) $this->maxseq = substr($val,7);
         if (strstr($val,'maxkey=')) $this->maxkey = substr($val,7);
         if (strpos($val,'crypt='))  $this->pwkey  = substr($val,6);
      }
      if ($this->trace && isset($this->maxkey)) $this->errm('T',864,$this->maxkey);
      if ($this->trace && isset($this->maxseq)) $this->errm('T',865,$this->maxseq);
      if ($this->trace && isset($this->minseq)) $this->errm('T',866,$this->minseq);
      if ($this->trace) $this->errm('T',867);
      if (isset($this->pwkey)) {
         $s = $this->scrambleData($this->pw);
         if ($s<>$this->pwkey) return FALSE;
      }
      $this->ctlcnt++;
   return TRUE;
   }
   function set_ctldata () {
      if ($this->trace) $this->errm('T',870);
      $str = NULL;
      $d   = NULL;
      if (isset($this->minseq)) $str .= 'minseq='.$this->minseq;
      if (!is_null($str)) $d = ';';
      if (isset($this->maxseq)) $str .= $d.'maxseq='.$this->maxseq;
      if (!is_null($str)) $d = ';';
      if (isset($this->maxkey)) $str .= $d.'maxkey='.$this->maxkey;
      if ($this->trace && isset($this->maxkey)) $this->errm('T',864,$this->maxkey);
      if ($this->trace && isset($this->maxseq)) $this->errm('T',865,$this->maxseq);
      if ($this->trace && isset($this->minseq)) $this->errm('T',866,$this->minseq);

      if (!is_null($str)) $delim = ';';
      if ($this->is_encrypted)   $str .= $d.'crypt='. $this->scrambleData($this->pw);
      if (!is_null($str)) $d = ';';
      $str .= $d.'LastWrite='.date('Y-m-d H:i:s');
      $str  = '<'.$this->tag.' CONTROL DATA>'.$str.'</'.$this->tag.'>';
      if ($this->trace) $this->errm('T',872);
   return $str;
   }
   function stripNamedKeys($rec) {
   /// Makes a record with "named keys" to numeric keys only
   /// First check if numeric keys only, if so .. return it
      if ($this->trace) $this->errm('T',868);
      if (!($this->hasRecDef() && is_array($rec))) return $rec;
      $numericKeys = true;
      foreach ($rec as $key => $value) {
         if (is_numeric($key)) continue;
         $numericKeys = false;
         break;
      }
      if ($numericKeys) return $rec;
      $newrec = array ();
      /// Rearanges a named record into fld order as well
      if ($this->trace) $this->errm('T',869);
      reset($this->recflds);
      $i      = 0;
      while ( list($fname, $pos) = each($this->recflds)) {
         $nk = $this->tblname.'.'.$fname;
         if (isset($rec[$nk])) {
            $newrec[$i++] = $rec[$nk];
         } else {
            $newrec[$i++] = NULL;
            if ($this->trace) $this->errm('T',857, $nk, $i);
         }
         unset($rec[$nk]);
      }
      if (isset($rec[$nk])) unset($rec[$nk]);
      if (count($rec)>0) { /// Record contains illegal field names
         $this->errm('E',230, implode (', ',array_keys($rec)));
         return FALSE;
      }
      return $newrec;
   }
   /// /////////////////////////////////////////////////
   ///        B A S E   F U N C T I O N S            ///
   /// Does not require a record definition to work  ///
   /// /////////////////////////////////////////////////
   function getRec ($keyvalue, $searchfld=NULL) {
   /// Returns first record found with searched key in keyfield or NULL
      if ($this->trace) $this->errm('T',838,$keyvalue);
      $ptr = NULL;
      if (is_null($searchfld)) $fno = $this->keyfld;
      else                     $fno = $searchfld;
      if ($this->hasRecDef())        /// Is there a pk or uk to search on?
	 $ptr = $this->getRnByKey ($keyvalue, $fno);
      if (!is_null($ptr))      return $this->formatRec($ptr,FALSE);
      else {
         $recset = $this->scanRecs($keyvalue, $fno);
         if (isset($recset))   return $this->formatRec($recset[0],FALSE);
      }
      return NULL;
   } /// getRec

   function scanRecs ($value, $searchfld=NULL, $recset=NULL, $op=NULL) {
   /// Returns set of pointers to records found with searched key in keyfield, or NULL
   /// Searches THIS SET ONLY
      if ($this->trace) {
         if (empty($value)) $v = ' (NULL) ';
         else               $v = $value;
         $this->errm('T',848,$v,$searchfld,$op);
      }
      if (!$this->is_read)     $this->read();
      if (is_null($searchfld)) $fno = $this->keyfld;
      else                     $fno = $searchfld;
      $op = $this->chkOp($op);
      if (is_null($op))                           return NULL;
      if (is_string($value))   $value = trim($value);
      if ($this->trace && empty($value)) $this->errm('T',849);
      if ($this->hasRecDef()) {
         $type = $this->type($this->fldno2fldname($fno));
         if ($type=='DATE'     && !$value=$this->str2date($value))      return NULL; /// Inv date
         if ($type=='DATETIME' && !$value=$this->str2datetime($value))  return NULL; /// Inv date
         if ($type=='BINARY')  { $this->errm('E',308,$fno);             return NULL; }
      }
      if (is_null($recset))   $recset = $this->selectAll();
      $rec_count = count($recset);
      if (!is_array($recset) || $rec_count==0) return NULL;
      $selected   = NULL;
      for ($i=0; $i<$rec_count; $i++ ) {
         $ptr = $recset[$i];
	 if (!isset($this->recs[$ptr][$fno])) { $this->errm('W',310,$ptr); return NULL; }
         $fldtype = gettype($this->recs[$ptr][$fno]);
         settype($value,$fldtype);
	 if ($op=='EQUAL' && $this->recs[$ptr][$fno]==$value)
            { $selected[] = $ptr; continue; }
	 if ($op=='LIKE' && substr($this->recs[$ptr][$fno],0,strlen($value))==$value) 
	    { $selected[] = $ptr; continue; }
	 if ($op=='CMP' && strcasecmp($this->recs[$ptr][$fno],$value)==0) // Str compare ignores case.
	    { $selected[] = $ptr; continue; }
	 if ($op=='LCMP') { // Like ignore case.
            $s = substr($this->recs[$ptr][$fno],0,strlen($value));
            if (strcasecmp($s,$value)==0) $selected[] = $ptr;
            continue;
         }
	 if ($op=='LT' && $this->recs[$ptr][$fno]<$value)
	    { $selected[] = $ptr; continue; }
	 if ($op=='GT' && $this->recs[$ptr][$fno]>$value)
	    { $selected[] = $ptr; continue; }
	 if ($op=='LE' && $this->recs[$ptr][$fno]<=$value)
	    { $selected[] = $ptr; continue; }
	 if ($op=='GE' && $this->recs[$ptr][$fno]>=$value)
	    { $selected[] = $ptr; continue; }
	 if ($op=='NE' && $this->recs[$ptr][$fno]<>$value)
	    { $selected[] = $ptr; continue; }
      }
      if ($this->trace) $this->errm('T',850,count($selected),count($recset));
    return $selected;
   } /// scanRecs

   function delete ($keyvalue, $searchfld=NULL) {
   /// Deletes all records with matching keyvalue in keyfield, returns FALSE or TRUE
      if ($this->trace) $this->errm('T',840,$keyvalue,$searchfld);
      $r = $this->scanRecs($keyvalue, $searchfld);
      if (is_null($r)) { $this->errm('W',300); return FALSE; }
      $j = count($r);
      if ($this->trace) $this->errm('T',842,$j);
      for ($i=0; $i<$j; $i++)  if (!$this->delByRn($r[$i])) return FALSE;
      return TRUE;
   }
   function update ($rec, $keyvalue, $searchfld=NULL) {
   /// Updates all records with matching key, returns FALSE or TRUE
      if ($this->trace) $this->errm('T',844,$keyvalue,$searchfld);
      if ($this->hasRecDef())  $rec = $this->stripNamedKeys($rec);    /// Convert rec
      if (is_null($rec)) { $this->errm('W',301); return FALSE; }
      $r = $this->scanRecs($keyvalue, $searchfld);
      if (is_null($r))   { $this->errm('W',302); return FALSE; }
      $j = count($r);
      if ($this->trace) $this->errm('T',846,$j);
      for ($i=0; $i<$j; $i++)  if (!$this->updByRn($r[$i], $rec)) return FALSE;
      return TRUE;
   }
   function insert ($newrec) {
   /// New record "newrec" is an array of all fields.
     $this->lastInsRn = NULL;
     if ($this->trace) $this->errm('T',852);
     if (!is_array($newrec)) { $this->errm('E',320); return FALSE; }
     if (!$this->is_read)     $this->read();
     if (!$this->open('a'))  { $this->errm('E',321); return FALSE; }  /// Open and lock
     if ($this->hasRecDef()) {
        $newrec = $this->stripNamedKeys($newrec);            /// Check and convert rec
        if (is_null($newrec)) { $this->errm('W',301); return FALSE; }
	if ($this->seqkey) {
	   if (isset($this->maxkey)) ++$this->maxkey;        /// Increase Sequence key
	   if (isset($this->maxkey) && isset($this->maxseq)) /// Check Min and max sequence
	     if ($this->maxkey>=$this->maxseq) {
	        if (isset($this->minseq)) $this->maxkey = $this->minseq;
	        else                      $this->maxkey = 0;
	     }
	   $newrec[$this->keyfld] = $this->maxkey;
	}
	if(!$this->validateRec(&$newrec, 'INSERT', $this->rowcnt))  return FALSE;
     } else {                                                /// No rec def,,,,
        $kfld = gettype($newrec[$this->keyfld]);             /// If keyfld = num assume key
        if (!($kfld=='integer'||$kfld=='double'||$kfld=='float')) /// Non Numeric, add NUM key
	  array_unshift($newrec,++$this->maxkey);            /// push key onto record as fld 0
     }
     $this->recs[$this->rowcnt]   = $newrec;
     $this->lastInsRn             = $this->rowcnt;
     $this->recmap[$this->rowcnt] = 'I';
     $this->rowcnt++;
     if (!isset($this->operation))   $this->operation='INSERT';
     return TRUE;
   } /// insert

   function updByRn ($rowno, $updrec) {
   /// Updates the record in the rec-array by row number ( array index )
     if ($this->trace)    $this->errm('T',858,$rowno);
     if (!$this->is_read) $this->read();
     if (!is_array($updrec))         { $this->errm('E',335);        return FALSE; }
     if ($this->recmap[$rowno]=='D') { $this->errm('E',340,$rowno); return FALSE; }
     if (!$this->recs[$rowno])       { $this->errm('E',345,$rowno); return FALSE; }
     if (is_null($updrec))           { $this->errm('W',301);        return FALSE; }
     if ($this->hasRecDef()) $updrec = $this->stripNamedKeys($updrec);   /// Convert rec
     if (!$this->open('a'))      { $this->errm('W',322); return FALSE; } /// Open&lock
     $updrec[$this->keyfld] = $this->recs[$rowno][$this->keyfld];        /// Conserve pk
     if ($this->hasRecDef() && !$this->validateRec(&$updrec, 'UPDATE', $rowno)) return FALSE;
     $this->recs[$rowno]    = $updrec;
     if ($this->trace) $this->errm('T',860,$rowno);
     if ($this->recmap[$rowno]=='O') $this->recmap[$rowno] = 'U';
     $this->operation = 'MIXED'; /// does not handle update of newly inserted rec
     return TRUE;
   } /// updByRn

   function delByRn ($rowno) {
   /// Marks the record for Delete in the rec-array by row number ( array index )
     if ($this->trace) $this->errm('T',853,$rowno);
     if (!$this->is_read)     $this->read();
     if (!$this->open('a'))   { $this->errm('W',323); return FALSE; }  /// Open and lock
     if (!$this->hasVarValue(($this->recs[$rowno]))) { $this->errm('E',330,$rowno); return FALSE; }
     if ($this->trace) $this->errm('T',854,$rowno);
     $this->recmap[$rowno] = 'D';
     $this->operation = 'MIXED'; /// does not handle delete of newly inserted rec
     return TRUE;
    } /// delByRn

   function getRecByRn ($rowno, $namedIdx=TRUE) {
   /// Returns record by Row number or NULL
     if ($this->trace) $this->errm('T',856,$rowno);
     if (!$this->is_read)     $this->read();
     if (!isset($this->recs[$rowno])) { $this->errm('E',338,$rowno); return NULL; }
     if ($this->recmap[$rowno]=='D')  { $this->errm('E',337,$rowno); return NULL; }
     if ($namedIdx && $this->hasRecDef()) $rec = $this->formatRec ($rowno, $namedIdx);
     else $rec = $this->recs[$rowno];
     return $rec;
   } /// getRecByRn

   function commit () {
      return $this->write();
   }
   function rollback () {
      if ($this->fp) $this->close();
      $this->reset_recs();
      return $this->read();
   }
   ///////////////////////
   /// File functions ///
   /////////////////////
   function reRead () {
      if ($this->trace) $this->errm('T',862);
      if (!is_null($this->operation)) { $this->errm('E',350);  return FALSE; }
      $this->reset_recs();
      return $this->read();
   }
   function reset_recs () {
      $this->is_read   = FALSE;
      $this->recs      = NULL;
      $this->comments  = NULL;
      $this->recmap    = NULL;
      $this->selrecs   = NULL;
      $this->selptr    = NULL;
      $this->pkkeys    = NULL;
      $this->ukkeys    = NULL;
      $this->operation = NULL;
      $this->filetime  = NULL;
    }
   function mk_newrec ($str, $i ) {
   /// Private, used in read only
      if ($this->is_encrypted) $str = $this->descrambleData($str);
      $r              = $this->line2rec($str);
      if ($r[$this->keyfld]>$this->maxkey)  $this->maxkey = $r[$this->keyfld];
      $this->recs[$i] = $r;
      $this->recmap[$i] ='O';
      if (is_null($this->trace_level) && $this->trace) $this->errm('T',881,$i);
      return;
   }
   function read () {
      if ($this->trace) $this->errm('T',873);
      if ($this->is_read) return TRUE;
      if (!$this->hasRecDef()) $this->parseRecDef();
      $this->reset_recs();
      if (file_exists($this->file)) {
         if (!$this->open('r')) return FALSE;
      } else {
         if ($this->trace) $this->errm('T',874);
         $this->is_read  = TRUE;
         return TRUE;
      }
      $stag           = '<'.$this->tag;                   /// Open tag test close later
      $etag           = '</'.$this->tag.'>';
      $taglen         = strlen($stag);
      $str            = NULL;
      $i              = 0;
      if ($this->trace) {
         $this->errm('T',875);
         if ($this->is_encrypted) $this->errm('T',876);
      }
      while (!feof($this->fp)) {
         $line = fgets($this->fp, $this->maxline);
         if (substr($line,0,1)=='#') {                   /// Commentlines must start with #
	    $this->comments[] = strstr($line,'#');
	    continue;
	 }
         $op       = NULL;
         $startpos = strpos($line,$stag);
         $endpos   = strpos($line,$etag);
	 if (!($startpos === false )) {                  /// Beginning of StartTag
            //if (is_null($this->trace_level) && $this->trace)  $this->errm('T',877,$i);
	    $clpos    = strpos($line,'>');               /// End of Starttag, close pos
	    if ($clpos==$taglen+13) {                    /// <R I=1234567890>fld1... Type 2
	       $startpos += $taglen + 14;
               $op        = substr($line,$taglen+1,1);
               if ($op=='I'||$op=='U'||$op=='D'||$op=='O') {
	          $this->tsrecs[$i] = substr($line,$taglen+1,12); /// Timestamp for each rec
                  if (!$this->rectype) $this->rectype = 2;
               } else if ($op!='C') $this->errm('E',401,$op);
	    } elseif ($clpos==$taglen) {                 /// <R>fld1... Type 1
	       $startpos += $taglen+1;
               if (!$this->rectype) $this->rectype = 1;
            } else $this->errm('E',213,substr($line,$startpos,$clpos));
            $line   = substr($line,$startpos);
         }
         if (!($endpos === false)) {
            //if (is_null($this->trace_level) && $this->trace) $this->errm('T',878,$i);
	    $str   .= substr($line,0, $endpos-$startpos);
            if ($op=='C') {
               if (!$this->get_ctldata($str)) { $this->errm('E',405); $this->reset_recs(); break; }
               $str    = NULL;
               continue;
            }
            $this->mk_newrec($str, $i);
            $str    = NULL;
            $i++;
         } else if (trim($line)!='') $str .= $line;
      } //while
      if ($this->mode=='r') $this->close();          /// this is only a read op
      $this->rowcnt      = count($this->recs);
      if ($this->hasRecDef()) $this->reGenKeys();    /// Create uk & pk array
      $this->is_read  = TRUE;
      if ($this->ctlcnt > 100) $operation = 'MIXED'; /// Rewrite file if many ctl-recs
      if ($this->trace) $this->errm('T',882,count($this->rowcnt),$this->maxkey);
      return TRUE;
   } /// read

   /// Functions below is not made private even if they could be
   function write () {
   /// Checks writemode and does a write of all records or appendwrite to file if only inserts
      if ($this->trace) $this->errm('T',883);
      if (!isset($this->operation)) {                              /// No Changes made to records
         if ($this->trace) $this->errm('T',884);
         return TRUE;
      }
      if (!$this->is_read) { $this->errm('E',410); return FALSE; } /// File Not read, do not write!
      if ($this->is_encrypted) {
         if ($this->trace) $this->errm('T',885);
         if (!isset($this->pw)) { if ($this->trace) $this->errm('T',886); return FALSE; }
         if (isset($this->pwkey) && $this->pwkey<>$this->scrambleData($this->pw)) {
            $this->errm('E',420);
            return FALSE;
         }
      }
      if (!$this->rectype) $this->rectype = 2;
      $stag             = '<'.$this->tag.'>';
      $etag             = '</'.$this->tag.'>';
      $newrecs          = array ();
      $ts               = time();
      if ($this->changelog) $logfile = $this->file.'_log';             /// Set LogFile Name
      if (!is_null($this->logswitch)) {
         $logfmt = array ('y'=>'y', 'm'=>'ym', 'd'=>'ymd', 'h'=>'ymdH', 'w'=>'ymW');
         $lt     = strtolower(substr(trim($this->logswitch),0));       /// ... if set, can be ymdhw
         if (isset($logfmt[$lt])) $logfile = $this->file.'_log_'.date($logfmt[$lt],$ts);
         else $this->errm('W',502,$lt);
      }
      if (!$this->open('a')) return FALSE;                             /// Open and lock
      $comment_cnt      = count($this->comments);
      if ($this->trace) $this->errm('T',888,$comment_cnt);
      if ($this->changelog && !($lfp = fopen($logfile, 'a'))) {
         $this->errm('E',501);
         $this->changelog = FALSE;
      }
      if ($this->operation=='INSERT') {                           /// Only Append new record(s)
	 $only_new_recs = TRUE;                                   /// Recmap maintained at I/U/D ops
	 for ($i=0; $i<count($this->recmap); $i++) {              /// Check if only Insert records
	    if ($this->recmap[$i++]=='I') { next($this->recmap); continue; }
	    $only_new_recs = FALSE;
	    break;                                                /// Old record found
         }
         if ($this->trace) $this->errm('T',892);
	 if ($only_new_recs && $comment_cnt==0)                   /// New file, Insert header info
	    $this->writeLine($this->fp, "# Created: ".date('Y-m-d H:i:s')."\n");
      } else {                                                    /// Rewrite all record(s)
         if ($this->trace) $this->errm('T',893);
         fseek($this->fp, 0);                                     /// Reset filepointer
         ftruncate($this->fp, 0);                                 /// Truncate file
	 for ($i=0; $i<$comment_cnt; $i++)                        /// Write Comment lines
            $this->writeLine($this->fp, $this->comments[$i]);
      }
      if ($this->seqkey)
         $this->writeLine($this->fp, "\n".$this->set_ctldata());   /// Write Control record
      $j = 0;
      $new_rec_cnt = count($this->recmap);
      if ($this->trace) $this->errm('T',894,$new_rec_cnt);
      $res = FALSE;
      for ($i=0; $i<$new_rec_cnt; $i++) {                         /// Write record(s)
	 $op = $this->recmap[$i];
	 if ($op<>'D') $newrecs[] = $this->recs[$i];              /// New array, Skip deleted recs
	 if ($this->operation=='INSERT' && $op<>'I') continue;    /// Only Append new recs
	 $line   = $this->rec2line($this->recs[$i]);
         if ($this->is_encrypted) $line = $this->scrambleData($line);
         if ($line) {
            if ($this->changelog && $op<>'O') {
               if (!$this->writeLine($lfp, "\n".'<'.$this->tag.' '.$op.'='.$ts.'>'.$line.$etag))
                  $this->errm('E',517);
	    }
	    if ($op<>'D') {                                       /// Skip deleted records
	       $j++;
	       if ($this->rectype==2) {
	          if ($op=='O' && isset($this->tsrecs[$i]))
		     $oline = "\n<".$this->tag.' '.$this->tsrecs[$i].'>'.$line.$etag;
	          else $oline = "\n<".$this->tag.' '.$op.'='.$ts.'>'.$line.$etag;
	       } else {
	          $oline = "\n".$stag.$line.$etag;
	       }
               if (!$this->writeLine($this->fp,$oline)) break;
            }
	 }
         $res = TRUE;
      } // write loop end
      if ($this->trace) $this->errm('T',896,$j);
      if ($this->changelog) {
         fflush($lfp);
         if (!fclose($lfp)) $this->errm('E',531,$this->changelog);
      }
      $this->close();
      if (!$res) { $this->errm('E',516); return FALSE; }
      $this->operation = NULL;
      $this->reRead();
      return TRUE;
   } /// write

   function writeLine ($fp, $line) {
      if (!fputs($fp,$line)) { $this->errm('E',515); return FALSE; }
      return TRUE;
   }

   function lock ($lockmode) {
      if ($this->trace) $this->errm('T',900,$lockmode);
      $mlw       = $this->maxlockwait * 1000;
      $lockmode  = strtolower($lockmode);
      for ($i=100; $i<$mlw; $i +=200) {      
      /// LOCK_NB is used with "flock" to return immediately instead of waiting for the lock
         switch ($lockmode) {
	   case 'r':  $lck = LOCK_SH | LOCK_NB; break;
	   case 'a':
	   case 'w':  $lck = LOCK_EX | LOCK_NB; break;
           case 'u':  $lck = LOCK_UN;           break;
           default:   $this->errm('E',510,$lockmode); return FALSE; break; 
         }
	 if (flock($this->fp,$lck)) {
            if ($lockmode =='u') $this->is_locked = FALSE;
	    else                 $this->is_locked = TRUE;
            return TRUE;
         }
	 usleep($i);    /// linux/unix ok, windows ? sleep(1);
         if($this->trace) $this->errm('T',902,$i);
      }
      $this->errm('E',511);
      return FALSE;
   } /// lock
   function open ($mode) {
      if ($this->trace) $this->errm('T',904,$mode);
      if (is_null($this->file)) { $this->errm('E',520); return FALSE; }
      $mode = strtolower($mode);
      $m1   = substr($mode,0,1);
      $m2   = substr($mode,1,1);
      if ($m1=='w') $m1 = 'a';                                      /// Write  -> Allways open append 
      if (($m1=='r'||$m1=='a') && ($m2==NULL||$m2=='+'||$m2=='b')) $mode = $m1.$m2;
      else                           { $this->errm('E',524,$mode); return FALSE; }
      if ($this->is_open && $this->mode==$m1)    return TRUE;       /// Allready Open in Req mode

      $fd = dirname($this->file);
      if (!file_exists($fd))                               { $this->errm('E',521,$fd); return FALSE; }
      if (file_exists($this->file) && is_dir($this->file)) { $this->errm('E',522,$fd); return FALSE; }

      if (file_exists($this->file))  {
         if (!is_readable($this->file))             { $this->errm('E',525); return FALSE; }
         if ($m1=='a' && !is_writable($this->file)) { $this->errm('E',527); return FALSE;} 
         $cf = $this->getFileTime();
         if (is_null($this->filetime)) $this->filetime = $cf;       /// New read, set filetime
         if ($this->filetime<>$cf) {                                /// Check if modified since last read
            if ($this->trace) $this->errm('T',889,$this->filetime, $this->getFileTime());
            $this->reset_recs();
         }
      } else {
         if ($m1=='r') { $this->errm('E',526); return FALSE; }      /// File does not exist for read 
      }
      if ($this->fp = fopen($this->file, $mode)) {
         if ($this->lock($m1)) {
            $this->is_open = TRUE;
            $this->mode    = $m1;
            if (isset($cf)) $this->filetime = $cf;                  /// Save last file mod time
            else $this->filetime = $this->getFileTime();
            return TRUE; 
	 } else {
	    $this->close();
	    $this->errm('E',528,$mode);
         }
      }
      $this->errm('E',523,$mode);
      return FALSE; 
   } /// open
   function close () {
      if ($this->trace) $this->errm('T',906);
      if ($this->is_locked)    $this->lock('u');
      fflush($this->fp);
      if ($this->is_open && !fclose($this->fp))  $this->errm('E',530);
      $this->is_open  = FALSE;  /// Do this in any case
      $this->mode     = NULL;
      $this->fp       = FALSE;
      return TRUE;
   }
   function getFileTime() {
      clearstatcache();
      if ($ts = filemtime($this->file)) return $ts; /// File modified since last read
      else return NULL;
   }
   ///////////////////////
   /// Date Utilities ///
   /////////////////////
   function date2str($dt) {
      if (!$this->hasVarValue($dt))  return NULL;
      // Be forgiving and strip time if this is a datetime
      if ($this->isDate(substr($dt,0,8))) { /// Internal Format YYYYMMDD -> mktime (HHiissMMDDYYYY) 
         $ts = mktime(0, 0, 0, substr($dt,4,2), substr($dt,6,2),  substr($dt,0,4));
      } else {
         $this->errm('E',205, $dt);
         return NULL;
      }
      return date($this->datefmt,$ts);
   }
   function datetime2str($dt) {
      if (!$this->hasVarValue($dt))  return NULL;;
      if ($this->isDateTime($dt)) { /// Internal Format YYYYMMDDHHiiss -> mktime (HHiissMMDDYYYY) 
         $ts = mktime(substr($dt,8,2), substr($dt,10,2), substr($dt,12,2),
                      substr($dt,4,2), substr($dt,6,2),  substr($dt,0,4));
      } else {
         $this->errm('E',203, $dt);
         return NULL;
      }
      return date($this->datetimefmt,$ts);
   }
   function isDateTime ($str) { /// Check datestr, must be in format yyyymmddHHMiSS
      if (strlen($str)<>14) return FALSE;
      $hour  = substr($str,8,2);
      $min   = substr($str,10,2);
      $sec   = substr($str,12,2);
      if ($hour<24 && $hour>-1 && $min<60 && $min>-1 && $sec<60 && $sec>-1 &&
          $this->isDate(substr($str,0,8))) return TRUE;
      return FALSE;
   }
   function isDate ($str) {   /// Check datestr, must be in format yyyymmdd
      $year = substr($str,0,4);
      if ($year < 1902 || $year > 2037) return FALSE;
      if (strlen($str)==8 && checkdate(substr($str,4,2), substr($str,6,2), $year)) return TRUE;
      return FALSE;
   }
   function str2datetime($str) {
      $ts = $this->str2ts($str);
      if ($ts==-1 || is_null($ts)) { $this->errm('E',204,$str); return NULL; }
      return date('YmdHis',$ts);
   }
   function str2date($str) {
      $ts = $this->str2ts($str);
      if ($ts==-1 || is_null($ts)) { $this->errm('E',206,$str); return NULL; }
      return date('Ymd',$ts);
   }
   function str2ts($str) {
      if (!$this->hasVarValue($str)) return NULL;    // null => invalid
      $str = strtolower($str);
      if ($str=='now')               return strtotime('now');
      if ($this->isDate($str))     // internal date
         return mktime(0,0,0, substr($str,4,2), substr($str,6,2), substr($str,0,4));
      if ($this->isDateTime($str)) // internal datetime
         return mktime(substr($str,8,2), substr($str,10,2), substr($str,12,2),
                       substr($str,4,2), substr($str,6,2),  substr($str,0,4));
      // ? php5 use $ts = strptime ( $str, $this->datetimefmt )
      // assume: dateparts has a delimiter, and year is a full 4 digit year, 
      // date and time are separated by space, timeparts are separated by colon
      $date = trim($str);
      $time = NULL;
      if (strpos($str,':')) {      // Asuming : as time delimiter
         $t = explode(' ',$str);
         for ($i=0; $i<count($t); $i++) {
            if (empty($t[$i])) continue;
            if (strpos($t[$i],':')) $time = $t[$i];
            else                    $date = $t[$i];
         }
      }
      $s = strtr($date,'+/.,;_ ','-------');
      list($year,$month,$day) = split('-', $s, 3);  // separate date parts
      if (strlen($day)>2 || strlen($year)<3) {
         list($day,$month,$year) = split('-',$s,3); // guess reverse date parts
      }
      if (strlen($year)<3) {
         if ($year<40) $year = '20'.$year;          // max year is 2037
         else          $year = '19'.$year;
      }
      $s = substr($month,0,3);
      if (in_array($s,$this->month3name)) {
         $nummonth  = (int) array_search($s,$this->month3name);
         $monthname = $month;
      } else {
         $nummonth  = (int) $month;
         if ($nummonth>0 && $nummonth<13) $monthname = $this->month3name[$nummonth];
      }
      if (!empty($time)) {
         $t  = explode(':',$time);
         $tp = array (0,0,0);
         for ($i=0; $i<count($t); $i++) { // handle time with two or three fields
            if (empty($t[$i])) continue;
            $tp[$i] = $t[$i];
         }
         list ($hour, $min, $sec) = $tp;
         $s = sprintf('%04d%02d%02d%02d%02d%02d',$year,$nummonth,$day,$hour,$min,$sec);
         if (!$this->isDateTime($s)) return NULL;
         $s = sprintf('%s-%s-%s  %s', $day, $monthname, $year, $time);
         // return mktime($hour, $min, $sec, $nummonth, $day, $year); // does not handle am/pm
         if (($ts = strtotime($s)) !==FALSE) return $ts;
      } else {
         $s = sprintf('%04d%02d%02d',$year,$nummonth,$day);
         if (!$this->isDate($s))     return NULL;
         return mktime(0, 0, 0, $nummonth, $day, $year);
      }
      return NULL;
   }
   //////////////////////////
   /// General Utilities  ///
   //////////////////////////
   function chkOp($op=NULL) {               /// Check search op and convert to upper case
      if (!isset($op)) return 'EQUAL';
      if (is_string($op)) $op = strtoupper($op);
      else return NULL;
      $op  = trim(strtr($op,' ',''));
      if (strpos('EQUAL LIKE LT GT LE GE NE CMP LCMP',$op)===FALSE) {
         $this->errm('E',210,$op);
	 return NULL;
      }
      return $op;
   }
   function hasVarValue($value) {
     if (is_null($value) || ( $value<>'0' && empty($value))) return FALSE;
     return TRUE;
   }

   function rec2line ($newrec) {
      $line      = NULL;
      $fld_count = count($newrec);
      if ($fld_count==0)  return NULL;
      for ( $j=0; $j<$fld_count; $j++ ) {               /// Format the fields in the in-record
         if (isset($newrec[$j]))   $str = trim($newrec[$j]);
	 else                      $str = '';           /// Stored with delimiter replaced by |
	 $str   = strtr($str,$this->delimiter,'|');     /// Convert back all replaced chars
         $line .= $str;
         if ($j<$fld_count-1) $line .= $this->delimiter;
      }
      return $line;
   }

   function line2rec ($line) {
      if (is_null($line))  return NULL;
      if (is_null($this->delimiter)) $arr[] = $line;
      else {
	 $arr  = explode($this->delimiter,$line);
         for ( $j=0; $j<count($arr); $j++ ) {
            $arr[$j] = trim($arr[$j]," ");                   /// string is stored with delimiter
            $arr[$j] = strtr($arr[$j],'|',$this->delimiter); /// Convert all replaced chars
         }
      }
      return $arr;
   }
   function cnv2uc($arg) { /// string or array to upper case
     if (is_string($arg)) return strtoupper($arg);
     if (is_array($arg)) {
        $newarr = array ();
        while ( list($key,$val) = each($arg)) {
           if (is_string($key))   $key = strtoupper($key);
           if (is_string($val)) { $newarr[$key] = strtoupper($val);    continue; }
           if (is_array($val))  { $newarr[$key] = $this->cnv2uc($val); continue; }
           $newarr[$key] = $val;
        }
	return $newarr;
     }
     return $arg;
   }
   function getuTime() {
     list($usec, $sec) = explode(" ",microtime()); 
     return ((float)$usec + (float)$sec); 
   }
   function chkMod10($n) {                    /// Calculate mod10 check digit
      $sum   = 0;
      $len   = strlen($n);
      if (strlen($len-1)<2) return FALSE;     /// Value must be at least 1 plus 1 check digit
      for($i=0; $i<$len-1; $i++) {            /// Check digit by digit, last digit is check digit
         $digit=substr($n,$i,1);
         if ($i%2==0)   $digit  = $digit * 2; /// multiply by 2 every other digit
         if ($digit<10) $sum   += $digit;
         else           $sum   += $digit-9;
      }
      $i = ceil($sum/10);
      $i = ($i * 10) - $sum;
      if (substr($n,$len,1)==$i) return TRUE;
      else                       return FALSE;
   }
   function errm ($etype, $ecode, $p1=NULL, $p2=NULL, $p3=NULL, $p4=NULL, $level=0) {
      global $dbfferr; // Last error
      global $emsg;
      $errtype = $emsg[$etype];
      $str     = NULL;
      if (!is_null($p1) && is_array($p1)) { $str .=  'Param p1 is array! '; $p1 = implode(' ',$p1); }
      if (!is_null($p2) && is_array($p2)) { $str .=  'Param p2 is array! '; $p2 = implode(' ',$p2); }
      if (!is_null($p3) && is_array($p3)) { $str .=  'Param p3 is array! '; $p3 = implode(' ',$p3); }
      if (!is_null($p4) && is_array($p4)) { $str .=  'Param p4 is array! '; $p4 = implode(' ',$p4); }
      if ($etype=='E' || ($this->trace && $level<$this->trace_level)) {
         if (isset($emsg[$ecode])) $str .= $emsg[$ecode];
         else $str .= '001, Error: Unknown error code: '.$ecode;
         $fn = basename($this->file); // hide path if users may see this
         $repstr = array ('%FIL%'=>$fn, '%TBL%'=>$this->tblname,
                          '%P01%'=>$p1, '%P02%'=>$p2, '%P03%'=>$p3, '%P04%'=>$p4);
         foreach ($repstr as $frmstr=>$tostr) {
            $pos = strpos($str,$frmstr);  // all ident fields is %XXX%
            if ($pos===false) continue;
            $str = substr_replace($str,$tostr,$pos,5);
         }
         $str = "DBFF-$ecode $errtype: $str";
         if ($etype=='E') $dbfferr = $this->errstk[] = $str;  // last error
      }
      if ($this->trace && $level<$this->trace_level) {
         if (!isset($this->startTime)) { $t = 0; $this->startTime = $this->getuTime(); }
         else $t = round($this->getuTime() - $this->startTime,3);
         $pos = strpos($str,'<br>');
         if (!$pos===false) $str = substr_replace($str," (time=$t)<br>",$pos,4);
         print $str;           // write2file(tracefile,$str)
      }
      return;
   }
} /// dbff class end
?>
Return current item: dbff