~ubuntu-branches/ubuntu/saucy/horde3/saucy

« back to all changes in this revision

Viewing changes to lib/Horde/Data/palm.php

  • Committer: Bazaar Package Importer
  • Author(s): Ola Lundqvist
  • Date: 2005-05-04 23:08:08 UTC
  • Revision ID: james.westby@ubuntu.com-20050504230808-p4hf3hk28o3v7wir
Tags: upstream-3.0.4
ImportĀ upstreamĀ versionĀ 3.0.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * Abstract class to allow data exchange between the Horde
 
4
 * applications and various Palm formats.
 
5
 *
 
6
 * $Horde: framework/Data/Data/palm.php,v 1.5 2004/06/23 19:43:25 chuck Exp $
 
7
 *
 
8
 * TODO: export method
 
9
 *
 
10
 * @author  Mathieu Clabaut <mathieu.clabaut@free.fr>
 
11
 * @package Horde_Data
 
12
 */
 
13
class Data_palm extends Data {
 
14
 
 
15
}
 
16
 
 
17
/**
 
18
 * PHP-PDB -- PHP class to write PalmOS databases.
 
19
 *
 
20
 * Copyright (C) 2001 - PHP-PDB development team
 
21
 * Licensed under the GNU LGPL software license.
 
22
 * See the doc/LEGAL file for more information
 
23
 * See http://php-pdb.sourceforge.net/ for more information about the library
 
24
 *
 
25
 * As a note, storing all of the information as hexadecimal kinda
 
26
 * sucks, but it is tough to store and properly manipulate a binary
 
27
 * string in PHP. We double the size of the data but decrease the
 
28
 * difficulty level immensely.
 
29
 */
 
30
 
 
31
/**
 
32
 * Define constants
 
33
 */
 
34
 
 
35
// Sizes
 
36
define('PDB_HEADER_SIZE', 72); // Size of the database header
 
37
define('PDB_INDEX_HEADER_SIZE', 6); // Size of the record index header
 
38
define('PDB_RECORD_HEADER_SIZE', 8); // Size of the record index entry
 
39
define('PDB_RESOURCE_SIZE', 10);  // Size of the resource index entry
 
40
define('PDB_EPOCH_1904', 2082844800); // Difference between Palm's time and Unix
 
41
 
 
42
// Attribute Flags
 
43
define('PDB_ATTRIB_RESOURCE', 1);
 
44
define('PDB_ATTRIB_READ_ONLY', 2);
 
45
define('PDB_ATTRIB_APPINFO_DIRTY', 4);
 
46
define('PDB_ATTRIB_BACKUP', 8);
 
47
define('PDB_ATTRIB_OK_NEWER', 16);
 
48
define('PDB_ATTRIB_RESET', 32);
 
49
define('PDB_ATTRIB_OPEN', 64);
 
50
define('PDB_ATTRIB_LAUNCHABLE', 512);
 
51
 
 
52
// Record Flags
 
53
// The first nibble is reserved for the category number
 
54
// See PDB_CATEGORY_MASK
 
55
define('PDB_RECORD_ATTRIB_PRIVATE', 16);
 
56
define('PDB_RECORD_ATTRIB_DELETED', 32);
 
57
define('PDB_RECORD_ATTRIB_DIRTY', 64);
 
58
define('PDB_RECORD_ATTRIB_EXPUNGED', 128);
 
59
 
 
60
// Category support
 
61
define('PDB_CATEGORY_NUM', 16);  // Number of categories
 
62
define('PDB_CATEGORY_NAME_LENGTH', 16);  // Bytes allocated for name
 
63
define('PDB_CATEGORY_SIZE', 276); // 2 + (num * length) + num + 1 + 1
 
64
define('PDB_CATEGORY_MASK', 15);  // Bitmask -- use with attribute of record
 
65
                                  // to get the category ID
 
66
 
 
67
 
 
68
// Double conversion
 
69
define('PDB_DOUBLEMETHOD_UNTESTED', 0);
 
70
define('PDB_DOUBLEMETHOD_NORMAL', 1);
 
71
define('PDB_DOUBLEMETHOD_REVERSE', 2);
 
72
define('PDB_DOUBLEMETHOD_BROKEN', 3);
 
73
 
 
74
/**
 
75
 * PalmDB Class
 
76
 *
 
77
 * Contains all of the required methods and variables to write a pdb file.
 
78
 * Extend this class to provide functionality for memos, addresses, etc.
 
79
 *
 
80
 * @package Horde_Data
 
81
 */
 
