2020-01-14 15:31:41 +01:00
< ? php
2020-01-15 13:08:29 +01:00
/**
* \file dev / initdata / dbf / includes / dbase . class . php
* \ingroup dev
* \brief Class to manage DBF databases
*/
2020-01-14 15:31:41 +01:00
// source : https://github.com/donfbecker/php-dbase
define ( 'DBASE_RDONLY' , 0 );
define ( 'DBASE_WRONLY' , 1 );
define ( 'DBASE_RDWR' , 2 );
define ( 'DBASE_TYPE_DBASE' , 0 );
define ( 'DBASE_TYPE_FOXPRO' , 1 );
2020-01-15 13:08:29 +01:00
/**
* Class for DBase
*/
2020-01-14 15:34:36 +01:00
class DBase
{
2020-01-14 15:31:41 +01:00
private $fd ;
private $headerLength = 0 ;
private $fields = array ();
private $fieldCount = 0 ;
private $recordLength = 0 ;
private $recordCount = 0 ;
//resource dbase_open ( string $filename , int $mode )
2020-01-14 15:34:36 +01:00
public static function open ( $filename , $mode )
{
2020-01-14 15:31:41 +01:00
if ( ! file_exists ( $filename ))
return false ;
$modes = array ( 'r' , 'w' , 'r+' );
$mode = $modes [ $mode ];
$fd = fopen ( $filename , $mode );
if ( ! $fd )
return false ;
return new DBase ( $fd );
}
//resource dbase_create ( string $filename , array $fields [, int $type = DBASE_TYPE_DBASE ] )
2020-01-14 15:34:36 +01:00
public static function create ( $filename , $fields , $type = DBASE_TYPE_DBASE )
{
2020-01-14 15:31:41 +01:00
if ( file_exists ( $filename ))
return false ;
$fd = fopen ( $filename , 'c+' );
if ( ! $fd )
return false ;
// Byte 0 (1 byte): Valid dBASE for DOS file; bits 0-2 indicate version number, bit 3
// indicates the presence of a dBASE for DOS memo file, bits 4-6 indicate the
// presence of a SQL table, bit 7 indicates the presence of any memo file
// (either dBASE m PLUS or dBASE for DOS)
self :: putChar8 ( $fd , 5 );
// Byte 1-3 (3 bytes): Date of last update; formatted as YYMMDD
self :: putChar8 ( $fd , date ( 'Y' ) - 1900 );
self :: putChar8 ( $fd , date ( 'm' ));
self :: putChar8 ( $fd , date ( 'd' ));
// Byte 4-7 (32-bit number): Number of records in the database file. Currently 0
self :: putInt32 ( $fd , 0 );
// Byte 8-9 (16-bit number): Number of bytes in the header.
self :: putInt16 ( $fd , 32 + ( 32 * count ( $fields )) + 1 );
// Byte 10-11 (16-bit number): Number of bytes in record.
// Make sure the include the byte for deleted flag
$len = 1 ;
foreach ( $fields as & $field )
$len += self :: length ( $field );
self :: putInt16 ( $fd , $len );
// Byte 12-13 (2 bytes): Reserved, 0 filled.
self :: putInt16 ( $fd , 0 );
// Byte 14 (1 byte): Flag indicating incomplete transaction
// The ISMARKEDO function checks this flag. BEGIN TRANSACTION sets it to 1, END TRANSACTION and ROLLBACK reset it to 0.
self :: putChar8 ( $fd , 0 );
// Byte 15 (1 byte): Encryption flag. If this flag is set to 1, the message Database encrypted appears. Changing this flag to 0 removes the message, but does not decrypt the file.
self :: putChar8 ( $fd , 0 );
// Byte 16-27 (12 bytes): Reserved for dBASE for DOS in a multi-user environment
self :: putInt32 ( $fd , 0 );
self :: putInt32 ( $fd , 0 );
self :: putInt32 ( $fd , 0 );
// Byte 28 (1 byte): Production .mdx file flag; 0x01 if there is a production .mdx file, 0x00 if not
self :: putChar8 ( $fd , 0 );
// Byte 29 (1 byte): Language driver ID
// (no clue what this is)
self :: putChar8 ( $fd , 0 );
// Byte 30-31 (2 bytes): Reserved, 0 filled.
self :: putInt16 ( $fd , 0 );
// Byte 32 - n (32 bytes each): Field descriptor array
foreach ( $fields as & $field ) {
self :: putString ( $fd , $field [ 0 ], 11 ); // Byte 0 - 10 (11 bytes): Field name in ASCII (zero-filled)
self :: putString ( $fd , $field [ 1 ], 1 ); // Byte 11 (1 byte): Field type in ASCII (C, D, F, L, M, or N)
self :: putInt32 ( $fd , 0 ); // Byte 12 - 15 (4 bytes): Reserved
self :: putChar8 ( $fd , self :: length ( $field )); // Byte 16 (1 byte): Field length in binary. The maximum length of a field is 254 (0xFE).
self :: putChar8 ( $fd , $field [ 3 ]); // Byte 17 (1 byte): Field decimal count in binary
self :: putInt16 ( $fd , 0 ); // Byte 18 - 19 (2 bytes): Work area ID
self :: putChar8 ( $fd , 0 ); // Byte 20 (1 byte): Example (??)
self :: putInt32 ( $fd , 0 ); // Byte 21 - 30 (10 bytes): Reserved
self :: putInt32 ( $fd , 0 );
self :: putInt16 ( $fd , 0 );
self :: putChar8 ( $fd , 0 ); // Byte 31 (1 byte): Production MDX field flag; 1 if field has an index tag in the production MDX file, 0 if not
}
// Byte n + 1 (1 byte): 0x0D as the field descriptor array terminator
self :: putChar8 ( $fd , 0x0D );
return new DBase ( $fd );
}
// Create DBase instance
2020-01-14 15:34:36 +01:00
private function __construct ( $fd )
{
2020-01-14 15:31:41 +01:00
$this -> fd = $fd ;
// Byte 4-7 (32-bit number): Number of records in the database file. Currently 0
fseek ( $this -> fd , 4 , SEEK_SET );
$this -> recordCount = self :: getInt32 ( $fd );
// Byte 8-9 (16-bit number): Number of bytes in the header.
fseek ( $this -> fd , 8 , SEEK_SET );
$this -> headerLength = self :: getInt16 ( $fd );
// Number of fields is (headerLength - 33) / 32)
$this -> fieldCount = ( $this -> headerLength - 33 ) / 32 ;
// Byte 10-11 (16-bit number): Number of bytes in record.
fseek ( $this -> fd , 10 , SEEK_SET );
$this -> recordLength = self :: getInt16 ( $fd );
// Byte 32 - n (32 bytes each): Field descriptor array
fseek ( $fd , 32 , SEEK_SET );
for ( $i = 0 ; $i < $this -> fieldCount ; $i ++ ) {
$data = fread ( $this -> fd , 32 );
$field = array_map ( 'trim' , unpack ( 'a11name/a1type/c4/c1length/c1precision/s1workid/c1example/c10/c1production' , $data ));
$this -> fields [] = $field ;
}
}
//bool dbase_close ( resource $dbase_identifier )
2020-01-14 15:34:36 +01:00
public function close ()
{
2020-01-14 15:31:41 +01:00
fclose ( $this -> fd );
}
//array dbase_get_header_info ( resource $dbase_identifier )
2020-01-14 15:34:36 +01:00
public function get_header_info ()
{
2020-01-14 15:31:41 +01:00
return $this -> fields ;
}
//int dbase_numfields ( resource $dbase_identifier )
2020-01-14 15:34:36 +01:00
public function numfields ()
{
2020-01-14 15:31:41 +01:00
return $this -> fieldCount ;
}
//int dbase_numrecords ( resource $dbase_identifier )
2020-01-14 15:34:36 +01:00
public function numrecords ()
{
2020-01-14 15:31:41 +01:00
return $this -> recordCount ;
}
//bool dbase_add_record ( resource $dbase_identifier , array $record )
2020-01-14 15:34:36 +01:00
public function add_record ( $record )
{
2020-01-14 15:31:41 +01:00
if ( count ( $record ) != $this -> fieldCount )
return false ;
// Seek to end of file, minus the end of file marker
fseek ( $this -> fd , 0 , SEEK_END );
// Put the deleted flag
self :: putChar8 ( $this -> fd , 0x20 );
// Put the record
if ( ! $this -> putRecord ( $record ))
return false ;
// Update the record count
fseek ( $this -> fd , 4 );
self :: putInt32 ( $this -> fd , ++ $this -> recordCount );
return true ;
}
//bool dbase_replace_record ( resource $dbase_identifier , array $record , int $record_number )
2020-01-14 15:34:36 +01:00
public function replace_record ( $record , $record_number )
{
2020-01-14 15:31:41 +01:00
if ( count ( $record ) != $this -> fieldCount )
return false ;
if ( $record_number < 1 || $record_number > $this -> recordCount )
return false ;
// Skip to the record location, plus the 1 byte for the deleted flag
fseek ( $this -> fd , $this -> headerLength + ( $this -> recordLength * ( $record_number - 1 )) + 1 );
return $this -> putRecord ( $record );
}
//bool dbase_delete_record ( resource $dbase_identifier , int $record_number )
2020-01-14 15:34:36 +01:00
public function delete_record ( $record_number )
{
2020-01-14 15:31:41 +01:00
if ( $record_number < 1 || $record_number > $this -> recordCount )
return false ;
fseek ( $this -> fd , $this -> headerLength + ( $this -> recordLength * ( $record_number - 1 )));
self :: putChar8 ( $this -> fd , 0x2A );
return true ;
}
//array dbase_get_record ( resource $dbase_identifier , int $record_number )
2020-01-14 15:34:36 +01:00
public function get_record ( $record_number )
{
2020-01-14 15:31:41 +01:00
if ( $record_number < 1 || $record_number > $this -> recordCount )
return false ;
fseek ( $this -> fd , $this -> headerLength + ( $this -> recordLength * ( $record_number - 1 )));
$record = array (
'deleted' => self :: getChar8 ( $this -> fd ) == 0x2A ? 1 : 0
);
foreach ( $this -> fields as $i => & $field ) {
$value = trim ( fread ( $this -> fd , $field [ 'length' ]));
if ( $field [ 'type' ] == 'L' ) {
$value = strtolower ( $value );
if ( $value == 't' || $value == 'y' )
$value = true ;
2020-01-14 15:34:36 +01:00
elseif ( $value == 'f' || $value == 'n' )
2020-01-14 15:31:41 +01:00
$value = false ;
2020-05-21 02:17:21 +02:00
else $value = null ;
2020-01-14 15:31:41 +01:00
}
$record [ $i ] = $value ;
}
return $record ;
}
//array dbase_get_record_with_names ( resource $dbase_identifier , int $record_number )
2020-01-14 15:34:36 +01:00
public function get_record_with_names ( $record_number )
{
2020-01-14 15:31:41 +01:00
if ( $record_number < 1 || $record_number > $this -> recordCount )
return false ;
$record = $this -> get_record ( $record_number );
foreach ( $this -> fields as $i => & $field ) {
$record [ $field [ 'name' ]] = $record [ $i ];
unset ( $record [ $i ]);
}
return $record ;
}
//bool dbase_pack ( resource $dbase_identifier )
2020-01-14 15:34:36 +01:00
public function pack ()
{
2020-01-14 15:31:41 +01:00
$in_offset = $out_offset = $this -> headerLength ;
$new_count = 0 ;
$rec_count = $this -> recordCount ;
while ( $rec_count > 0 ) {
fseek ( $this -> fd , $in_offset , SEEK_SET );
$record = fread ( $this -> fd , $this -> recordLength );
$deleted = substr ( $record , 0 , 1 );
if ( $deleted != '*' ) {
fseek ( $this -> fd , $out_offset , SEEK_SET );
fwrite ( $this -> fd , $record );
$out_offset += $this -> recordLength ;
$new_count ++ ;
}
$in_offset += $this -> recordLength ;
$rec_count -- ;
}
ftruncate ( $this -> fd , $out_offset );
// Update the record count
fseek ( $this -> fd , 4 );
self :: putInt32 ( $this -> fd , $new_count );
}
/*
* A few utilitiy functions
*/
2020-01-14 15:34:36 +01:00
private static function length ( $field )
{
2020-01-14 15:31:41 +01:00
switch ( $field [ 1 ]) {
case 'D' : // Date: Numbers and a character to separate month, day, and year (stored internally as 8 digits in YYYYMMDD format)
return 8 ;
case 'T' : // DateTime (YYYYMMDDhhmmss.uuu) (FoxPro)
return 18 ;
case 'M' : // Memo (ignored): All ASCII characters (stored internally as 10 digits representing a .dbt block number, right justified, padded with whitespaces)
case 'N' : // Number: -.0123456789 (right justified, padded with whitespaces)
case 'F' : // Float: -.0123456789 (right justified, padded with whitespaces)
case 'C' : // String: All ASCII characters (padded with whitespaces up to the field's length)
return $field [ 2 ];
case 'L' : // Boolean: YyNnTtFf? (? when not initialized)
return 1 ;
}
return 0 ;
}
/*
* Functions for reading and writing bytes
*/
2020-01-14 15:34:36 +01:00
private static function getChar8 ( $fd )
{
2020-01-14 15:31:41 +01:00
return ord ( fread ( $fd , 1 ));
}
2020-01-14 15:34:36 +01:00
private static function putChar8 ( $fd , $value )
{
2020-01-14 15:31:41 +01:00
return fwrite ( $fd , chr ( $value ));
}
2020-01-14 15:34:36 +01:00
private static function getInt16 ( $fd , $n = 1 )
{
2020-01-14 15:31:41 +01:00
$data = fread ( $fd , 2 * $n );
$i = unpack ( " S $n " , $data );
if ( $n == 1 )
return ( int ) $i [ 1 ];
2020-05-21 02:17:21 +02:00
else return array_merge ( $i );
2020-01-14 15:31:41 +01:00
}
2020-01-14 15:34:36 +01:00
private static function putInt16 ( $fd , $value )
{
2020-01-14 15:31:41 +01:00
return fwrite ( $fd , pack ( 'S' , $value ));
}
2020-01-14 15:34:36 +01:00
private static function getInt32 ( $fd , $n = 1 )
{
2020-01-14 15:31:41 +01:00
$data = fread ( $fd , 4 * $n );
$i = unpack ( " L $n " , $data );
if ( $n == 1 )
return ( int ) $i [ 1 ];
2020-05-21 02:17:21 +02:00
else return array_merge ( $i );
2020-01-14 15:31:41 +01:00
}
2020-01-14 15:34:36 +01:00
private static function putInt32 ( $fd , $value )
{
2020-01-14 15:31:41 +01:00
return fwrite ( $fd , pack ( 'L' , $value ));
}
2020-01-14 15:34:36 +01:00
private static function putString ( $fd , $value , $length = 254 )
{
2020-01-14 15:31:41 +01:00
$ret = fwrite ( $fd , pack ( 'A' . $length , $value ));
}
2020-01-14 15:34:36 +01:00
private function putRecord ( $record )
{
2020-01-14 15:31:41 +01:00
foreach ( $this -> fields as $i => & $field ) {
$value = $record [ $i ];
// Number types are right aligned with spaces
if ( $field [ 'type' ] == 'N' || $field [ 'type' ] == 'F' && strlen ( $value ) < $field [ 'length' ]) {
$value = str_repeat ( ' ' , $field [ 'length' ] - strlen ( $value )) . $value ;
}
self :: putString ( $this -> fd , $value , $field [ 'length' ]);
}
return true ;
}
}
if ( ! function_exists ( 'dbase_open' )) {
2020-01-14 15:34:36 +01:00
function dbase_open ( $filename , $mode )
{
2020-01-14 15:31:41 +01:00
return DBase :: open ( $filename , $mode );
}
2020-01-14 15:34:36 +01:00
function dbase_create ( $filename , $fields , $type = DBASE_TYPE_DBASE )
{
2020-01-14 15:31:41 +01:00
return DBase :: create ( $filename , $fields , $type );
}
2020-01-14 15:34:36 +01:00
function dbase_close ( $dbase_identifier )
{
2020-01-14 15:31:41 +01:00
return $dbase_identifier -> close ();
}
2020-01-14 15:34:36 +01:00
function dbase_get_header_info ( $dbase_identifier )
{
2020-01-14 15:31:41 +01:00
return $dbase_identifier -> get_header_info ();
}
2020-01-14 15:34:36 +01:00
function dbase_numfields ( $dbase_identifier )
{
2020-01-14 15:31:41 +01:00
$dbase_identifier -> numfields ();
}
2020-01-14 15:34:36 +01:00
function dbase_numrecords ( $dbase_identifier )
{
2020-01-14 15:31:41 +01:00
return $dbase_identifier -> numrecords ();
}
2020-01-14 15:34:36 +01:00
function dbase_add_record ( $dbase_identifier , $record )
{
2020-01-14 15:31:41 +01:00
return $dbase_identifier -> add_record ( $record );
}
2020-01-14 15:34:36 +01:00
function dbase_delete_record ( $dbase_identifier , $record_number )
{
2020-01-14 15:31:41 +01:00
return $dbase_identifier -> delete_record ( $record_number );
}
2020-01-14 15:34:36 +01:00
function dbase_replace_record ( $dbase_identifier , $record , $record_number )
{
2020-01-14 15:31:41 +01:00
return $dbase_identifier -> replace_record ( $record , $record_number );
}
2020-01-14 15:34:36 +01:00
function dbase_get_record ( $dbase_identifier , $record_number )
{
2020-01-14 15:31:41 +01:00
return $dbase_identifier -> get_record ( $record_number );
}
2020-01-14 15:34:36 +01:00
function dbase_get_record_with_names ( $dbase_identifier , $record_number )
{
2020-01-14 15:31:41 +01:00
return $dbase_identifier -> get_record_with_names ( $record_number );
}
2020-01-14 15:34:36 +01:00
function dbase_pack ( $dbase_identifier )
{
2020-01-14 15:31:41 +01:00
return $dbase_identifier -> pack ();
}
2020-01-14 15:34:36 +01:00
}