Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
20.00% |
8 / 40 |
CRAP | |
28.27% |
266 / 941 |
| tdb | |
0.00% |
0 / 1 |
|
20.00% |
8 / 40 |
36239.19 | |
28.12% |
264 / 939 |
| __construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
| tdb | |
0.00% |
0 / 1 |
12.00 | |
66.67% |
16 / 24 |
|||
| createDatabase | |
100.00% |
1 / 1 |
6 | |
100.00% |
19 / 19 |
|||
| removeDatabase | |
100.00% |
1 / 1 |
3 | |
100.00% |
6 / 6 |
|||
| removeTable | |
100.00% |
1 / 1 |
5 | |
100.00% |
18 / 18 |
|||
| getNumberOfRecords | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
| getTableList | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| getFieldList | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 9 |
|||
| readHeader | |
100.00% |
1 / 1 |
17 | |
100.00% |
58 / 58 |
|||
| setFp | |
0.00% |
0 / 1 |
3.04 | |
83.33% |
10 / 12 |
|||
| cleanUp | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 7 |
|||
| isTable | |
100.00% |
1 / 1 |
4 | |
100.00% |
6 / 6 |
|||
| createTable | |
0.00% |
0 / 1 |
12.61 | |
83.78% |
31 / 37 |
|||
| addField | |
0.00% |
0 / 1 |
306 | |
0.00% |
0 / 58 |
|||
| editField | |
0.00% |
0 / 1 |
506 | |
0.00% |
0 / 81 |
|||
| removeField | |
0.00% |
0 / 1 |
182 | |
0.00% |
0 / 54 |
|||
| edit | |
0.00% |
0 / 1 |
90 | |
0.00% |
0 / 36 |
|||
| delete | |
0.00% |
0 / 1 |
5.10 | |
83.87% |
26 / 31 |
|||
| sortAndBuild | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| sort | |
0.00% |
0 / 1 |
156 | |
0.00% |
0 / 48 |
|||
| reBuild | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| add | |
0.00% |
0 / 1 |
16.62 | |
76.27% |
45 / 59 |
|||
| fileIdById | |
0.00% |
0 / 1 |
3.14 | |
75.00% |
9 / 12 |
|||
| get_ref_data | |
100.00% |
1 / 1 |
2 | |
100.00% |
3 / 3 |
|||
| get | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 19 |
|||
| listRec | |
0.00% |
0 / 1 |
110 | |
0.00% |
0 / 38 |
|||
| parseQueryString | |
0.00% |
0 / 1 |
992 | |
0.00% |
0 / 56 |
|||
| query | |
0.00% |
0 / 1 |
1260 | |
0.00% |
0 / 104 |
|||
| basicQuery | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| parseRecord | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 14 |
|||
| bytesToSeek | |
0.00% |
0 / 1 |
3.33 | |
66.67% |
4 / 6 |
|||
| rewriteMemo | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| readMemo | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 15 |
|||
| deleteMemo | |
0.00% |
0 / 1 |
90 | |
0.00% |
0 / 20 |
|||
| writeMemo | |
0.00% |
0 / 1 |
306 | |
0.00% |
0 / 48 |
|||
| check | |
0.00% |
0 / 1 |
17.38 | |
62.50% |
10 / 16 |
|||
| sendError | |
0.00% |
0 / 1 |
90 | |
0.00% |
0 / 9 |
|||
| define_error_handler | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| version | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| deXSS | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| <?php | |
| namespace TextDb; | |
| /* | |
| variable types: | |
| string - anything goes, length restriction (>1 bytes) | |
| number - numbers only (some symbols allowed), length restriction (>1 bytes) | |
| memo - anything goes, NO length restriction (7 bytes) | |
| id - auto-increment id field used to retrieve specific records (7 bytes) | |
| */ | |
| //TDB will print errors instead of using trigger_error() or a user defined error handler | |
| /** | |
| * | |
| */ | |
| use TextDb\Exception\DatabaseExistsException; | |
| use TextDb\Exception\InvalidDirectoryException; | |
| use TextDb\Exception\InvalidFilePointerException; | |
| use TextDb\Exception\NotWritableException; | |
| use TextDb\Exception\InvalidTableException; | |
| use TextDb\Exception\UndefinedDatabaseException; | |
| use TextDb\Exception\UndefinedWorkingDirectoryException; | |
| DEFINE('TDB_PRINT_ERRORS', FALSE); | |
| //TDB will include the file and line number of your script that led to the error | |
| /** | |
| * | |
| */ | |
| DEFINE('TDB_ERROR_INCLUDE_ORIGIN', TRUE); | |
| /** | |
| * Class tdb | |
| * @package TextDb | |
| */ | |
| class tdb { | |
| /** | |
| * @var array | |
| */ | |
| protected $fp = array(); // allowing for multiply file pointers | |
| /** | |
| * @var string | |
| */ | |
| protected $workingDir; // working directory | |
| /** | |
| * @var string | |
| */ | |
| protected $Db; // database.tdb | |
| /** | |
| * @var array | |
| */ | |
| protected $Tables; // list of tables in the database | |
| /** | |
| * @var bool | |
| */ | |
| protected $error_handler = false; // user defined error handler | |
| /** | |
| * @var array | |
| */ | |
| protected $_header = array(); // cache publics | |
| /** | |
| * @var array | |
| */ | |
| protected $_query = array(); | |
| /** | |
| * @var array | |
| */ | |
| protected $_fileId = array(); | |
| /** | |
| * @var array | |
| */ | |
| protected $_ref = array(); | |
| /** | |
| * @var array | |
| */ | |
| protected $_firstBlankMemoBlockRef = array(); | |
| //Does not store FPs but physical file addy, does not clear for cleanUp() etc. | |
| //prompts statsclearcache() | |
| /** | |
| * @var array | |
| */ | |
| protected $editedTable = array(); | |
| /** | |
| * tdb constructor. | |
| */ | |
| public function __construct() | |
| { | |
| } | |
| /** | |
| * Defines the directory and database. Must be ran before most functions can function. | |
| * | |
| * @param string $dir | |
| * @param string $db | |
| * @return bool | |
| */ | |
| public function tdb($dir="", $db="") { | |
| if($dir == "" && $db == "") return false; | |
| $dir = str_replace("../", "", $dir); | |
| if(substr($dir, -1) != "/") $dir .= "/"; | |
| if(substr($db, -4) != '.tdb') $db .= '.tdb'; | |
| if(is_dir($dir)) { | |
| if(!is_writable($dir)) { | |
| $this->sendError(E_ERROR, "Fatal: Working directory ($dir) is not writable, try chmod 777 if 666 does not work.", __LINE__); | |
| } else { | |
| if(is_writable($dir.$db)) { | |
| if(filesize($dir.$db) != 0) { | |
| $f = fopen($dir.$db, "rb"); | |
| @$this->Tables = trim(@fread($f, filesize($dir.$db))); | |
| fclose($f); | |
| $this->Tables = explode("\n", $this->Tables); | |
| } else $this->Tables = array(); | |
| $this->Db = $db; | |
| $this->workingDir = $dir; | |
| return true; | |
| } else { | |
| $this->sendError(E_ERROR, "Fatal: Database ($db) is not writable, try chmod 777 if 666 does not work.", __LINE__); | |
| return false; | |
| } | |
| } | |
| } else { | |
| $this->sendError(E_USER_ERROR, "Failed setting working directory ($dir), not a valid path.", __LINE__); | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Creates a database. Run tdb() before handling the database | |
| * | |
| * @param string $dir | |
| * @param string $filename | |
| * @throws DatabaseExistsException if the database already exists | |
| * @throws NotWritableException if the directory isn't writable | |
| * @throws InvalidDirectoryException if the directory doesn't exist | |
| */ | |
| public function createDatabase($dir, $filename) { | |
| $dir = str_replace("../", "", $dir); | |
| if(substr($dir, -1) != "/") { | |
| $dir .= "/"; | |
| } | |
| if(substr($filename, -4) != ".tdb") { | |
| $filename .= ".tdb"; | |
| } | |
| if(file_exists($dir.$filename)) { | |
| throw new DatabaseExistsException(); | |
| } | |
| if(is_dir($dir)) { | |
| if(!is_writable($dir)) { | |
| throw new NotWritableException(); | |
| } else { | |
| $f = fopen($dir.$filename, "wb"); | |
| fwrite($f, ""); | |
| fclose($f); | |
| $this->tdb($dir, $filename); | |
| } | |
| } else { | |
| throw new InvalidDirectoryException(); | |
| } | |
| } | |
| /** | |
| * Deletes the database as well as its tables | |
| * | |
| * @return bool | |
| */ | |
| public function removeDatabase() { | |
| $this->check(); | |
| foreach($this->Tables as $ta) { | |
| if(trim($ta) != "") $this->removeTable($ta); | |
| } | |
| unlink($this->workingDir.$this->Db); | |
| return true; | |
| } | |
| /** | |
| * Deletes a table managed be the defined database. | |
| * | |
| * @param string $tableName | |
| * @return bool | |
| */ | |
| public function removeTable($tableName) { | |
| $this->check(); | |
| if(substr($tableName, 0, (strlen($this->Db)-4)) != substr($this->Db, 0, -4)) $tableName = substr($this->Db, 0, -4).'_'.$tableName; | |
| if(!$this->isTable($tableName)) { | |
| throw new InvalidTableException(); | |
| } | |
| foreach($this->Tables as $key => $ta) { | |
| if($ta == $tableName) { | |
| unset($this->Tables[$key]); | |
| $f = fopen($this->workingDir.$this->Db, 'wb'); | |
| flock($f, 2); | |
| fwrite($f, implode("\n", $this->Tables)); | |
| flock($f, 3); | |
| fclose($f); | |
| } | |
| } | |
| unlink($this->workingDir.$tableName.'.ta'); | |
| unlink($this->workingDir.$tableName.'.memo'); | |
| unlink($this->workingDir.$tableName.'.ref'); | |
| return true; | |
| } | |
| /** | |
| * Returns the number of records. | |
| * | |
| * @param string $fp | |
| * @return int count on success, bool false on fail | |
| */ | |
| public function getNumberOfRecords($fp) { | |
| $this->check($fp); | |
| return substr_count($this->get_ref_data($fp), chr(31)); | |
| } | |
| /** | |
| * Returns the names of tables. | |
| * | |
| * @return array tables on success, bool false on fail | |
| */ | |
| function getTableList() { | |
| $this->check(); | |
| return $this->Tables; | |
| } | |
| /** | |
| * Retrieves the list of fields of a table and their parameters | |
| * | |
| * @param string $fp | |
| * @return array fields on success, bool false on fail | |
| */ | |
| function getFieldList($fp) { | |
| $this->check($fp); | |
| $header = array(); | |
| $this->readHeader($fp, $header); | |
| $returnArr = array(); | |
| $cHeader = count($header) - 8; | |
| for($i=1;$i<=$cHeader;$i++) { | |
| $returnArr[] = $header[$i]; | |
| } | |
| return $returnArr; | |
| } | |
| /** | |
| * Retrieves the headers. | |
| * | |
| * @param string $fp | |
| * @param array $header | |
| * @return bool | |
| */ | |
| function readHeader($fp, &$header) { | |
| $this->check($fp); | |
| if(isset($this->_header[$fp])) { | |
| $header = $this->_header[$fp]; | |
| return true; | |
| } | |
| $header = array(); | |
| $f = fopen($this->fp[$fp].'.ta', 'rb'); | |
| if(!$f) return false; | |
| $header["headLen"] = ""; | |
| while(!feof($f)) { | |
| $next = fgetc($f); | |
| if(ord($next) == 28) break; | |
| else $header["headLen"] .= $next; | |
| } | |
| if(is_numeric($header['headLen'])) $header['headLen'] = (int)$header['headLen']; | |
| $header["raw"] = fread($f, $header["headLen"]); | |
| $header["fieldsraw"] = explode(chr(29), $header["raw"]); | |
| for($i=0;$i<count($header["fieldsraw"]);$i++) { | |
| $tmp = explode(chr(31), $header["fieldsraw"][$i]); | |
| $header[$tmp[0]]["fName"] = $tmp[3]; | |
| $header[$tmp[0]]["fType"] = $tmp[1]; | |
| $header[$tmp[0]]["fLength"] = (int)$tmp[2]; | |
| unset($tmp); | |
| } | |
| unset($header["raw"],$header["fieldsraw"]); | |
| $header["recLen"] = ""; | |
| while(!feof($f)) { | |
| $next = fgetc($f); | |
| if(ord($next) == 28) break; | |
| else $header["recLen"] .= $next; | |
| } | |
| if(is_numeric($header['recLen'])) $header['recLen'] = (int)$header['recLen']; | |
| $header["curId"] = ""; | |
| $header["idPos"] = ftell($f); | |
| while(!feof($f)) { | |
| $next = fgetc($f); | |
| if(ord($next) == 28) break; | |
| else $header["curId"] .= $next; | |
| } | |
| $header["curId"] = (int)trim($header["curId"]); | |
| $header["lastBlank"] = ""; | |
| $header["blankPos"] = ftell($f); | |
| while(!feof($f)) { | |
| $next = fgetc($f); | |
| if(ord($next) == 28) break; | |
| else $header["lastBlank"] .= $next; | |
| } | |
| $header["lastBlank"] = (int)trim($header["lastBlank"]); | |
| $header["blockLength"] = ""; | |
| while(!feof($f)) { | |
| $next = fgetc($f); | |
| if(ord($next) == 28) break; | |
| else $header["blockLength"] .= $next; | |
| } | |
| if(is_numeric($header['blockLength'])) $header['blockLength'] = (int)$header['blockLength']; | |
| $header["recPos"] = ftell($f); | |
| fclose($f); | |
| $this->_header[$fp] = $header; | |
| return true; | |
| } | |
| /** | |
| * Sets a filepointer to a table. Necessary for most functions. | |
| * | |
| * @param string $fp | |
| * @param string $table | |
| * @return bool | |
| */ | |
| function setFp($fp, $table) { | |
| if(substr($table, 0, (strlen($this->Db)-4)) != substr($this->Db, 0, -4)) $table = substr($this->Db, 0, -4).'_'.$table; | |
| if($this->isTable($table)) { | |
| $this->fp[$fp] = $this->workingDir.$table; | |
| unset($this->_header[$fp]); | |
| unset($this->_query[$fp]); | |
| unset($this->_fileId[$fp]); | |
| unset($this->_ref[$fp]); | |
| unset($this->_firstBlankMemoBlockRef[$fp]); | |
| } else { | |
| $this->sendError(E_WARNING, "$table is not a valid table in $this->Db", __LINE__); | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Clears cached information on tables aswell as filepoints ($fp) | |
| * | |
| */ | |
| function cleanUp() { | |
| $this->fp = array(); | |
| $this->_header = array(); | |
| $this->_query = array(); | |
| $this->_fileId = array(); | |
| $this->_ref = array(); | |
| $this->_firstBlankMemoBlockRef = array(); | |
| } | |
| /** | |
| * Determines if the table exists in the defined database | |
| * | |
| * @param string $table | |
| * @return bool | |
| */ | |
| function isTable($table) { | |
| $this->check(); | |
| if(substr($table, 0, (strlen($this->Db) -4)) != substr($this->Db, 0, -4)) $table = substr($this->Db, 0, -4).'_'.$table; | |
| foreach($this->Tables as $ta) { | |
| if($ta == $table) return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * @param $table | |
| * @param $fields | |
| * @param string $block_length | |
| * @return bool | |
| */ | |
| function createTable($table, $fields, $block_length="100") { | |
| $this->check(); | |
| $block_length = $block_length + 8; //To store the next fp of every block + end of text chr | |
| if(substr($table, 0, (strlen($this->Db) -4) != substr($this->Db, 0, -4))) $table = substr($this->Db, 0, -4).'_'.$table; | |
| if(!is_array($fields)) { | |
| $this->sendError(E_USER_ERROR, "\$fields must be an array", __LINE__); | |
| return false; | |
| } else { | |
| // check if table already exists | |
| if(file_exists($this->workingDir.$table) || in_array($table, $this->Tables)) { | |
| $this->sendError(E_WARNING, "Table ($this->workingDir$table) already exists", __LINE__); | |
| return false; | |
| } | |
| // start building the header | |
| $h_fields = array(); | |
| $h_recLen = 0; | |
| for($i=0;$i<count($fields);$i++) { | |
| if($fields[$i][1] != "string" && $fields[$i][1] != "number" && $fields[$i][1] != "memo" && $fields[$i][1] != "id") { | |
| $this->sendError(E_USER_ERROR, "Field type must be either string, number, memo, or id.", __LINE__); | |
| return false; | |
| } | |
| if($fields[$i][1] == "id" || $fields[$i][1] == 'memo') $fields[$i][2] = "7"; | |
| //if($fields[$i][1] == "memo") $fields[$i][2] = "10"; //memo functions only use 7 chars | |
| $h_fields[] = ($i + 1).chr(31).$fields[$i][1].chr(31).$fields[$i][2].chr(31).$fields[$i][0]; | |
| $h_recLen += $fields[$i][2]; | |
| } | |
| $h_fields = implode(chr(29), $h_fields); | |
| $h_cid = "0 "; | |
| $h_lastBlank = "-1 "; | |
| $header = strlen($h_fields).chr(28).$h_fields.$h_recLen.chr(28).$h_cid.chr(28).$h_lastBlank.chr(28).$block_length.chr(28); | |
| // write the table header | |
| $f = fopen($this->workingDir.$table.'.ta', 'wb'); | |
| fwrite($f, $header); | |
| fclose($f); | |
| $f = fopen($this->workingDir.$table.'.memo', 'wb'); | |
| fwrite($f, '-1'.str_repeat(' ', 5).str_repeat(' ', ($block_length - 7))); //This is the memo header aswell as block #0. | |
| fclose($f); | |
| $f = fopen($this->workingDir.$table.'.ref', 'wb'); | |
| fwrite($f, ""); | |
| fclose($f); | |
| // write the table in the database | |
| $this->Tables[] = $table; | |
| $f = fopen($this->workingDir.$this->Db, 'wb'); | |
| fwrite($f, implode("\n", $this->Tables)); | |
| fclose($f); | |
| return true; | |
| } | |
| } | |
| /** | |
| * Adds another field to a table | |
| * | |
| * @param string $fp | |
| * @param array $field | |
| * @return bool | |
| */ | |
| function addField($fp, $field) { | |
| $this->check($fp); | |
| $header = array(); | |
| $this->readHeader($fp, $header); | |
| $cHeader = count($header) - 8; | |
| for($i=1;$i<=$cHeader;$i++) { | |
| if($header[$i]["fName"] == $field[0]) { | |
| $this->sendError(E_USER_WARNING, "Field already exists, aborting...", __LINE__); | |
| return false; | |
| } | |
| } | |
| $cHeader += 1; // for the new field | |
| //name, type, size | |
| if($field[1] != "string" && $field[1] != "number" && $field[1] != "memo" && $field[1] != "id") { | |
| $this->sendError(E_USER_ERROR, "Field type must be either string, number, memo, or id.", __LINE__); | |
| return false; | |
| } | |
| if($field[1] == "id" || $field[1] == "memo") $field[2] = "7"; | |
| //if($field[1] == "memo") $field[2] = "10"; | |
| $header[$cHeader]["fName"] = $field[0]; | |
| $header[$cHeader]["fType"] = $field[1]; | |
| $header[$cHeader]["fLength"] = $field[2]; | |
| // start building the header | |
| $h_fields = array(); | |
| $h_recLen = 0; | |
| for($i=1;$i<=$cHeader;$i++) { | |
| $h_fields[] = $i.chr(31).$header[$i]["fType"].chr(31).$header[$i]["fLength"].chr(31).$header[$i]["fName"]; | |
| $h_recLen += $header[$i]["fLength"]; | |
| } | |
| $h_fields = implode(chr(29), $h_fields); | |
| $h_cid = $header["curId"].str_repeat(" ", 7 - strlen($header["curId"])); | |
| $h_lastBlank = $header["lastBlank"].str_repeat(" ", 7 - strlen($header["lastBlank"])); | |
| $h_header = strlen($h_fields).chr(28).$h_fields.$h_recLen.chr(28).$h_cid.chr(28).$h_lastBlank.chr(28).$header["blockLength"].chr(28); | |
| // rebuild the table with the new field | |
| $eRecSize = filesize($this->fp[$fp].'.ta') - $header["recPos"]; | |
| $eRecCount = $eRecSize / $header["recLen"]; | |
| $f = fopen($this->fp[$fp].'.ta', 'rb'); | |
| // open up temp file for writing the buffers | |
| $newFnam = $this->workingDir."tmpF_".md5(uniqid(rand())); | |
| $newF = fopen($newFnam, "wb"); | |
| // write the header to the new table file | |
| fwrite($newF, $h_header); | |
| $value = ""; | |
| if($header[$cHeader]["fType"] == "id") { | |
| $ref_data = $this->get_ref_data($fp); | |
| $ref_arr = explode(chr(31), $ref_data); | |
| $rfArr = array(); | |
| foreach($ref_arr as $ref_tmp) { | |
| $ref_tmp = explode(':', $ref_tmp); | |
| $rfArr[$ref_tmp[1]-1] = $ref_tmp[0]; | |
| } | |
| } | |
| $cid = 1; | |
| while(!feof($f)) { | |
| if($cid > $eRecCount) break; | |
| $value = ""; | |
| fseek($f, $this->bytesToSeek($fp, $header, $cid)); | |
| if($header[$cHeader]["fType"] == "id" && isset($rfArr[$cid])) $value = $rfArr[$cid]; | |
| // write the record with the new field | |
| fwrite($newF, fread($f, $header["recLen"]).$value.str_repeat(" ", $header[$cHeader]["fLength"] - strlen($value))); | |
| $cid++; | |
| } | |
| fclose($newF); | |
| fclose($f); | |
| unlink($this->fp[$fp].'.ta'); | |
| rename($newFnam, $this->fp[$fp].'.ta'); | |
| if(isset($this->_header[$fp])) unset($this->_header[$fp]); | |
| return true; | |
| } | |
| /** | |
| * Edits a field's parameters | |
| * | |
| * @param string $fp | |
| * @param string $oldfield | |
| * @param array $field | |
| * @return bool | |
| */ | |
| function editField($fp, $oldfield, $field=array()) { | |
| $this->check($fp); | |
| $header = array(); | |
| $this->readHeader($fp, $header); | |
| $cHeader = count($header) - 8; | |
| $foundField = false; | |
| for($i=1;$i<=$cHeader;$i++) { | |
| if($header[$i]["fName"] == $oldfield) { | |
| $foundField = true; | |
| $fieldId = $i; | |
| break; | |
| } | |
| } | |
| if(!$foundField) { | |
| $this->sendError(E_USER_WARNING, "Cannot remove '$oldfield' field, it does not exist.", __LINE__); | |
| return false; | |
| } | |
| //name, type, size | |
| if($field[1] != "string" && $field[1] != "number" && $field[1] != "memo" && $field[1] != "id") { | |
| $this->sendError(E_USER_ERROR, "New field type must be either string, number, memo, or id.", __LINE__); | |
| return false; | |
| } | |
| if($field[1] == "id" || $field[1] == 'memo') $field[2] = "7"; | |
| //if($field[1] == "memo") $field[2] = "10"; //memo functions only use 7 chars | |
| $oldlength = $header[$fieldId]["fLength"]; | |
| $oldType = $header[$fieldId]["fType"]; | |
| $header[$fieldId]["fName"] = $field[0]; | |
| $header[$fieldId]["fType"] = $field[1]; | |
| $header[$fieldId]["fLength"] = $field[2]; | |
| // start building the header | |
| $h_fields = array(); | |
| $h_recLen = 0; | |
| for($i=1;$i<=$cHeader;$i++) { | |
| $h_fields[] = $i.chr(31).$header[$i]["fType"].chr(31).$header[$i]["fLength"].chr(31).$header[$i]["fName"]; | |
| $h_recLen += $header[$i]["fLength"]; | |
| } | |
| $h_fields = implode(chr(29), $h_fields); | |
| $h_cid = $header["curId"].str_repeat(" ", 7 - strlen($header["curId"])); | |
| $h_lastBlank = $header["lastBlank"].str_repeat(" ", 7 - strlen($header["lastBlank"])); | |
| $h_header = strlen($h_fields).chr(28).$h_fields.$h_recLen.chr(28).$h_cid.chr(28).$h_lastBlank.chr(28).$header["blockLength"].chr(28); | |
| // rebuild the table with the new field | |
| $eRecSize = filesize($this->fp[$fp].'.ta') - $header["recPos"]; | |
| $eRecCount = $eRecSize / $header["recLen"]; | |
| $f = fopen($this->fp[$fp].'.ta', 'rb'); | |
| // open up temp file for writing the buffers | |
| $newFnam = $this->workingDir."tmpF_".md5(uniqid(rand())); | |
| $newF = fopen($newFnam, "wb"); | |
| // write the header to the new table file | |
| fwrite($newF, $h_header); | |
| $cid = 1; | |
| while(!feof($f)) { | |
| if($cid > $eRecCount) break; | |
| fseek($f, $this->bytesToSeek($fp, $header, $cid)); | |
| if(fread($f, 1) == chr(24)) { | |
| fwrite($newF, chr(24).fread($f, 7).str_repeat(' ', $h_recLen - 8)); | |
| $cid++; | |
| continue; | |
| } else fseek($f, -1, SEEK_CUR); | |
| for($i=1;$i<=$cHeader;$i++) { | |
| if($i != $fieldId) { | |
| fwrite($newF, fread($f, $header[$i]["fLength"])); | |
| } else { | |
| $value = rtrim(fread($f, $oldlength)); | |
| if($oldType == "memo" && $header[$i]["fType"] != "memo") { | |
| $memo = $value; | |
| $value = $this->readMemo($fp, $memo, $header); | |
| $this->deleteMemo($fp, $memo, $header); | |
| unset($memo); | |
| } | |
| $field = $value; | |
| if($header[$i]["fType"] == "memo") { | |
| $this->writeMemo($fp, $field, $header); | |
| } elseif($header[$i]["fType"] == "string") { | |
| $field = substr($field, 0, $header[$i]["fLength"]); | |
| } elseif($header[$i]["fType"] == "number") { | |
| $field = preg_replace("/[^0-9.-]/i", "", $field); | |
| $field = substr($field, 0, $header[$i]["fLength"]); | |
| } elseif($header[$i]["fType"] == "id") { | |
| $field = $header["curId"]; | |
| } | |
| $field = $field.str_repeat(" ", $header[$i]["fLength"] - strlen($field)); | |
| fwrite($newF, $field); | |
| } | |
| } | |
| $cid++; | |
| } | |
| fclose($newF); | |
| fclose($f); | |
| unlink($this->fp[$fp].'.ta'); | |
| rename($newFnam, $this->fp[$fp].'.ta'); | |
| $this->_header[$fp] = $header; | |
| return true; | |
| } | |
| /** | |
| * Removes a column from the table | |
| * | |
| * @param string $fp | |
| * @param string $field | |
| * @return bool | |
| */ | |
| function removeField($fp, $field) { | |
| $this->check($fp); | |
| $header = array(); | |
| $this->readHeader($fp, $header); | |
| $cHeader = count($header) - 8; | |
| $foundField = false; | |
| for($i=1;$i<=$cHeader;$i++) { | |
| if($header[$i]["fName"] == $field) { | |
| $foundField = true; | |
| break; | |
| } | |
| } | |
| if(!$foundField) { | |
| $this->sendError(E_USER_WARNING, "Cannot remove '$field' field, it does not exist.", __LINE__); | |
| return false; | |
| } | |
| // start building the header | |
| $h_fields = array(); | |
| $h_recLen = 0; | |
| for($i=1,$j=1;$i<=$cHeader;$i++) { | |
| if($header[$i]["fName"] != $field) { | |
| $h_fields[] = $j++.chr(31).$header[$i]["fType"].chr(31).$header[$i]["fLength"].chr(31).$header[$i]["fName"]; | |
| $h_recLen += $header[$i]["fLength"]; | |
| } | |
| } | |
| $h_fields = implode(chr(29), $h_fields); | |
| $h_cid = $header["curId"].str_repeat(" ", 7 - strlen($header["curId"])); | |
| $h_lastBlank = $header["lastBlank"].str_repeat(" ", 7 - strlen($header["lastBlank"])); | |
| $h_header = strlen($h_fields).chr(28).$h_fields.$h_recLen.chr(28).$h_cid.chr(28).$h_lastBlank.chr(28).$header["blockLength"].chr(28); | |
| // rebuild the table with the field removed | |
| $eRecSize = filesize($this->fp[$fp].'.ta') - $header["recPos"]; | |
| $eRecCount = $eRecSize / $header["recLen"]; | |
| $f = fopen($this->fp[$fp].'.ta', 'rb'); | |
| // open up temp file for writing the buffers | |
| $newFnam = $this->workingDir."tmpF_".md5(uniqid(rand())); | |
| $newF = fopen($newFnam, "wb"); | |
| // write the header to the new table file | |
| fwrite($newF, $h_header); | |
| $cid = 1; | |
| while(!feof($f)) { | |
| if($cid > $eRecCount) break; | |
| fseek($f, $this->bytesToSeek($fp, $header, $cid)); | |
| if(fread($f, 1) == chr(24)) { | |
| fwrite($newF, chr(24).fread($f, 7).str_repeat(' ', $h_recLen - 8)); | |
| $cid++; | |
| continue; | |
| } else fseek($f, -1, SEEK_CUR); | |
| for($i=1;$i<=$cHeader;$i++) { | |
| if($header[$i]["fName"] != $field) { | |
| fwrite($newF, fread($f, $header[$i]["fLength"])); | |
| } elseif($header[$i]["fType"] != "memo") fseek($f, $header[$i]["fLength"], SEEK_CUR); | |
| else $this->deleteMemo($fp, trim(fread($f, $header[$i]["fLength"])), $header); | |
| } | |
| $cid++; | |
| } | |
| fclose($newF); | |
| fclose($f); | |
| unlink($this->fp[$fp].'.ta'); | |
| rename($newFnam, $this->fp[$fp].'.ta'); | |
| if(isset($this->_header[$fp])) unset($this->_header[$fp]); | |
| return true; | |
| } | |
| /** | |
| * Edits a record. | |
| * | |
| * @param string $fp | |
| * @param int $id | |
| * @param array $recArr | |
| * @return bool | |
| */ | |
| function edit($fp, $id, $recArr) { | |
| $this->check($fp); | |
| $header = array(); | |
| if(FALSE === ($fileId = $this->fileIdById($fp, $id))) { | |
| $this->sendError(E_WARNING, "Unable to execute edit(). Unable to retrieve fileID", __LINE__); | |
| return false; | |
| } | |
| $this->readHeader($fp, $header); | |
| $f = fopen($this->fp[$fp].'.ta', 'r+b'); | |
| //fseek($f, $this->bytesToSeek($fp, $header, $fileId)); | |
| $offset = $this->bytesToSeek($fp, $header, $fileId); | |
| //edit the record | |
| $cHeader = count($header) - 8; | |
| for($i=1;$i<=$cHeader;$i++) { | |
| fseek($f, $offset); | |
| $field = ""; | |
| if(isset($recArr[$header[$i]["fName"]])) { | |
| $field = $recArr[$header[$i]["fName"]]; | |
| if($header[$i]["fType"] == "memo") { | |
| $this->deleteMemo($fp, trim(fread($f, $header[$i]["fLength"])), $header); | |
| fseek($f, $offset); | |
| $field = $this->writeMemo($fp, $field, $header); | |
| } elseif($header[$i]["fType"] == "string") { | |
| //$field = preg_replace("/[^a-z0-9 ,.:?/#]/i", "", $field); | |
| $field = substr($field, 0, $header[$i]["fLength"]); | |
| } elseif($header[$i]["fType"] == "number") { | |
| $field = preg_replace("/[^0-9.-]/i", "", $field); | |
| $field = substr($field, 0, $header[$i]["fLength"]); | |
| } elseif($header[$i]["fType"] == "id") { | |
| $theId = trim(fread($f, $header[$i]["fLength"])); | |
| fseek($f, $offset); | |
| $field = $theId; | |
| } | |
| $field = $field.str_repeat(" ", $header[$i]["fLength"] - strlen($field)); | |
| fwrite($f, $field); | |
| } | |
| $offset += $header[$i]["fLength"]; | |
| } | |
| fclose($f); | |
| if(isset($this->_query[$fp])) unset($this->_query[$fp]); | |
| return true; | |
| } | |
| /** | |
| * Deletes a record. | |
| * | |
| * @param string $fp | |
| * @param int $id | |
| * @return bool | |
| */ | |
| function delete($fp, $id) { | |
| $this->check($fp); | |
| if(FALSE === ($fileId = $this->fileIdById($fp, $id))) { | |
| $this->sendError(E_WARNING, "Unable to execute delete(). Unable to retrieve fileID", __LINE__); | |
| return false; | |
| } | |
| $header = array(); | |
| $this->readHeader($fp, $header); | |
| $f = fopen($this->fp[$fp].'.ta', 'r+b'); | |
| fseek($f, $this->bytesToSeek($fp, $header, $fileId)); | |
| //Gather memo fps | |
| $offset = 0; | |
| for($i=1, $hmax=(count($header)-8);$i<=$hmax;$i++) { | |
| if($header[$i]["fType"] == "memo") { | |
| fseek($f, $offset, SEEK_CUR); | |
| $this->deleteMemo($fp, trim(fread($f, $header[$i]["fLength"])), $header); | |
| $offset = 0; | |
| } else $offset += $header[$i]["fLength"]; | |
| } | |
| //erase the record | |
| fseek($f, $this->bytesToSeek($fp, $header, $fileId)); | |
| fwrite($f, chr(24).$header['lastBlank'].str_repeat(" ", ($header["recLen"] - (strlen($header['lastBlank']) + 1)))); | |
| //fwrite($f, chr(24).$header['lastBlank']); | |
| $this->_header[$fp]['lastBlank'] = $fileId; | |
| fseek($f, $header['blankPos']); | |
| $fileId = substr($fileId, 0, 7); | |
| fwrite($f, $fileId.str_repeat(' ', 7 - strlen($fileId))); | |
| fclose($f); | |
| $ref_data = chr(31).$this->get_ref_data($fp); | |
| $ref_data = substr(str_replace(chr(31).$id.':'.$fileId.chr(31), chr(31), $ref_data), 1); | |
| $f = fopen($this->fp[$fp].'.ref', 'wb'); | |
| $this->_ref[$fp] = $ref_data; | |
| fwrite($f, $ref_data); | |
| fclose($f); | |
| if(isset($this->_query[$fp])) unset($this->_query[$fp]); | |
| return true; | |
| } | |
| /** | |
| * Obsolete function, passes arguments to tdb::sort() | |
| * | |
| * @param string $fp | |
| * @param string $fieldName | |
| * @param string $direction | |
| * @return bool | |
| */ | |
| function sortAndBuild($fp, $fieldName, $direction="ASC") { | |
| $this->sendError(E_USER_NOTICE, 'tdb::sortAndBuild() function obsolete. Update scripts accordingly', __LINE__); | |
| return $this->sort($fp, $fieldName, $direction); | |
| } | |
| //new sys not avail. | |
| /** | |
| * Sorts records in a specific order based on a field | |
| * | |
| * @param string $fp | |
| * @param string $fieldName | |
| * @param string $direction[Optional] | |
| * @return bool | |
| */ | |
| function sort($fp, $fieldName, $direction="ASC") { | |
| $this->check($fp); | |
| $header = array(); | |
| $this->readHeader($fp, $header); | |
| $sortFieldIndex = 0; | |
| $sortType = ""; //numeric or string | |
| //first make sure this fieldname exists | |
| $exists = false; | |
| for($i=1;$i<=count($header)-8;$i++) { | |
| if($header[$i]["fName"] == $fieldName) { | |
| $sortFieldIndex = $i; | |
| $exists = true; | |
| break; | |
| } | |
| } | |
| unset($i); | |
| if($exists) { | |
| if($header[$sortFieldIndex]["fType"] == "number" || $header[$sortFieldIndex]["fType"] == "id") { | |
| $sortType = SORT_NUMERIC; | |
| } elseif($header[$sortFieldIndex]["fType"] == "string") { | |
| $sortType = SORT_STRING; | |
| } else { | |
| $this->sendError(E_USER_WARNING, "You can only sort the following field types: number, id, and string", __LINE__); | |
| return false; | |
| } | |
| } else { | |
| $this->sendError(E_USER_WARNING, "The field '$fieldName' does not exist", __LINE__); | |
| return false; | |
| } | |
| //build the sorting array | |
| $arrById = array(); | |
| $row = $this->listRec($fp, 1); | |
| for($i=0;$i<count($row);$i++) { | |
| $arrById[$row[$i][$fieldName]] = $row[$i]['id']; | |
| } | |
| //sort | |
| if($direction == "ASC") ksort($arrById, $sortType); | |
| elseif($direction == "DESC") krsort($arrById, $sortType); | |
| reset($arrById); | |
| // grab all the references | |
| $ref_data = $this->get_ref_data($fp); | |
| $refArrOld = explode(chr(31), substr($ref_data, 0, -1)); | |
| $refArr = array(); | |
| foreach($refArrOld as $refArrTmp) { | |
| $refTmp = explode(':', $refArrTmp); | |
| $refArr[$refTmp[0]] = $refArrTmp; | |
| } | |
| //Rebuild the references | |
| $new_ref = ''; | |
| /*for($i=0;$i<count($row);$i++) { | |
| $arrInfo = each($arrById); | |
| $id = $arrInfo["key"] + 1; | |
| $new_ref .= $refArr[$id].":".($i + 1).chr(31); | |
| }*/ | |
| foreach($arrById as $arrInfo) { | |
| $new_ref .= $refArr[$arrInfo].chr(31); | |
| } | |
| $this->_ref[$fp] = $new_ref; | |
| $f = fopen($this->fp[$fp].'.ref', 'wb'); | |
| fwrite($f, $new_ref); | |
| fclose($f); | |
| return true; | |
| } | |
| /** | |
| * Obsolete function | |
| * | |
| */ | |
| function reBuild() { | |
| $this->sendError(E_USER_NOTICE, 'tdb::reBuild() function obsolete. Update scripts accordingly', __LINE__); | |
| } | |
| /** | |
| * Add a record to table $fp, using values from $recArr | |
| * | |
| * @param string $fp | |
| * @param array $recArr | |
| * @return bool false on fail, int ID on success | |
| */ | |
| public function add($fp, $recArr) { | |
| $this->check($fp); | |
| $header = array(); | |
| $this->readHeader($fp, $header); | |
| $header["curId"]++; | |
| //update the cache curId as well | |
| $this->_header[$fp]["curId"]++; | |
| if($header["curId"] > 5000000) { | |
| $this->sendError(E_WARNING, "Maximum records reached (5,000,000) aborting...", __LINE__); | |
| return false; | |
| } | |
| $record = ""; | |
| for($i=1, $cHeader = (count($header) - 8);$i<=$cHeader;$i++) { | |
| if(isset($recArr[$header[$i]["fName"]])) $field = $recArr[$header[$i]["fName"]]; | |
| else $field = ""; | |
| if($header[$i]["fType"] == "memo") { | |
| $field = $this->writeMemo($fp, $field, $header); | |
| } elseif($header[$i]["fType"] == "string") { | |
| //$field = preg_replace("/[^a-z0-9 ,.:?/#]/i", "", $field); | |
| //$field = substr($field, 0, $header[$i]["fLength"]); | |
| } elseif($header[$i]["fType"] == "number") { | |
| $field = preg_replace("/[^0-9.-]/i", "", $field); | |
| //$field = substr($field, 0, $header[$i]["fLength"]); | |
| } elseif($header[$i]["fType"] == "id") { | |
| $field = $header["curId"]; | |
| } | |
| $field = substr($field, 0, $header[$i]["fLength"]); | |
| $field = $field.str_repeat(" ", $header[$i]["fLength"] - strlen($field)); | |
| $record[] = $field; | |
| } | |
| $record = implode("", $record); | |
| if(strlen($record) != $header["recLen"]) { | |
| $this->sendError(E_ERROR, "There was an error adding the record.", __LINE__); | |
| return false; | |
| } | |
| if($header['lastBlank'] != -1) { | |
| $fileId = $header['lastBlank']; | |
| } elseif(isset($this->_fileId[$fp])) { | |
| $this->_fileId[$fp]++; | |
| $fileId = $this->_fileId[$fp]; | |
| } else { | |
| $fileId = (filesize($this->fp[$fp].'.ta') - $header["recPos"]) / $header["recLen"] + 1; | |
| $this->_fileId[$fp] = $fileId; | |
| } | |
| $f = fopen($this->fp[$fp].'.ref', 'ab'); | |
| flock($f, 2); | |
| fwrite($f, $header["curId"].":".$fileId.chr(31)); | |
| flock($f, 3); | |
| if(isset($this->_ref[$fp])) $this->_ref[$fp] .= $header["curId"].":".$fileId.chr(31); | |
| fclose($f); | |
| $f = fopen($this->fp[$fp].'.ta', 'r+b'); | |
| flock($f, 2); | |
| fseek($f, $header["idPos"]); | |
| fwrite($f, $header["curId"]); | |
| if($header['lastBlank'] == -1) fseek($f, 0, SEEK_END); | |
| else { | |
| fseek($f, $this->bytesToSeek($fp, $header, $fileId)); | |
| $nextBlank = ltrim(fread($f, 8), chr(24)); | |
| fseek($f, $header['blankPos']); | |
| $nextBlank = substr($nextBlank, 0, 7); | |
| fwrite($f, $nextBlank); | |
| $this->_header[$fp]['lastBlank'] = (int)trim($nextBlank); | |
| fseek($f, $this->bytesToSeek($fp, $header, $fileId)); | |
| } | |
| fwrite($f, $record); | |
| flock($f, 3); | |
| fclose($f); | |
| if(!in_array($this->fp[$fp], $this->editedTable)) $this->editedTable[] = $this->fp[$fp]; | |
| return $header["curId"]; | |
| } | |
| /** | |
| * Finds the physical address of a record based on its ID | |
| * | |
| * @param string $fp | |
| * @param int $id | |
| * @return int fileID on success, bool false on fail | |
| */ | |
| function fileIdById($fp, $id) { | |
| $this->check($fp); | |
| $ref_data = $this->get_ref_data($fp); | |
| $ref_data = chr(31).$ref_data; | |
| if(FALSE !== ($pos1 = strpos($ref_data, chr(31).$id.':'))) { | |
| $pos1 = $pos1 + strlen(chr(31).$id.':'); | |
| $length = strpos($ref_data, chr(31), $pos1) - $pos1; | |
| $fileId = substr($ref_data, $pos1, $length); | |
| if((int)$fileId <= 0) { | |
| $this->sendError(E_ERROR, "fileIdById() found a nonpositive integer fileId(\"$fileId\") based on ID(\"$id\") in ".$this->fp[$fp].". Dumping reference file:".$ref_data.". \$pos1:$pos1. \$length:$length.", __LINE__); | |
| return false; | |
| } | |
| return (int)$fileId; | |
| //return substr($ref_data, $pos1, $length); | |
| } | |
| return false; | |
| } | |
| /** | |
| * Retrieves the ref information of a table based on the $fp | |
| * | |
| * @param string $fp | |
| * @return string | |
| */ | |
| function get_ref_data($fp) { | |
| $this->check($fp); | |
| if(!isset($this->_ref[$fp])) $this->_ref[$fp] = file_get_contents($this->fp[$fp].'.ref'); | |
| return $this->_ref[$fp]; | |
| } | |
| /** | |
| * @param $fp | |
| * @param $id | |
| * @param array $fields | |
| * @return array|bool | |
| */ | |
| function get($fp, $id, $fields=array('*')) { | |
| if(FALSE === ($fileId = $this->fileIdById($fp, $id))) { | |
| //$this->sendError(E_WARNING, "Unable to execute get(). Unable to retrieve fileID", __LINE__); | |
| return false; | |
| } | |
| $reqFields = array(); | |
| if($fields[0] == "*") { | |
| $reqFields = array("*"); | |
| } else { | |
| foreach($fields as $incField) { | |
| $reqFields[$incField] = true; | |
| } | |
| } | |
| $header = array(); | |
| $this->readHeader($fp, $header); | |
| $f = fopen($this->fp[$fp].'.ta', 'rb'); | |
| fseek($f, $this->bytesToSeek($fp, $header, $fileId)); | |
| $buffer = fread($f, $header["recLen"]); | |
| fclose($f); | |
| if(ord($buffer{0}) != 28) { | |
| //return $this->parseRecord($fp, $buffer, $header); //new method | |
| return array($this->parseRecord($fp, $buffer, $header, $reqFields)); //old method | |
| } | |
| $this->sendError(E_USER_NOTICE, 'Unable to parse record with recordId of '.$id.' at fileId:'.$fileId.' in get() at '.$this->fp[$fp], __LINE__); | |
| return false; | |
| } | |
| /** | |
| * Retrieves records in sequential order | |
| * | |
| * @param string $fp | |
| * @param int $start | |
| * @param int $howmany | |
| * @param array $fields | |
| * @return array records on success, bool false on fail | |
| */ | |
| function listRec($fp, $start, $howmany=-1, $fields=array("*")) { | |
| $this->check($fp); | |
| $return = array(); | |
| $pos2 = 0; | |
| $pos1 = 0; | |
| $ref_data = $this->get_ref_data($fp); | |
| for($i=1;$i<$start;$i++) { //skipping 1st rec, b/c default: $pos1 = 0; | |
| $pos1 = strpos($ref_data, chr(31), $pos1) + 1; | |
| } | |
| if($pos1 > strlen($ref_data)) { | |
| $this->sendError(E_ERROR, 'Searching for records past the end of file in listRec('.$fp.')', __LINE__); | |
| return false; | |
| } | |
| $reqFields = array(); | |
| if($fields[0] == "*") { | |
| $reqFields = array("*"); | |
| } else { | |
| foreach($fields as $incField) { | |
| $reqFields[$incField] = true; | |
| } | |
| } | |
| $header = array(); | |
| $this->readHeader($fp, $header); | |
| $f = fopen($this->fp[$fp].'.ta', 'rb'); | |
| while(FALSE !== ($pos2 = strpos($ref_data, chr(31), $pos1))) { | |
| if($howmany == 0) break; | |
| $fileId = substr(strstr(substr($ref_data, $pos1, ($pos2 - $pos1)), ':'), 1); | |
| $pos1 = $pos2 + 1; | |
| if($fileId === FALSE) continue; | |
| fseek($f, $this->bytesToSeek($fp, $header, $fileId)); | |
| $buffer = fread($f, $header["recLen"]); | |
| if(ord($buffer{0}) != 28) { | |
| $return[] = $this->parseRecord($fp, $buffer, $header, $reqFields); | |
| $howmany--; | |
| } | |
| else $this->sendError(E_PARSE, 'Unable to parse record at fileId:'.$fileId.' in listRec() at '.$this->fp[$fp], __LINE__); | |
| } | |
| unset($buffer); | |
| fclose($f); | |
| if(empty($return)) return false; | |
| return $return; | |
| } | |
| /** | |
| * Internal function used to parse a querystring for the query() function | |
| * | |
| * @param string $query | |
| * @return bool false on fail, array search terms on success | |
| */ | |
| function parseQueryString($query) { | |
| if(trim($query) == "") return FALSE; | |
| //first thing is first, we need to find out if we are using =,?,<,>,! | |
| $pos1 = strpos($query, "="); | |
| $pos2 = strpos($query, "?"); | |
| $pos3 = strpos($query, ">"); | |
| $pos4 = strpos($query, "<"); | |
| $pos5 = strpos($query, "!"); | |
| $pointer = 0; | |
| $result = array(); | |
| $lenquery = strlen($query); | |
| $i = 0; | |
| while($pos1 !== FALSE || $pos2 !== FALSE || $pos3 !== FALSE || $pos4 !== FALSE || $pos5 !== FALSE) { | |
| //find out which one came first | |
| $pos = FALSE; | |
| if($pos1 !== FALSE) { | |
| $pos = $pos1; | |
| $type = "="; | |
| } | |
| if($pos2 !== FALSE && $pos2 < $pos || $pos2 !== FALSE && $pos === FALSE) { | |
| $pos = $pos2; | |
| $type = "?"; | |
| } | |
| if($pos3 !== FALSE && $pos3 < $pos || $pos3 !== FALSE && $pos === FALSE) { | |
| $pos = $pos3; | |
| $type = ">"; | |
| } | |
| if($pos4 !== FALSE && $pos4 < $pos || $pos4 !== FALSE && $pos === FALSE) { | |
| $pos = $pos4; | |
| $type = "<"; | |
| } | |
| if($pos5 !== FALSE && $pos5 < $pos || $pos5 !== FALSE && $pos === FALSE) { | |
| $pos = $pos5; | |
| $type = "!"; | |
| } | |
| $field = substr($query, $pointer, ($pos - $pointer)); | |
| $pointer = $pos + 1; | |
| //get the search text | |
| if(substr($query, $pointer, 1) != "'") $this->sendError(E_USER_ERROR, "Missing quote (') in query syntax", __LINE__); | |
| $pointer += 1; | |
| $text_pos = strpos($query, "'", $pointer); | |
| if($text_pos === FALSE) $this->sendError(E_USER_ERROR, "Invalid query syntax, missing ending quote (') in search term", __LINE__); | |
| $text = substr($query, $pointer, ($text_pos - $pointer)); | |
| $pointer = $text_pos + 1; | |
| if($pointer >= $lenquery) $b = "&&"; | |
| else { | |
| $b = substr($query, $pointer, 2); | |
| $pointer += 2; | |
| } | |
| if($b != "&&" && $b != "||") $this->sendError(E_USER_ERROR, "Invalid query syntax, missing '&&' or '||' between search terms", __LINE__); | |
| $result[$i][] = array("field" => $field, "type" => $type, "value" => $text); | |
| if($b == "||") { | |
| //create new list | |
| $i += 1; | |
| } | |
| //get our next term... | |
| $pos1 = strpos($query, "=", $pointer); | |
| $pos2 = strpos($query, "?", $pointer); | |
| $pos3 = strpos($query, ">", $pointer); | |
| $pos4 = strpos($query, "<", $pointer); | |
| $pos5 = strpos($query, "!", $pointer); | |
| } | |
| if($result !== FALSE) return $result; | |
| else return FALSE; | |
| } | |
| /** | |
| * Queries a table based on the query string's parameters. | |
| * | |
| * @param string $fp | |
| * @param string $query | |
| * @param int $start[optional] | |
| * @param int $howmany[optional] | |
| * @return bool false on fail, array records on success | |
| */ | |
| function query($fp, $query, $start=1, $howmany=-1, $fields=array("*")) { | |
| $this->check($fp); | |
| $tmpfields = implode(",", $fields); | |
| if(!empty($this->_query[$fp])) { | |
| foreach($this->_query[$fp] as $cached_query) { | |
| if( | |
| $cached_query["query_string"] == $query && | |
| $cached_query["start"] == $start && | |
| $cached_query["howmany"] == $howmany && | |
| $cached_query["fields"] == $tmpfields) | |
| return $cached_query["result"]; | |
| } | |
| } | |
| $original_start = $start; | |
| $original_howmany = $howmany; | |
| $original_query = $query; | |
| $original_fields = $tmpfields; | |
| $string = $query; | |
| unset($query); | |
| $query_array = $this->parseQueryString($string); | |
| if(empty($query_array)) $this->sendError(E_USER_ERROR, "Invalid query syntax or empty query string", __LINE__); | |
| $header = array(); | |
| $this->readHeader($fp, $header); | |
| $reqFields = array(); | |
| if($fields[0] == "*") { | |
| //$getAllFields = true; | |
| $reqFields = array("*"); | |
| } else { | |
| //$getAllFields = false; | |
| foreach($fields as $incField) { | |
| $reqFields[$incField] = true; | |
| } | |
| } | |
| $fieldOffsets[$header[1]["fName"]]["offset"] = 0; | |
| $fieldOffsets[$header[1]["fName"]]["length"] = $header[1]["fLength"]; | |
| $fieldOffsets[$header[1]["fName"]]["type"] = $header[1]["fType"]; | |
| $total = 0; | |
| //if($getAllFields) $reqFields[$header[1]["fName"]] = true; | |
| for($i=2;$i<count($header)-7;$i++) { | |
| $fieldOffsets[$header[$i]["fName"]]["offset"] = $total + $header[$i-1]["fLength"]; | |
| $fieldOffsets[$header[$i]["fName"]]["length"] = $header[$i]["fLength"]; | |
| $fieldOffsets[$header[$i]["fName"]]["type"] = $header[$i]["fType"]; | |
| $total += $header[$i-1]["fLength"]; | |
| //if($getAllFields) $reqFields[$header[1]["fName"]] = true; | |
| } | |
| $return = array(); | |
| $start_c = 1; | |
| $ref_pos1 = 0; | |
| $ref_data = $this->get_ref_data($fp); | |
| $ref_pos2 = 0; | |
| $f = fopen($this->fp[$fp].'.ta', 'rb'); | |
| while(FALSE !== ($ref_pos2 = strpos($ref_data, chr(31), $ref_pos1))) { | |
| if(FALSE === ($fileId = strstr(substr($ref_data, $ref_pos1, ($ref_pos2 - $ref_pos1)), ':'))) { | |
| $ref_pos1 = $ref_pos2 + 1; | |
| continue; | |
| } | |
| $fileId = (int) substr($fileId, 1); | |
| $ref_pos1 = $ref_pos2 + 1; | |
| if($howmany == 0) break; | |
| //add OR functionality BEGIN | |
| $foundMatch = false; | |
| foreach($query_array as $query) { | |
| $pass = true; | |
| for($i=0;$i<count($query);$i++){ | |
| if(!$pass) break; | |
| if($foundMatch) break; | |
| $field = $query[$i]["field"]; | |
| $value = $query[$i]["value"]; | |
| if(!isset($fieldOffsets[$field])) $this->sendError(E_USER_ERROR, "Cannot run query, the field '$field' does not exist in this table", __LINE__); | |
| fseek($f, $this->bytesToSeek($fp, $header, $fileId) + $fieldOffsets[$field]["offset"]); | |
| $fieldValue = rtrim(fread($f, $fieldOffsets[$field]["length"])); | |
| $fieldType = $fieldOffsets[$field]["type"]; | |
| if($fieldType == "memo") { | |
| $fieldValue = $this->readMemo($fp, $fieldValue, $header); | |
| } | |
| if($query[$i]["type"] == "=") { | |
| if(trim(strtolower($fieldValue)) != strtolower($value)) $pass = false; | |
| } elseif($query[$i]["type"] == "?") { | |
| if(strpos(strtolower($fieldValue), strtolower($value)) <= -1) $pass = false; | |
| } elseif($query[$i]["type"] == "<") { | |
| if((double) $fieldValue >= (double) $value) $pass = false; | |
| } elseif($query[$i]["type"] == ">") { | |
| if((double) $fieldValue <= (double) $value) $pass = false; | |
| } elseif($query[$i]["type"] == "!") { | |
| if(trim(strtolower($fieldValue)) == strtolower($value)) $pass = false; | |
| } else { | |
| $this->sendError(E_USER_ERROR, "Invalid query syntax, missing operator (=,?,>,<,!)", __LINE__); | |
| $pass = false; | |
| } | |
| } | |
| if($pass && !$foundMatch) { | |
| if($start_c < $start) { | |
| //echo $start_c.'<='.$start; | |
| $start_c++; | |
| continue; | |
| } | |
| //we have a match, return it | |
| fseek($f, $this->bytesToSeek($fp, $header, $fileId)); | |
| $buffer = fread($f, $header["recLen"]); | |
| if(ord($buffer{0}) != 28) { | |
| $return[] = $this->parseRecord($fp, $buffer, $header, $reqFields); | |
| $howmany--; | |
| $foundMatch = true; | |
| } | |
| else $this->sendError(E_PARSE, 'Unable to parse record at fileId:'.$fileId.' in listRec() at '.$this->fp[$fp], __LINE__); | |
| //if($howmany_c >= $howmany && $howmany != -1) break; | |
| } | |
| //END add OR functionalilty | |
| } | |
| } | |
| unset($buffer); | |
| fclose($f); | |
| //cache the result | |
| $this->_query[$fp][] = array("result" => $return, | |
| "query_string" => $original_query, | |
| "start" => $original_start, | |
| "howmany" => $original_howmany, | |
| "fields" => $original_fields); | |
| if(empty($return)) return false; | |
| return $return; | |
| } | |
| /** | |
| * Queries a table without a query string using only one field. | |
| * | |
| * @param string $fp | |
| * @param string $field | |
| * @param string $value | |
| * @param int $start[optional] | |
| * @param int $howmany[optional] | |
| * @return bool false on fail, array records on success | |
| */ | |
| function basicQuery($fp, $field, $value, $start = 1, $howmany = -1, $fields=array('*')) { | |
| return $this->query($fp, "$field='$value'", $start, $howmany,$fields); | |
| } | |
| /** | |
| * Internal function to parse raw records into arrays | |
| * | |
| * @param string $fp | |
| * @param string $rawRecord | |
| * @param array $header | |
| * @param array $reqFields | |
| * @return array | |
| */ | |
| function parseRecord($fp, $rawRecord, $header, $reqFields=array("*")) { | |
| if(ord($rawRecord{0}) == 28) { | |
| $this->sendError(E_PARSE, 'Unable to parse record in parseRecord() at '.$this->fp[$fp], __LINE__); | |
| return array(); | |
| } | |
| $cHeader = count($header) - 8; | |
| $pos = 0; | |
| for($i=1;$i<=$cHeader;$i++) { | |
| if(isset($reqFields[$header[$i]["fName"]]) || $reqFields[0] == "*") { | |
| $value = rtrim(substr($rawRecord, $pos, $header[$i]["fLength"])); | |
| if($header[$i]["fType"] == "memo") $value = $this->readMemo($fp, $value, $header); | |
| $fRec[$header[$i]["fName"]] = $value; | |
| } | |
| $pos = $pos + $header[$i]["fLength"]; | |
| } | |
| return $fRec; | |
| } | |
| /** | |
| * Internal function to seek to a paricular record based on the fileID | |
| * | |
| * @param string $fp | |
| * @param array $header | |
| * @param int $recordId | |
| * @return bool false on fail, int seek on success | |
| */ | |
| function bytesToSeek($fp, $header, $recordId) { | |
| $recordId--; | |
| $seek = $header["recPos"] + ($recordId * $header["recLen"]); | |
| if($seek < $header['recPos']) {$this->sendError(E_ERROR, "The record($recordId) you are trying to access before the RECORD_START_POSTION of the file(seeking ".$seek." at ".$this->fp[$fp].").", __LINE__); return false; } | |
| if($seek < filesize($this->fp[$fp].'.ta')) return $seek; | |
| else $this->sendError(E_ERROR, "The record($recordId) you are trying to access is past the end of the file(seeking ".$seek." at ".$this->fp[$fp].").", __LINE__); | |
| return false; | |
| } | |
| /** | |
| * Obsolete function | |
| * | |
| */ | |
| function rewriteMemo() { | |
| $this->sendError(E_USER_NOTICE, 'rewriteMemo() function obsolete. Update scripts accordingly', __LINE__); | |
| } | |
| /** | |
| * Retrieves information from the memo file based on the given index. | |
| * | |
| * @param string $fp | |
| * @param int $index | |
| * @param array $header | |
| * @return string | |
| */ | |
| function readMemo($fp, $index, $header) { | |
| $readIndexes = array(); | |
| $return = ''; | |
| $next = $index; | |
| $f = fopen($this->fp[$fp].'.memo', 'rb'); | |
| while(!empty($next) && $next > 0) { | |
| if(!ctype_digit($next)) die('<b>Fatal Error</b>(line '.__LINE__.'):The Script encountered a non-numeric value for readMemo(): readMemo("'.$this->fp[$fp].'", "'.$index.'", $header) literally at "'.$next.'"position ('.$next.' of '.(filesize($this->fp[$fp].'.memo') / $header["blockLength"]).' block) in the memo file.<br />'); | |
| // Store read index to make sure we don't loop forever if indexes are messed up | |
| $readIndexes[$next] = true; | |
| fseek($f, ($next * $header["blockLength"])); | |
| $next = trim(fread($f, 7)); | |
| // Make sure the next index hasn't already been read | |
| if(isset($readIndexes[$next])) die('<b>Fatal Error</b>(line '.__LINE__.'): There is an error in '.$this->fp[$fp].'.memo, this needs to be corrected. The error starts on index <b>'.$index.'</b>'); | |
| if((ftell($f) - 7) == $next * $header["blockLength"]) die('<b>Fatal Error</b>(line '.__LINE__.'): Script entered an endless loop in readMemo("'.$this->fp[$fp].'", "'.$index.'", $header) at "'.$next.'" position ('.$next.' of '.(filesize($this->fp[$fp].'.memo') / $header["blockLength"]).' block) in the memo file.<br />'); | |
| $return .= substr(rtrim(fread($f, $header["blockLength"] - 7)), 0, -1); | |
| } | |
| fclose($f); | |
| //str_replace added to remove <x> from all queries. Should not be used for non-UPB context. | |
| return str_replace('<x>','',$return); | |
| } | |
| /** | |
| * Deletes information associated with the index from the memo file. | |
| * | |
| * @param string $fp | |
| * @param int $index | |
| * @param array $header | |
| * @return void | |
| */ | |
| function deleteMemo($fp, $index, $header) { | |
| if($index == '0') die('<b>Fatal Error</b>(line '.__LINE__.'): Tried to delete 0th memo record in '.$this->fp[$fp].'. Literally: '.$index); | |
| if(!(ctype_digit($index) && !empty($index))) return true; | |
| $readIndexes = array(); | |
| $next = $index; | |
| $f = fopen($this->fp[$fp].'.memo', 'r+b'); | |
| if(empty($this->_firstBlankMemoBlockRef[$fp])) $first_blank_memo_block_ref = trim(fread($f, 7)); | |
| else $first_blank_memo_block_ref = $this->_firstBlankMemoBlockRef[$fp]; | |
| while(ctype_digit($next) && !empty($next)) { | |
| // Store read index to make sure we don't loop forever if indexes are messed up | |
| $readIndexes[$next] = true; | |
| fseek($f, $next * $header["blockLength"]); | |
| $next = trim(fread($f, 7)); | |
| // Make sure the next index hasn't already been read | |
| if(isset($readIndexes[$next])) die('<b>Fatal Error</b>(line '.__LINE__.'): There is an error in '.$this->fp[$fp].'.memo, this needs to be corrected. The error starts on index <b>'.$index.'</b>'); | |
| if((ftell($f) - 7) == $next * $header["blockLength"]) die('<b>Fatal Error</b>(line '.__LINE__.'): Script entered an endless loop in deleteMemo("'.$this->fp[$fp].'", "'.$index.'", $header) at ('.$next.' of '.(filesize($this->fp[$fp].'.memo') / $header["blockLength"]).' block) in the memo file.<br />'); | |
| } | |
| fseek($f, -7, SEEK_CUR); | |
| fwrite($f, $first_blank_memo_block_ref.str_repeat(' ', 7 - strlen($first_blank_memo_block_ref))); | |
| fseek($f, 0); | |
| fwrite($f, $index.str_repeat(' ', 7 - strlen($index))); | |
| $this->_firstBlankMemoBlockRef[$fp] = $index; | |
| return true; | |
| } | |
| /** | |
| * Writes the data into the memo file | |
| * | |
| * @param string $fp | |
| * @param string $oriData | |
| * @param array $header | |
| * @return int | |
| */ | |
| function writeMemo($fp, $oriData, $header) { | |
| $data = trim($oriData,"\t\n\r\0\x0B"); //strip all but whitespace from both ends of data | |
| if(strlen($data) == 0) return; | |
| $f = fopen($this->fp[$fp].'.memo', 'r+b'); | |
| if(empty($this->_firstBlankMemoBlockRef[$fp])) { | |
| $next = trim(fread($f, 7)); | |
| $this->_firstBlankMemoBlockRef[$fp] = $next; | |
| } else $next = $this->_firstBlankMemoBlockRef[$fp]; | |
| $readIndexes = array(); | |
| while(ctype_digit($next) && !empty($next) && !(strlen($data) == 0)) { | |
| if(!isset($return)) $return = $next; | |
| // Store read index to make sure we don't loop forever if indexes are messed up | |
| $readIndexes[$next] = true; | |
| fseek($f, $next * $header["blockLength"]); | |
| $next = trim(fread($f, 7)); | |
| // Make sure the next index hasn't already been read | |
| if(isset($readIndexes[$next])) die('<b>Fatal Error</b>(line '.__LINE__.'): There is an error in '.$this->fp[$fp].'.memo, this needs to be corrected. The error starts on index <b>'.$index.'</b>'); | |
| if(ftell($f) - 7 == $next * $header["blockLength"]) die('<b>Fatal Error</b>(line '.__LINE__.'): Script entered an endless loop in writeMemo("'.$this->fp[$fp].'", "'.$oriData.'", $header) at "'.$next.'" position in the memo file.<br />'); | |
| if(strlen($data) > ($header["blockLength"] - 8)) { //if it won't fit | |
| fwrite($f, substr($data, 0, $header["blockLength"] - 8).chr(3)); | |
| $data = substr($data, $header["blockLength"] - 8); | |
| } else { | |
| fseek($f, -7, SEEK_CUR); | |
| fwrite($f, '-1'.str_repeat(' ', 5).$data.chr(3).str_repeat(' ', ($header["blockLength"] - (strlen($data) + 8)))); | |
| $data = ''; | |
| } | |
| } | |
| if(!(strlen($data) == 0) && ftell($f) >= $header["blockLength"]) { | |
| fseek($f, -($header["blockLength"]), SEEK_CUR); | |
| $last_write_offset = ftell($f); | |
| fseek($f, 0, SEEK_END); | |
| $EOF_index = ftell($f) / $header["blockLength"]; | |
| fseek($f, $last_write_offset); | |
| fwrite($f, $EOF_index.str_repeat(' ', 7 - strlen($EOF_index))); | |
| } | |
| fseek($f, 0); | |
| fwrite($f, $next.str_repeat(' ', 7 - strlen($next))); | |
| $this->_firstBlankMemoBlockRef[$fp] = $next; | |
| fseek($f, 0, SEEK_END); | |
| while(!(strlen($data) == 0)) { | |
| $next = (ftell($f) / $header["blockLength"]) + 1; | |
| if(!isset($return)) $return = $next - 1; | |
| if(!is_integer($next) || $next < 0) die('<b>Fatal Error</b>(line '.__LINE__.'):The Script encountered a non-numeric value for writeMemo(): writeMemo("'.$this->fp[$fp].'", "'.$oriData.'", $header) literally at "'.$next.'" position ('.($next / $header["blockLength"]).' of '.(filesize($this->fp[$fp].'.memo') / $header["blockLength"]).' block) in the memo file.<br />'); | |
| if(strlen($data) > ($header["blockLength"] - 8)) { //if it won't fit | |
| fwrite($f, $next.str_repeat(' ', 7 - strlen($next)).substr($data, 0, $header["blockLength"] - 8).chr(3)); | |
| $data = substr($data, $header["blockLength"] - 8); | |
| } else { | |
| fwrite($f, '-1'.str_repeat(' ', 5).$data.chr(3).str_repeat(' ', ($header["blockLength"] - (strlen($data) + 8)))); | |
| $data = ''; | |
| } | |
| } | |
| fclose($f); | |
| return $return; | |
| } | |
| /** | |
| * Checks to validate a working database, directory, and [optional]table | |
| * | |
| * @param int $line | |
| * @param string $fp | |
| * @return bool | |
| */ | |
| function check($fp = null) { | |
| clearstatcache(); | |
| if(!isset($this->Db) || $this->Db == '') { | |
| throw new UndefinedDatabaseException(); | |
| } | |
| if(!isset($this->workingDir) || $this->workingDir == '') { | |
| throw new UndefinedWorkingDirectoryException(); | |
| } | |
| if(!file_exists($this->workingDir.$this->Db)) { | |
| throw new DatabaseExistsException(); | |
| } | |
| if($fp != null) { | |
| if($fp == '') { | |
| throw new InvalidFilePointerException(); | |
| } | |
| if(!isset($this->fp[$fp]) || $this->fp[$fp] == '') { | |
| throw new InvalidFilePointerException(); | |
| } | |
| if(!file_exists($this->fp[$fp].'.ta')) { | |
| throw new InvalidTableException(); | |
| } | |
| } | |
| } | |
| /** | |
| * @param $errno | |
| * @param $errMsg | |
| * @param string $line | |
| */ | |
| function sendError($errno, $errMsg, $line = '') { | |
| if(TDB_ERROR_INCLUDE_ORIGIN == TRUE) { | |
| $error_trace = debug_backtrace(); | |
| $error_origin = ' Executed on '.$error_trace[count($error_trace) -1]['file'].' at line '.$error_trace[count($error_trace) -1]['line'].'.'; | |
| $errMsg .= $error_origin; | |
| } | |
| if(TDB_PRINT_ERRORS === TRUE) print('<b>Text Database Error</b>: '.$errMsg. ((($line != null || $line != '') && (TDB_ERROR_INCLUDE_ORIGIN === false)) ? ' near line '.$line : '').'<br />'); | |
| elseif($this->error_handler !== FALSE) call_user_func_array($this->error_handler, array($errno, $errMsg, 'tdb.php', $line)); | |
| else trigger_error('<b>Text Database Error</b>: '.$errMsg. (($line != null || $line != '') ? ' near line '.$line : '')); | |
| } | |
| /** | |
| * Defines an error handler | |
| * | |
| * @param object $object | |
| * @param string $function | |
| */ | |
| function define_error_handler(&$object, $function) { | |
| $this->error_handler = array(&$object, $function); | |
| } | |
| /** | |
| * Returns the version of tdb class in use | |
| * | |
| * @return string | |
| */ | |
| function version() { | |
| return "4.4.4"; | |
| } | |
| /** | |
| * @param $text | |
| * @return mixed | |
| */ | |
| function deXSS($text) { | |
| //echo "$text::".substr_count('<x>',$text)."<br>"; | |
| return str_replace('<x>','',$text); | |
| } | |
| } |