PageRenderTime 24ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 0ms

/dev/initdata/dbf/includes/dbase.class.php

http://github.com/Dolibarr/dolibarr
PHP | 402 lines | 302 code | 40 blank | 60 comment | 42 complexity | 8269af733d3d55e88f60ad68aa5ccec2 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-3.0, LGPL-2.0, CC-BY-SA-4.0, BSD-3-Clause, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, LGPL-2.1, MIT
  1. <?php
  2. /**
  3. * \file dev/initdata/dbf/includes/dbase.class.php
  4. * \ingroup dev
  5. * \brief Class to manage DBF databases
  6. */
  7. // source : https://github.com/donfbecker/php-dbase
  8. define('DBASE_RDONLY', 0);
  9. define('DBASE_WRONLY', 1);
  10. define('DBASE_RDWR', 2);
  11. define('DBASE_TYPE_DBASE', 0);
  12. define('DBASE_TYPE_FOXPRO', 1);
  13. /**
  14. * Class for DBase
  15. */
  16. class DBase
  17. {
  18. private $fd;
  19. private $headerLength = 0;
  20. private $fields = array();
  21. private $fieldCount = 0;
  22. private $recordLength = 0;
  23. private $recordCount = 0;
  24. //resource dbase_open ( string $filename , int $mode )
  25. public static function open($filename, $mode)
  26. {
  27. if (!file_exists($filename))
  28. return false;
  29. $modes = array('r', 'w', 'r+');
  30. $mode = $modes[$mode];
  31. $fd = fopen($filename, $mode);
  32. if (!$fd)
  33. return false;
  34. return new DBase($fd);
  35. }
  36. //resource dbase_create ( string $filename , array $fields [, int $type = DBASE_TYPE_DBASE ] )
  37. public static function create($filename, $fields, $type = DBASE_TYPE_DBASE)
  38. {
  39. if (file_exists($filename))
  40. return false;
  41. $fd = fopen($filename, 'c+');
  42. if (!$fd)
  43. return false;
  44. // Byte 0 (1 byte): Valid dBASE for DOS file; bits 0-2 indicate version number, bit 3
  45. // indicates the presence of a dBASE for DOS memo file, bits 4-6 indicate the
  46. // presence of a SQL table, bit 7 indicates the presence of any memo file
  47. // (either dBASE m PLUS or dBASE for DOS)
  48. self::putChar8($fd, 5);
  49. // Byte 1-3 (3 bytes): Date of last update; formatted as YYMMDD
  50. self::putChar8($fd, date('Y') - 1900);
  51. self::putChar8($fd, date('m'));
  52. self::putChar8($fd, date('d'));
  53. // Byte 4-7 (32-bit number): Number of records in the database file. Currently 0
  54. self::putInt32($fd, 0);
  55. // Byte 8-9 (16-bit number): Number of bytes in the header.
  56. self::putInt16($fd, 32 + (32 * count($fields)) + 1);
  57. // Byte 10-11 (16-bit number): Number of bytes in record.
  58. // Make sure the include the byte for deleted flag
  59. $len = 1;
  60. foreach ($fields as &$field)
  61. $len += self::length($field);
  62. self::putInt16($fd, $len);
  63. // Byte 12-13 (2 bytes): Reserved, 0 filled.
  64. self::putInt16($fd, 0);
  65. // Byte 14 (1 byte): Flag indicating incomplete transaction
  66. // The ISMARKEDO function checks this flag. BEGIN TRANSACTION sets it to 1, END TRANSACTION and ROLLBACK reset it to 0.
  67. self::putChar8($fd, 0);
  68. // 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.
  69. self::putChar8($fd, 0);
  70. // Byte 16-27 (12 bytes): Reserved for dBASE for DOS in a multi-user environment
  71. self::putInt32($fd, 0);
  72. self::putInt32($fd, 0);
  73. self::putInt32($fd, 0);
  74. // Byte 28 (1 byte): Production .mdx file flag; 0x01 if there is a production .mdx file, 0x00 if not
  75. self::putChar8($fd, 0);
  76. // Byte 29 (1 byte): Language driver ID
  77. // (no clue what this is)
  78. self::putChar8($fd, 0);
  79. // Byte 30-31 (2 bytes): Reserved, 0 filled.
  80. self::putInt16($fd, 0);
  81. // Byte 32 - n (32 bytes each): Field descriptor array
  82. foreach ($fields as &$field) {
  83. self::putString($fd, $field[0], 11); // Byte 0 - 10 (11 bytes): Field name in ASCII (zero-filled)
  84. self::putString($fd, $field[1], 1); // Byte 11 (1 byte): Field type in ASCII (C, D, F, L, M, or N)
  85. self::putInt32($fd, 0); // Byte 12 - 15 (4 bytes): Reserved
  86. self::putChar8($fd, self::length($field)); // Byte 16 (1 byte): Field length in binary. The maximum length of a field is 254 (0xFE).
  87. self::putChar8($fd, $field[3]); // Byte 17 (1 byte): Field decimal count in binary
  88. self::putInt16($fd, 0); // Byte 18 - 19 (2 bytes): Work area ID
  89. self::putChar8($fd, 0); // Byte 20 (1 byte): Example (??)
  90. self::putInt32($fd, 0); // Byte 21 - 30 (10 bytes): Reserved
  91. self::putInt32($fd, 0);
  92. self::putInt16($fd, 0);
  93. 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
  94. }
  95. // Byte n + 1 (1 byte): 0x0D as the field descriptor array terminator
  96. self::putChar8($fd, 0x0D);
  97. return new DBase($fd);
  98. }
  99. // Create DBase instance
  100. private function __construct($fd)
  101. {
  102. $this->fd = $fd;
  103. // Byte 4-7 (32-bit number): Number of records in the database file. Currently 0
  104. fseek($this->fd, 4, SEEK_SET);
  105. $this->recordCount = self::getInt32($fd);
  106. // Byte 8-9 (16-bit number): Number of bytes in the header.
  107. fseek($this->fd, 8, SEEK_SET);
  108. $this->headerLength = self::getInt16($fd);
  109. // Number of fields is (headerLength - 33) / 32)
  110. $this->fieldCount = ($this->headerLength - 33) / 32;
  111. // Byte 10-11 (16-bit number): Number of bytes in record.
  112. fseek($this->fd, 10, SEEK_SET);
  113. $this->recordLength = self::getInt16($fd);
  114. // Byte 32 - n (32 bytes each): Field descriptor array
  115. fseek($fd, 32, SEEK_SET);
  116. for ($i = 0; $i < $this->fieldCount; $i++) {
  117. $data = fread($this->fd, 32);
  118. $field = array_map('trim', unpack('a11name/a1type/c4/c1length/c1precision/s1workid/c1example/c10/c1production', $data));
  119. $this->fields[] = $field;
  120. }
  121. }
  122. //bool dbase_close ( resource $dbase_identifier )
  123. public function close()
  124. {
  125. fclose($this->fd);
  126. }
  127. //array dbase_get_header_info ( resource $dbase_identifier )
  128. public function get_header_info()
  129. {
  130. return $this->fields;
  131. }
  132. //int dbase_numfields ( resource $dbase_identifier )
  133. public function numfields()
  134. {
  135. return $this->fieldCount;
  136. }
  137. //int dbase_numrecords ( resource $dbase_identifier )
  138. public function numrecords()
  139. {
  140. return $this->recordCount;
  141. }
  142. //bool dbase_add_record ( resource $dbase_identifier , array $record )
  143. public function add_record($record)
  144. {
  145. if (count($record) != $this->fieldCount)
  146. return false;
  147. // Seek to end of file, minus the end of file marker
  148. fseek($this->fd, 0, SEEK_END);
  149. // Put the deleted flag
  150. self::putChar8($this->fd, 0x20);
  151. // Put the record
  152. if (!$this->putRecord($record))
  153. return false;
  154. // Update the record count
  155. fseek($this->fd, 4);
  156. self::putInt32($this->fd, ++$this->recordCount);
  157. return true;
  158. }
  159. //bool dbase_replace_record ( resource $dbase_identifier , array $record , int $record_number )
  160. public function replace_record($record, $record_number)
  161. {
  162. if (count($record) != $this->fieldCount)
  163. return false;
  164. if ($record_number < 1 || $record_number > $this->recordCount)
  165. return false;
  166. // Skip to the record location, plus the 1 byte for the deleted flag
  167. fseek($this->fd, $this->headerLength + ($this->recordLength * ($record_number - 1)) + 1);
  168. return $this->putRecord($record);
  169. }
  170. //bool dbase_delete_record ( resource $dbase_identifier , int $record_number )
  171. public function delete_record($record_number)
  172. {
  173. if ($record_number < 1 || $record_number > $this->recordCount)
  174. return false;
  175. fseek($this->fd, $this->headerLength + ($this->recordLength * ($record_number - 1)));
  176. self::putChar8($this->fd, 0x2A);
  177. return true;
  178. }
  179. //array dbase_get_record ( resource $dbase_identifier , int $record_number )
  180. public function get_record($record_number)
  181. {
  182. if ($record_number < 1 || $record_number > $this->recordCount)
  183. return false;
  184. fseek($this->fd, $this->headerLength + ($this->recordLength * ($record_number - 1)));
  185. $record = array(
  186. 'deleted' => self::getChar8($this->fd) == 0x2A ? 1 : 0
  187. );
  188. foreach ($this->fields as $i => &$field) {
  189. $value = trim(fread($this->fd, $field['length']));
  190. if ($field['type'] == 'L') {
  191. $value = strtolower($value);
  192. if ($value == 't' || $value == 'y')
  193. $value = true;
  194. elseif ($value == 'f' || $value == 'n')
  195. $value = false;
  196. else
  197. $value = null;
  198. }
  199. $record[$i] = $value;
  200. }
  201. return $record;
  202. }
  203. //array dbase_get_record_with_names ( resource $dbase_identifier , int $record_number )
  204. public function get_record_with_names($record_number)
  205. {
  206. if ($record_number < 1 || $record_number > $this->recordCount)
  207. return false;
  208. $record = $this->get_record($record_number);
  209. foreach ($this->fields as $i => &$field) {
  210. $record[$field['name']] = $record[$i];
  211. unset($record[$i]);
  212. }
  213. return $record;
  214. }
  215. //bool dbase_pack ( resource $dbase_identifier )
  216. public function pack()
  217. {
  218. $in_offset = $out_offset = $this->headerLength;
  219. $new_count = 0;
  220. $rec_count = $this->recordCount;
  221. while ($rec_count > 0) {
  222. fseek($this->fd, $in_offset, SEEK_SET);
  223. $record = fread($this->fd, $this->recordLength);
  224. $deleted = substr($record, 0, 1);
  225. if ($deleted != '*') {
  226. fseek($this->fd, $out_offset, SEEK_SET);
  227. fwrite($this->fd, $record);
  228. $out_offset += $this->recordLength;
  229. $new_count++;
  230. }
  231. $in_offset += $this->recordLength;
  232. $rec_count--;
  233. }
  234. ftruncate($this->fd, $out_offset);
  235. // Update the record count
  236. fseek($this->fd, 4);
  237. self::putInt32($this->fd, $new_count);
  238. }
  239. /*
  240. * A few utilitiy functions
  241. */
  242. private static function length($field)
  243. {
  244. switch ($field[1]) {
  245. case 'D': // Date: Numbers and a character to separate month, day, and year (stored internally as 8 digits in YYYYMMDD format)
  246. return 8;
  247. case 'T': // DateTime (YYYYMMDDhhmmss.uuu) (FoxPro)
  248. return 18;
  249. case 'M': // Memo (ignored): All ASCII characters (stored internally as 10 digits representing a .dbt block number, right justified, padded with whitespaces)
  250. case 'N': // Number: -.0123456789 (right justified, padded with whitespaces)
  251. case 'F': // Float: -.0123456789 (right justified, padded with whitespaces)
  252. case 'C': // String: All ASCII characters (padded with whitespaces up to the field's length)
  253. return $field[2];
  254. case 'L': // Boolean: YyNnTtFf? (? when not initialized)
  255. return 1;
  256. }
  257. return 0;
  258. }
  259. /*
  260. * Functions for reading and writing bytes
  261. */
  262. private static function getChar8($fd)
  263. {
  264. return ord(fread($fd, 1));
  265. }
  266. private static function putChar8($fd, $value)
  267. {
  268. return fwrite($fd, chr($value));
  269. }
  270. private static function getInt16($fd, $n = 1)
  271. {
  272. $data = fread($fd, 2 * $n);
  273. $i = unpack("S$n", $data);
  274. if ($n == 1)
  275. return (int) $i[1];
  276. else
  277. return array_merge($i);
  278. }
  279. private static function putInt16($fd, $value)
  280. {
  281. return fwrite($fd, pack('S', $value));
  282. }
  283. private static function getInt32($fd, $n = 1)
  284. {
  285. $data = fread($fd, 4 * $n);
  286. $i = unpack("L$n", $data);
  287. if ($n == 1)
  288. return (int) $i[1];
  289. else
  290. return array_merge($i);
  291. }
  292. private static function putInt32($fd, $value)
  293. {
  294. return fwrite($fd, pack('L', $value));
  295. }
  296. private static function putString($fd, $value, $length = 254)
  297. {
  298. $ret = fwrite($fd, pack('A' . $length, $value));
  299. }
  300. private function putRecord($record)
  301. {
  302. foreach ($this->fields as $i => &$field) {
  303. $value = $record[$i];
  304. // Number types are right aligned with spaces
  305. if ($field['type'] == 'N' || $field['type'] == 'F' && strlen($value) < $field['length']) {
  306. $value = str_repeat(' ', $field['length'] - strlen($value)) . $value;
  307. }
  308. self::putString($this->fd, $value, $field['length']);
  309. }
  310. return true;
  311. }
  312. }
  313. if (!function_exists('dbase_open')) {
  314. function dbase_open($filename, $mode)
  315. {
  316. return DBase::open($filename, $mode);
  317. }
  318. function dbase_create($filename, $fields, $type = DBASE_TYPE_DBASE)
  319. {
  320. return DBase::create($filename, $fields, $type);
  321. }
  322. function dbase_close($dbase_identifier)
  323. {
  324. return $dbase_identifier->close();
  325. }
  326. function dbase_get_header_info($dbase_identifier)
  327. {
  328. return $dbase_identifier->get_header_info();
  329. }
  330. function dbase_numfields($dbase_identifier)
  331. {
  332. $dbase_identifier->numfields();
  333. }
  334. function dbase_numrecords($dbase_identifier)
  335. {
  336. return $dbase_identifier->numrecords();
  337. }
  338. function dbase_add_record($dbase_identifier, $record)
  339. {
  340. return $dbase_identifier->add_record($record);
  341. }
  342. function dbase_delete_record($dbase_identifier, $record_number)
  343. {
  344. return $dbase_identifier->delete_record($record_number);
  345. }
  346. function dbase_replace_record($dbase_identifier, $record, $record_number)
  347. {
  348. return $dbase_identifier->replace_record($record, $record_number);
  349. }
  350. function dbase_get_record($dbase_identifier, $record_number)
  351. {
  352. return $dbase_identifier->get_record($record_number);
  353. }
  354. function dbase_get_record_with_names($dbase_identifier, $record_number)
  355. {
  356. return $dbase_identifier->get_record_with_names($record_number);
  357. }
  358. function dbase_pack($dbase_identifier)
  359. {
  360. return $dbase_identifier->pack();
  361. }
  362. }