82
class PalmDB {
 
83
   var $Records = array();     // All of the data from the records is here
 
84
                               // Key = record ID
 
85
   var $RecordAttrs = array(); // And their attributes are here
 
86
   var $CurrentRecord = 1;     // Which record we are currently editing
 
87
   var $Name = '';             // Name of the PDB file
 
88
   var $TypeID = '';           // The 'Type' of the file (4 chars)
 
89
   var $CreatorID = '';        // The 'Creator' of the file (4 chars)
 
90
   var $Attributes = 0;        // Attributes (bitmask)
 
91
   var $Version = 0;           // Version of the file
 
92
   var $ModNumber = 0;         // Modification number
 
93
   var $CreationTime = 0;      // Stored in unix time (Jan 1, 1970)
 
94
   var $ModificationTime = 0;  // Stored in unix time (Jan 1, 1970)
 
95
   var $BackupTime = 0;        // Stored in unix time (Jan 1, 1970)
 
96
   var $AppInfo = '';          // Basic AppInfo block
 
97
   var $SortInfo = '';         // Basic SortInfo block
 
98
   var $DoubleMethod = PDB_DOUBLEMETHOD_UNTESTED;
 
99
                               // What method to use for converting doubles
 
100
 
 
101
 
 
102
   // Creates a new database class
 
103
   function PalmDB($Type = '', $Creator = '', $Name = '') {
 
104
      $this->TypeID = $Type;
 
105
      $this->CreatorID = $Creator;
 
106
      $this->Name = $Name;
 
107
      $this->CreationTime = time();
 
108
      $this->ModificationTime = time();
 
109
   }
 
110
 
 
111
 
 
112
   /*
 
113
    * Data manipulation functions
 
114
    *
 
115
    * These convert various numbers and strings into the hexadecimal
 
116
    * format that is used internally to construct the file.  We use hex
 
117
    * encoded strings since that is a lot easier to work with than binary
 
118
    * data in strings, and we can easily tell how big the true value is.
 
119
    * B64 encoding does some odd stuff, so we just make the memory
 
120
    * consumption grow tremendously and the complexity level drops
 
121
    * considerably.
 
122
    */
 
123
       
 
124
   // Converts a byte and returns the value
 
125
   function Int8($value) {
 
126
      $value &= 0xFF;
 
127
      return sprintf("%02x", $value);
 
128
   }
 
129
   
 
130
   
 
131
   // Loads a single byte as a number from the file
 
132
   // Use if you want to make your own ReadFile function
 
133
   function LoadInt8($file) {
 
134
      if (is_resource($file))
 
135
         $string = fread($file, 1);
 
136
      else
 
137
         $string = $file;
 
138
      return ord($string[0]);
 
139
   }
 
140
   
 
141
   
 
142
   // Converts an integer (two bytes) and returns the value
 
143
   function Int16($value) {
 
144
      $value &= 0xFFFF;
 
145
      return sprintf("%02x%02x", $value / 256, $value % 256);
 
146
   }
 
147
   
 
148
   
 
149
   // Loads two bytes as a number from the file
 
150
   // Use if you want to make your own ReadFile function
 
151
   function LoadInt16($file) {
 
152
      if (is_resource($file))
 
153
         $string = fread($file, 2);
 
154
      else
 
155
         $string = $file;
 
156
      return ord($string[0]) * 256 + ord($string[1]);
 
157
   }
 
158
   
 
159
   
 
160
   // Converts an integer (three bytes) and returns the value
 
161
   function Int24($value) {
 
162
      $value &= 0xFFFFFF;
 
163
      return sprintf("%02x%02x%02x", $value / 65536, 
 
164
                     ($value / 256) % 256, $value % 256);
 
165
   }
 
166
 
 
167
 
 
168
   // Loads three bytes as a number from the file
 
169
   // Use if you want to make your own ReadFile function
 
170
   function LoadInt24($file) {
 
171
      if (is_resource($file))
 
172
         $string = fread($file, 3);
 
173
      else
 
174
     $string = $file;
 
175
      return ord($string[0]) * 65536 + ord($string[1]) * 256 +
 
176
         ord($string[2]);
 
177
   }
 
178
      
 
179
      
 
180
   // Converts an integer (four bytes) and returns the value
 
181
   // 32-bit integers have problems with PHP when they are bigger than
 
182
   // 0x80000000 (about 2 billion) and that's why I don't use pack() here
 
183
   function Int32($value) {
 
184
      $negative = false;
 
185
      if ($value < 0) {
 
186
         $negative = true;
 
187
     $value = - $value;
 
188
      }
 
189
      $big = $value / 65536;
 
190
      settype($big, 'integer');
 
191
      $little = $value - ($big * 65536);
 
192
      if ($negative) {
 
193
         // Little must contain a value
 
194
         $little = - $little;
 
195
     // Big might be zero, and should be 0xFFFF if that is the case.
 
196
     $big = 0xFFFF - $big;
 
197
      }
 
198
      $value = PalmDB::Int16($big) . PalmDB::Int16($little);
 
199
      return $value;
 
200
   }
 
201
   
 
202
   
 
203
   // Loads a four-byte string from a file into a number
 
204
   // Use if you want to make your own ReadFile function
 
205
   function LoadInt32($file) {
 
206
      if (is_resource($file))
 
207
         $string = fread($file, 4);
 
208
      else
 
209
         $string = $file;
 
210
      $value = 0;
 
211
      $i = 0;
 
212
      while ($i < 4) {
 
213
         $value *= 256;
 
214
     $value += ord($string[$i]);
 
215
     $i ++;
 
216
      }
 
217
      return $value;
 
218
   }
 
219
   
 
220
   
 
221
   // Converts the number into a double and returns the encoded value
 
222
   // Not sure if this will work on all platforms.
 
223
   // Double(10.53) should return "40250f5c28f5c28f"
 
224
   function Double($value) {
 
225
      if ($this->DoubleMethod == PDB_DOUBLEMETHOD_UNTESTED) {
 
226
         $val = bin2hex(pack('d', 10.53));
 
227
     $val = strtolower($val);
 
228
     if (substr($val, 0, 4) == '8fc2')
 
229
        $this->DoubleMethod = PDB_DOUBLEMETHOD_REVERSE;
 
230
     if (substr($val, 0, 4) == '4025')
 
231
        $this->DoubleMethod = PDB_DOUBLEMETHOD_NORMAL;
 
232
     if ($this->DoubleMethod == PDB_DOUBLEMETHOD_UNTESTED)
 
233
        $this->DoubleMethod = PDB_DOUBLEMETHOD_BROKEN;
 
234
      }
 
235
      
 
236
      if ($this->DoubleMethod == PDB_DOUBLEMETHOD_BROKEN)
 
237
         return '0000000000000000';
 
238
     
 
239
      $value = bin2hex(pack('d', $value));
 
240
      
 
241
      if ($this->DoubleMethod == PDB_DOUBLEMETHOD_REVERSE)
 
242
         $value = substr($value, 14, 2) . substr($value, 12, 2) . 
 
243
            substr($value, 10, 2) . substr($value, 8, 2) . 
 
244
        substr($value, 6, 2) . substr($value, 4, 2) . 
 
245
        substr($value, 2, 2) . substr($value, 0, 2);
 
246
        
 
247
      return $value;
 
248
   }
 
249
   
 
250
   
 
251
   // The reverse?  Not coded yet.
 
252
   // Use if you want to make your own ReadFile function
 
253
   function LoadDouble($file) {
 
254
      if (is_resource($file))
 
255
         $string = fread($file, 8);
 
256
      else
 
257
         $string = $file;
 
258
      return 0;
 
259
   }
 
260
   
 
261
   
 
262
   // Converts a string into hexadecimal.
 
263
   // If $maxLen is specified and is greater than zero, the string is 
 
264
   // trimmed and will contain up to $maxLen characters.
 
265
   // String("abcd", 2) will return "ab" hex encoded (a total of 4
 
266
   // resulting bytes, but 2 encoded characters).
 
267
   // Returned string is *not* NULL-terminated.
 
268
   function String($value, $maxLen = false) {
 
269
      $value = bin2hex($value);
 
270
      if ($maxLen !== false && $maxLen > 0)
 
271
         $value = substr($value, 0, $maxLen * 2);
 
272
      return $value;
 
273
   }
 
274
   
 
275
   
 
276
   // Pads a hex-encoded value (typically a string) to a fixed size.
 
277
   // May grow too long if $value starts too long
 
278
   // $value = hex encoded value
 
279
   // $minLen = Append nulls to $value until it reaches $minLen
 
280
   // $minLen is the desired size of the string, unencoded.
 
281
   // PadString('6162', 3) results in '616200' (remember the hex encoding)
 
282
   function PadString($value, $minLen) {
 
283
      $PadBytes = '00000000000000000000000000000000';
 
284
      $PadMe = $minLen - (strlen($value) / 2);
 
285
      while ($PadMe > 0) {
 
286
         if ($PadMe > 16)
 
287
        $value .= $PadBytes;
 
288
     else
 
289
        return $value . substr($PadBytes, 0, $PadMe * 2);
 
290
           
 
291
     $PadMe = $minLen - (strlen($value) / 2);
 
292
      }
 
293
      
 
294
      return $value;
 
295
   }
 
296
   
 
297
   
 
298
   /*
 
299
    * Record manipulation functions
 
300
    */
 
301
    
 
302
   // Sets the current record pointer to the new record number if an
 
303
   // argument is passed in.
 
304
   // Returns the old record number (just in case you want to jump back)
 
305
   // Does not do basic record initialization if we are going to a new 
 
306
   // record.
 
307
   function GoToRecord($num = false) {
 
308
      if ($num === false)
 
309
         return $this->CurrentRecord;
 
310
      if (gettype($num) == 'string' && ($num[0] == '+' || $num[0] == '-'))
 
311
         $num = $this->CurrentRecord + $num;
 
312
      $oldRecord = $this->CurrentRecord;
 
313
      $this->CurrentRecord = $num;
 
314
      return $oldRecord;
 
315
   }
 
316
   
 
317
   
 
318
   // Returns the size of the current record if no arguments.
 
319
   // Returns the size of the specified record if arguments.
 
320
   function GetRecordSize($num = false) {
 
321
      if ($num === false)
 
322
         $num = $this->CurrentRecord;
 
323
      if (! isset($this->Records[$num]))
 
324
         return 0;
 
325
      return strlen($this->Records[$num]) / 2;
 
326
   }
 
327
   
 
328
   
 
329
   // Adds to the current record.  The input data must be already
 
330
   // hex encoded.  Initializes the record if it doesn't exist.
 
331
   function AppendCurrent($value) {
 
332
      if (! isset($this->Records[$this->CurrentRecord]))
 
333
         $this->Records[$this->CurrentRecord] = '';
 
334
      $this->Records[$this->CurrentRecord] .= $value;
 
335
   }
 
336
   
 
337
   
 
338
   // Adds a byte to the current record
 
339
   function AppendInt8($value) {
 
340
      $this->AppendCurrent($this->Int8($value));
 
341
   }
 
342
   
 
343
   
 
344
   // Adds an integer (2 bytes) to the current record
 
345
   function AppendInt16($value) {
 
346
      $this->AppendCurrent($this->Int16($value));
 
347
   }
 
348
   
 
349
   
 
350
   // Adds an integer (4 bytes) to the current record
 
351
   function AppendInt32($value) {
 
352
      $this->AppendCurrent($this->Int32($value));
 
353
   }
 
354
   
 
355
   
 
356
   // Adds a double to the current record
 
357
   function AppendDouble($value) {
 
358
      $this->AppendCurrent($this->Double($value));
 
359
   }
 
360
   
 
361
   
 
362
   // Adds a string (not NULL-terminated)
 
363
   function AppendString($value, $maxLen = false) {
 
364
      $this->AppendCurrent($this->String($value, $maxLen));
 
365
   }
 
366
   
 
367
   
 
368
   // Returns true if the specified/current record exists and is set
 
369
   function RecordExists($Rec = false) {
 
370
      if ($Rec === false)
 
371
         $Rec = $this->CurrentRecord;
 
372
      if (isset($this->Records[$Rec]))
 
373
         return true;
 
374
      return false;
 
375
   }
 
376
   
 
377
   
 
378
   // Returns the hex-encoded data for the specified record or the current
 
379
   // record if not specified
 
380
   function GetRecord($Rec = false) {
 
381
      if ($Rec === false)
 
382
         $Rec = $this->CurrentRecord;
 
383
      if (isset($this->Records[$Rec]))
 
384
         return $this->Records[$Rec];
 
385
      return '';
 
386
   }
 
387
   
 
388
   
 
389
   // Returns the raw data inside the current/specified record.  Use this
 
390
   // for odd record types (like a Datebook record).  Also, use this
 
391
   // instead of just using $PDB->Records[] directly.
 
392
   function GetRecordRaw($Rec = false) {
 
393
      if ($Rec === false)
 
394
         $Rec = $this->CurrentRecord;
 
395
      if (isset($this->Records[$Rec]))
 
396
         return $this->Records[$Rec];
 
397
      return false;
 
398
   }
 
399
   
 
400
   
 
401
   // Sets the hex-encoded data (or whatever) for the current record
 
402
   // Use this instead of the Append* functions if you have an odd
 
403
   // type of record (like a Datebook record).
 
404
   // Also, use this instead of just setting $PDB->Records[]
 
405
   // directly.
 
406
   // SetRecordRaw('data');
 
407
   // SetRecordRaw(24, 'data');   (specifying the record num)
 
408
   function SetRecordRaw($A, $B = false) {
 
409
      if ($B === false) {
 
410
         $B = $A;
 
411
     $A = $this->CurrentRecord;
 
412
      }
 
413
      $this->Records[$A] = $B;
 
414
   }
 
415
   
 
416
   
 
417
   // Deletes the current record
 
418
   // You are urged to use GoToRecord() and jump to an existing record
 
419
   // after this function call so that the deleted record doesn't
 
420
   // get accidentally recreated/used -- all append functions will
 
421
   // create a new, empty record if the current record doesn't exist.
 
422
   function DeleteCurrentRecord() {
 
423
      if (isset($this->Records[$this->CurrentRecord]))
 
424
         unset($this->Records[$this->CurrentRecord]);
 
425
      if (isset($this->RecordAttrs[$this->CurrentRecord]))
 
426
         unset($this->RecordAttrs[$this->CurrentRecord]);
 
427
   }
 
428
   
 
429
   
 
430
   // Returns an array of available record IDs in the order they should
 
431
   // be written.
 
432
   // Probably should only be called within the class.
 
433
   function GetRecordIDs() {
 
434
      $keys = array_keys($this->Records);
 
435
      if (! is_array($keys) || count($keys) < 1)
 
436
         return array();
 
437
      sort($keys, SORT_NUMERIC);
 
438
      return $keys;
 
439
   }
 
440
   
 
441
   
 
442
   // Returns the number of records.  This should match the number of
 
443
   // keys returned by GetRecordIDs().
 
444
   function GetRecordCount() {
 
445
      return count($this->Records);
 
446
   }
 
447
   
 
448
   
 
449
   // Returns the size of the AppInfo block.
 
450
   // Used only for writing
 
451
   function GetAppInfoSize() {
 
452
      if (! isset($this->AppInfo))
 
453
         return 0;
 
454
      return strlen($this->AppInfo) / 2;
 
455
   }
 
456
   
 
457
   
 
458
   // Returns the AppInfo block (hex encoded)
 
459
   // Used only for writing
 
460
   function GetAppInfo() {
 
461
      if (! isset($this->AppInfo))
 
462
         return 0;
 
463
      return $this->AppInfo;
 
464
   }
 
465
   
 
466
   
 
467
   // Returns the size of the SortInfo block
 
468
   // Used only for writing
 
469
   function GetSortInfoSize() {
 
470
      if (! isset($this->SortInfo))
 
471
         return 0;
 
472
      return strlen($this->SortInfo) / 2;
 
473
   }
 
474
   
 
475
   
 
476
   // Returns the SortInfo block (hex encoded)
 
477
   // Used only for writing
 
478
   function GetSortInfo() {
 
479
      if (! isset($this->SortInfo))
 
480
         return 0;
 
481
      return $this->SortInfo;
 
482
   }
 
483
   
 
484
   
 
485
   /*
 
486
    * Category Support
 
487
    */
 
488
    
 
489
   // Creates the hex-encoded data to be stuck in the AppInfo
 
490
   // block if the database supports categories.
 
491
   //
 
492
   // Data format:
 
493
   //    $categoryArray[id#] = name
 
494
   // Or:
 
495
   //    $categoryArray[id#]['name'] = name
 
496
   //    $categoryArray[id#]['renamed'] = true / false
 
497
   //
 
498
   // Tips:
 
499
   //  * I'd suggest numbering your categories sequentially
 
500
   //  * Do not have a category 0.  It must always be 'Unfiled'.  This
 
501
   //    function will overwrite any category with the ID of 0.
 
502
   //  * There is a maximum of 16 categories, including 'Unfiled'.
 
503
   //
 
504
   // Category 0 is reserved for 'Unfiled'
 
505
   // Categories 1-127 are used for handheld ID numbers
 
506
   // Categories 128-255 are used for desktop ID numbers
 
507
   // Do not let category numbers be created larger than 255
 
508
   function CreateCategoryData($CategoryArray) {
 
509
      $CategoryArray[0] = array('Name' => 'Unfiled', 'Renamed' => false);
 
510
      $CatsWritten = 0;
 
511
      $LastIdWritten = 0;
 
512
      $RenamedFlags = 0;
 
513
      $CategoryStr = '';
 
514
      $IdStr = '';
 
515
      $keys = array_keys($CategoryArray);
 
516
      sort($keys);
 
517
      foreach ($keys as $id) {
 
518
         if ($CatsWritten < PDB_CATEGORY_NUM) {
 
519
        $CatsWritten ++;
 
520
        $LastIdWritten = $id;
 
521
        $RenamedFlags *= 2;
 
522
        if (is_array($CategoryArray[$id]) && 
 
523
            isset($CategoryArray[$id]['Renamed']) &&
 
524
        $CategoryArray[$id]['Renamed'])
 
525
           $RenamedFlags += 1;
 
526
        $name = '';
 
527
        if (is_array($CategoryArray[$id])) {
 
528
           if (isset($CategoryArray[$id]['Name']))
 
529
              $name = $CategoryArray[$id]['Name'];
 
530
        } else
 
531
           $name = $CategoryArray[$id];
 
532
        $name = $this->String($name, PDB_CATEGORY_NAME_LENGTH);
 
533
        $CategoryStr .= $this->PadString($name,
 
534
                                         PDB_CATEGORY_NAME_LENGTH);
 
535
        $IdStr .= $this->Int8($id);
 
536
     }
 
537
      }
 
538
     
 
539
      while ($CatsWritten < PDB_CATEGORY_NUM) {
 
540
         $CatsWritten ++;
 
541
     $LastIdWritten ++;
 
542
     $RenamedFlags *= 2;
 
543
     $CategoryStr .= $this->PadString('', PDB_CATEGORY_NAME_LENGTH);
 
544
     $IdStr .= $this->Int8($LastIdWritten);
 
545
      }
 
546
      
 
547
      $TrailingBytes = $this->Int8($LastIdWritten);
 
548
      $TrailingBytes .= $this->Int8(0);
 
549
     
 
550
      // Error checking
 
551
      if ($LastIdWritten >= 256)
 
552
         return $this->PadString('', PDB_CATEGORY_SIZE);
 
553
     
 
554
      return $this->Int16($RenamedFlags) . $CategoryStr . $IdStr . 
 
555
         $TrailingBytes;
 
556
   }
 
557
   
 
558
   
 
559
   // This should be called by other subclasses that use category support
 
560
   // It returns a category array.  Each element in the array is another
 
561
   // array with the key 'name' set to the name of the category and 
 
562
   // the key 'renamed' set to the renamed flag for that category.
 
563
   function LoadCategoryData($fileData) {
 
564
      $RenamedFlags = $this->LoadInt16(substr($fileData, 0, 2));
 
565
      $Offset = 2;
 
566
      $StartingFlag = 65536;
 
567
      $Categories = array();
 
568
      while ($StartingFlag > 1) {
 
569
         $StartingFlag /= 2;
 
570
     $Name = substr($fileData, $Offset, PDB_CATEGORY_NAME_LENGTH);
 
571
     $i = 0;
 
572
     while ($i < PDB_CATEGORY_NAME_LENGTH && $Name[$i] != "\0")
 
573
        $i ++;
 
574
     if ($i == 0)
 
575
        $Name = '';
 
576
     elseif ($i < PDB_CATEGORY_NAME_LENGTH)
 
577
        $Name = substr($Name, 0, $i);
 
578
     if ($RenamedFlags & $StartingFlag)
 
579
        $RenamedFlag = true;
 
580
     else
 
581
        $RenamedFlag = false;
 
582
     $Categories[] = array('Name' => $Name, 'Renamed' => $RenamedFlag);
 
583
     $Offset += PDB_CATEGORY_NAME_LENGTH;
 
584
      }
 
585
      
 
586
      $CategoriesParsed = array();
 
587
      
 
588
      foreach ($Categories as $CategoryData) {
 
589
         $UID = $this->LoadInt8(substr($fileData, $Offset, 1));
 
590
     $Offset ++;
 
591
     if ($CategoryData['Name'] != '')
 
592
        $CategoriesParsed[$UID] = $CategoryData;
 
593
      }
 
594
      
 
595
      // Ignore the last ID
 
596
      return $CategoriesParsed;
 
597
   }
 
598
   
 
599
   
 
600
   /*
 
601
    * Database Writing Functions
 
602
    */
 
603
   
 
604
   // *NEW*
 
605
   // Takes a hex-encoded string and makes sure that when decoded, the data
 
606
   // lies on a four-byte boundary.  If it doesn't, it pads the string with
 
607
   // NULLs
 
608
   /*
 
609
    * Commented out because we don't use this function currently.
 
610
    * It is part of a test to see what is needed to get files to sync
 
611
    * properly with Desktop 4.0
 
612
    *
 
613
   function PadTo4ByteBoundary($string) {
 
614
      while ((strlen($string)/2) % 4) {
 
615
         $string .= '00';
 
616
      }
 
617
      return $string;
 
618
   }
 
619
    *
 
620
    */
 
621
    
 
622
   // Returns the hex encoded header of the pdb file
 
623
   // Header = name, attributes, version, creation/modification/backup 
 
624
   //          dates, modification number, some offsets, record offsets,
 
625
   //          record attributes, appinfo block, sortinfo block
 
626
   // Shouldn't be called from outside the class
 
627
   function MakeHeader() {
 
628
      // 32 bytes = name, but only 31 available (one for null)
 
629
      $header = $this->String($this->Name, 31);
 
630
      $header = $this->PadString($header, 32);
 
631
      
 
632
      // Attributes & version fields
 
633
      $header .= $this->Int16($this->Attributes);
 
634
      $header .= $this->Int16($this->Version);
 
635
      
 
636
      // Creation, modification, and backup date
 
637
      if ($this->CreationTime != 0)
 
638
         $header .= $this->Int32($this->CreationTime + PDB_EPOCH_1904);
 
639
      else
 
640
         $header .= $this->Int32(time() + PDB_EPOCH_1904);
 
641
      if ($this->ModificationTime != 0)
 
642
         $header .= $this->Int32($this->ModificationTime + PDB_EPOCH_1904);
 
643
      else
 
644
         $header .= $this->Int32(time() + PDB_EPOCH_1904);
 
645
      if ($this->BackupTime != 0)
 
646
         $header .= $this->Int32($this->BackupTime + PDB_EPOCH_1904);
 
647
      else
 
648
         $header .= $this->Int32(0);
 
649
      
 
650
      // Calculate the initial offset
 
651
      $Offset = PDB_HEADER_SIZE + PDB_INDEX_HEADER_SIZE;
 
652
      $Offset += PDB_RECORD_HEADER_SIZE * count($this->GetRecordIDs());
 
653
      
 
654
      // Modification number, app information id, sort information id
 
655
      $header .= $this->Int32($this->ModNumber);
 
656
      
 
657
      $AppInfo_Size = $this->GetAppInfoSize();
 
658
      if ($AppInfo_Size > 0) {
 
659
         $header .= $this->Int32($Offset);
 
660
     $Offset += $AppInfo_Size;
 
661
      } else
 
662
         $header .= $this->Int32(0);
 
663
      
 
664
      $SortInfo_Size = $this->GetSortInfoSize();
 
665
      if ($SortInfo_Size > 0) {
 
666
         $header .= $this->Int32($Offset);
 
667
         $Offset += $SortInfo_Size;
 
668
      } else
 
669
         $header .= $this->Int32(0);
 
670
     
 
671
      // Type, creator
 
672
      $header .= $this->String($this->TypeID, 4);
 
673
      $header .= $this->String($this->CreatorID, 4);
 
674
      
 
675
      // Unique ID seed
 
676
      $header .= $this->Int32(0);
 
677
      
 
678
      // next record list
 
679
      $header .= $this->Int32(0);
 
680
      
 
681
      // Number of records
 
682
      $header .= $this->Int16($this->GetRecordCount());
 
683
      
 
684
      // Compensate for the extra 2 NULL characters in the $Offset
 
685
      $Offset += 2;
 
686
      
 
687
      // Dump each record
 
688
      if ($this->GetRecordCount() != 0) {
 
689
         $keys = $this->GetRecordIDs();
 
690
     sort($keys, SORT_NUMERIC);
 
691
     foreach ($keys as $index) {
 
692
        $header .= $this->Int32($Offset);
 
693
        if (isset($this->RecordAttrs[$index]))
 
694
           $header .= $this->Int8($this->RecordAttrs[$index]);
 
695
        else
 
696
           $header .= $this->Int8(0);
 
697
        
 
698
        // The unique id is just going to be the record number
 
699
        $header .= $this->Int24($index);
 
700
        
 
701
        $Offset += $this->GetRecordSize($index);
 
702
        // *new* method 3
 
703
        //$Mod4 = $Offset % 4;
 
704
        //if ($Mod4)
 
705
        //   $Offset += 4 - $Mod4;
 
706
     }
 
707
      }
 
708
      
 
709
      // These are the mysterious two NULL characters that we need
 
710
      $header .= $this->Int16(0);
 
711
      
 
712
      // AppInfo and SortInfo blocks go here
 
713
      if ($AppInfo_Size > 0)
 
714
         // *new* method 1
 
715
         $header .= $this->GetAppInfo();
 
716
         //$header .= $this->PadTo4ByteBoundary($this->GetAppInfo());
 
717
      
 
718
      if ($SortInfo_Size > 0)
 
719
         // *new* method 2
 
720
         $header .= $this->GetSortInfo();
 
721
         //$header .= $this->PadTo4ByteBoundary($this->GetSortInfo());
 
722
 
 
723
      return $header;
 
724
   }
 
725
   
 
726
   
 
727
   // Writes the database to the file handle specified.
 
728
   // Use this function like this:
 
729
   //   $file = fopen("output.pdb", "wb"); 
 
730
   //   // "wb" = write binary for non-Unix systems
 
731
   //   if (! $file) {
 
732
   //      echo "big problem -- can't open file";
 
733
   //      exit;
 
734
   //   }
 
735
   //   $pdb->WriteToFile($file);
 
736
   //   fclose($file);
 
737
   function WriteToFile($file) {
 
738
      $header = $this->MakeHeader();
 
739
      fwrite($file, pack('H*', $header), strlen($header) / 2);
 
740
      $keys = $this->GetRecordIDs();
 
741
      sort($keys, SORT_NUMERIC);
 
742
      foreach ($keys as $index) {
 
743
         // *new* method 3
 
744
         //$data = $this->PadTo4ByteBoundary($this->GetRecord($index));
 
745
         $data = $this->GetRecord($index);
 
746
     fwrite($file, pack('H*', $data), strlen($data) / 2);
 
747
      }
 
748
      fflush($file);
 
749
   }
 
750
   
 
751
   
 
752
   // Writes the database to the standard output (like echo).
 
753
   // Can be trapped with output buffering
 
754
   function WriteToStdout() {
 
755
      // You'd think these three lines would work.
 
756
      // If someone can figure out why they don't, please tell me.
 
757
      //
 
758
      // $fp = fopen('php://stdout', 'wb');
 
759
      // $this->WriteToFile($fp);
 
760
      // fclose($fp);
 
761
      
 
762
      $header = $this->MakeHeader();
 
763
      echo pack("H*", $header);
 
764
      $keys = $this->GetRecordIDs();
 
765
      sort($keys, SORT_NUMERIC);
 
766
      foreach ($keys as $index) {
 
767
         // *new* method 3
 
768
     $data = $this->GetRecord($index);
 
769
         //$data = $this->PadTo4ByteBoundary($this->GetRecord($index));
 
770
     echo pack("H*", $data);
 
771
      }
 
772
   }
 
773
   
 
774
 
 
775
   /*
 
776
    * Loading in a database
 
777
    */
 
778
       
 
779
   // Reads data from the file and tries to load it properly
 
780
   // $file is the already-opened file handle.
 
781
   // Returns false if no error
 
782
   function ReadFile($file) {
 
783
      // 32 bytes = name, but only 31 available
 
784
      $this->Name = fread($file, 32);
 
785
      
 
786
      $i = 0;
 
787
      while ($i < 32 && $this->Name[$i] != "\0")
 
788
         $i ++;
 
789
      $this->Name = substr($this->Name, 0, $i);
 
790
      
 
791
      $this->Attributes = $this->LoadInt16($file);
 
792
      $this->Version = $this->LoadInt16($file);
 
793
      
 
794
      $this->CreationTime = $this->LoadInt32($file);
 
795
      if ($this->CreationTime != 0)
 
796
         $this->CreationTime -= PDB_EPOCH_1904;
 
797
      if ($this->CreationTime < 0)
 
798
         $this->CreationTime = 0;
 
799
        
 
800
      $this->ModificationTime = $this->LoadInt32($file);
 
801
      if ($this->ModificationTime != 0)
 
802
         $this->ModificationTime -= PDB_EPOCH_1904;
 
803
      if ($this->ModificationTime < 0)
 
804
         $this->ModificationTime = 0;
 
805
        
 
806
      $this->BackupTime = $this->LoadInt32($file);
 
807
      if ($this->BackupTime != 0)
 
808
         $this->BackupTime -= PDB_EPOCH_1904;
 
809
      if ($this->BackupTime < 0)
 
810
         $this->BackupTime = 0;
 
811
 
 
812
      // Modification number
 
813
      $this->ModNumber = $this->LoadInt32($file);
 
814
      
 
815
      // AppInfo and SortInfo size
 
816
      $AppInfoOffset = $this->LoadInt32($file);
 
817
      $SortInfoOffset = $this->LoadInt32($file);
 
818
      
 
819
      // Type, creator
 
820
      $this->TypeID = fread($file, 4);
 
821
      $this->CreatorID = fread($file, 4);
 
822
      
 
823
      // Skip unique ID seed
 
824
      fread($file, 4);
 
825
      
 
826
      // skip next record list (hope that's ok)
 
827
      fread($file, 4);
 
828
      
 
829
      $RecCount = $this->LoadInt16($file);
 
830
      
 
831
      $RecordData = array();
 
832
      
 
833
      while ($RecCount > 0) {
 
834
         $RecCount --;
 
835
     $Offset = $this->LoadInt32($file);
 
836
     $Attrs = $this->LoadInt8($file);
 
837
     $UID = $this->LoadInt24($file);
 
838
     $RecordData[] = array('Offset' => $Offset, 'Attrs' => $Attrs,
 
839
                           'UID' => $UID);
 
840
      }
 
841
      
 
842
      // Create the offset list
 
843
      if ($AppInfoOffset != 0)
 
844
         $OffsetList[$AppInfoOffset] = 'AppInfo';
 
845
      if ($SortInfoOffset != 0)
 
846
         $OffsetList[$SortInfoOffset] = 'SortInfo';
 
847
      foreach ($RecordData as $data)
 
848
         $OffsetList[$data['Offset']] = array('Record', $data);
 
849
      fseek($file, 0, SEEK_END);
 
850
      $OffsetList[ftell($file)] = 'EOF';
 
851
      
 
852
      // Parse each chunk
 
853
      ksort($OffsetList);
 
854
      $Offsets = array_keys($OffsetList);
 
855
      while (count($Offsets) > 1) {
 
856
         // Don't use the EOF (which should be the last offset)
 
857
     $ThisOffset = $Offsets[0];
 
858
     $NextOffset = $Offsets[1];
 
859
     if ($OffsetList[$ThisOffset] == 'EOF')
 
860
        // Messed up file.  Stop here.
 
861
        return true;
 
862
     $FuncName = 'Load';
 
863
     if (is_array($OffsetList[$ThisOffset])) {
 
864
        $FuncName .= $OffsetList[$ThisOffset][0];
 
865
        $extraData = $OffsetList[$ThisOffset][1];
 
866
     } else {
 
867
        $FuncName .= $OffsetList[$ThisOffset];
 
868
        $extraData = false;
 
869
     }
 
870
     fseek($file, $ThisOffset);
 
871
     $fileData = fread($file, $NextOffset - $ThisOffset);
 
872
     if ($this->$FuncName($fileData, $extraData))
 
873
        return -2;
 
874
     array_shift($Offsets);
 
875
      }
 
876
      
 
877
      return false;
 
878
   }
 
879
 
 
880
  
 
881
   // Generic function to load the AppInfo block into $this->AppInfo
 
882
   // Should only be called within this class
 
883
   // Return false to signal no error
 
884
   function LoadAppInfo($fileData) {
 
885
      $this->AppInfo = bin2hex($fileData);
 
886
      return false;
 
887
   }
 
888
   
 
889
   
 
890
   // Generic function to load the SortInfo block into $this->SortInfo
 
891
   // Should only be called within this class
 
892
   // Return false to signal no error
 
893
   function LoadSortInfo($fileData) {
 
894
      $this->SortInfo = bin2hex($fileData);
 
895
      return false;
 
896
   }
 
897
   
 
898
   
 
899
   // Generic function to load a record
 
900
   // Should only be called within this class
 
901
   // Return false to signal no error
 
902
   function LoadRecord($fileData, $recordInfo) {
 
903
      $this->Records[$recordInfo['UID']] = bin2hex($fileData);
 
904
      $this->RecordAttrs[$recordInfo['UID']] = $recordInfo['Attrs'];
 
905
      return false;
 
906
   }
 
907
 
 
908
}