Subversion Repositories phpLibraryV2

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 rodolico 1
<?php
2
 
3
/* database.class.php
4
   Author: R. W. Rodolico (rodo at dailydata.net)
5
   GNU Public License
6
   20091023
7
 
8
   Encapsulates a database, specifically targeting allowing a database and HTML forms to 
9
   interact. Main function is to allow editing of databases simply by defining the database
10
   structure in a variable
11
 
12
   This code was written in procedural form in my library.php, but the decision to "objectify" 
13
   it was made for ease of maintenance in the future. For example, I had thought to define 
14
   columns that would be editable
15
   in an HTML widget, or special needs column types such as columns with special content (ie, 
16
   one application we have stores PHP in a text column, and another stores menus and reports in
17
   two tables with very weird storage requirements). By simply adding extensions to the basic
18
   DBColumn class, these special purpose uses can be rapidly created.
19
 
20
   A database object may be created manually, or may be created as an array of information
21
 
22
*/
23
 
24
 
25
define( 'DEBUG', true );
26
 
27
require_once "Logging.class.php"; // For logging
28
 
29
/* 
30
   This is the basic column type. It is a simple string, 
31
      left justified on HTML display
32
      <input type='text'> for form input
33
      varchar for db storage
34
   It is also used as the basis for all of the other column types
35
*/
36
 
37
 
38
/*
39
   CSS Classes used
40
   db_string      display and input a string
41
   db_field_name  display table column header or td
42
   db_textarea    display and input textarea (td or <textarea>)
43
   db_bool        display and input true/false (radio)
44
   db_date        display and input date (text)
45
   db_datetime    display and input date and time (text)
46
   db_int         display and input integers
47
   db_real        display and input floating point numbers
48
   db_password    display and input password (password, display is a series of 8 asterisks)
49
   db_file        display and input file (type="file")
50
*/
51
 
52
class DBColumn { // base class for various column types
53
   /*
54
      A column has
55
         name
56
         type (used to define how it is processed)
57
         nullable (if it can be null or not)
58
         default value (used if creating a row)
59
         required (must have a non-null value entered)
60
         readOnly (will be displayed as text in an input form)
61
         width (size of the actual field)
62
   */
63
   public      $columnName;   // name in database
64
   public      $primaryKey;   // true if this is a member of the primary key
65
   public      $displayName;  // human readable name, uses $columnName if not defined
66
   public      $value;        // in most cases, can hold the actual value (not type file, MTM)
67
   public      $required;     // if false, may be set to null
68
   public      $default;      // for not null columns, what value to use if it is null.
69
   public      $readOnly;     // if set, field will be displayed and not editable
70
   public      $width;        // width of input field and/or html display
71
   public      $nullable;     // true/false if it can be null (as opposed to empty)
72
 
73
   // Accepts a column name (required), and array of definitions, and a value (both optional)
74
   public function __construct ( $columnName, $definition = null, $value=null) {
75
      $this->columnName = $columnName;
76
      $this->value = isset($value) ? $value : '';
77
      $this->displayName = isset($definition['display name']) ? 
78
                           $definition['display name'] : 
79
                           $this->columnName;
80
      $this->required = isset($definition['required']) ? $definition['required'] : false;
81
      $this->default = isset($definition['default']) ? $definition['default'] : '';
82
      $this->width = isset($definition['width']) ? $definition['width'] : 00;
83
      $this->readOnly = isset($definition['readonly']) ? $definition['readonly'] : false;
84
      if ( $this->default == 'null' ) {
85
         $this->null = true;
86
         $this->default = '';
87
      }
88
      $this->primaryKey = ( isset($definition['keyfield']) ? $definition['keyfield'] : false);
89
   } // function __construct
90
 
91
   function __toString() {
92
      return isset( $this->value ) ? $this->value :  'null';
93
   }
94
 
95
   /*
96
    * function simply returns the column name and the display name
97
    * formatted so as to work correctly in an SQL select statement
98
    * Will also prepend the table name passed in as a parameter 
99
    * (if it exists) to allow explicit definition of the column
100
    * for multi-table queries
101
    */ 
102
   public function getQueryColumn ( $tableName = null, $useDisplayName = false ) {
103
      return array(  'fieldnames'  => ($tableName ? "$tableName." : '' ) 
104
                     . $this->columnName
105
                     . ( $useDisplayName ? " '$this->displayName'" : '' )
106
                     );
107
   } // getQueryColumn
108
 
109
   /* 
110
      function takes a string, and looks for the array names in $values, replacing occurrences
111
      of it with 
112
   */
113
   private function replaceTokens ( $string, $values ) {
114
      foreach( $values as $token => $value ) {
115
         $string = preg_replace("/$token/", $value, $string);
116
      }
117
      return $string;
118
   }
119
 
120
   function makeSafeSQLValue ( $value = null ) {
121
      if ($value) {
122
         $this->value = $value;
123
      }
124
      if (strlen($this->value)) {   // it actually has a value
125
         if(get_magic_quotes_gpc()) {
126
            $this->value = stripslashes($this->value);
127
         }
128
         $this->value = mysql_real_escape_string( $this->value );
129
         $this->value = "'" . $this->value . "'";  // put the quotes around it
130
      } else {
131
         $this->value = ($this->nullable ? 'null' : "''");
132
      }
133
      return $this->value;
134
   }
135
 
136
   function toHTML( $type = 'view', $parameters = '' ) {
137
      $return = '';
138
      switch ( $type ) {
139
        case 'view': $return = "<tr><td>$this->columnName</td><td>$this->value</td></tr>";
140
                     break;
141
        case 'list': $return = '<td>' . $this->value . '</td>';
142
                     break;
143
        case 'edit': $return = "<tr><td>$this->columnName</td><td>$this->value</td></tr>";
144
                     break;
145
      } // switch
146
      return $return;
147
   } // toHTML;
148
 
149
 
150
 
151
}
152
 
153
/* ============================================================================================
154
   class DBColumnBool
155
   Used for multi row columns, ie HTML TextArea's and DB Text columns 
156
*/
157
class DBColumnText extends DBColumn {
158
 
159
} // class DBColumnText
160
 
161
/* ============================================================================================
162
   class DBColumnBool
163
   used for True/False, 1/0, etc...
164
      html display T or F (can be overridden)
165
      Input is a True/False drop down on form input
166
      Stored in a char
167
*/
168
class DBColumnBool extends DBColumn {
169
 
170
   /*
171
    * array containing the values considered as the keys. If '' is included, an empty string is
172
    * considered false also
173
   */ 
174
   public      $falseValues;
175
 
176
   public function __construct ( $columnName, $definitions = null, $value=null) {
177
      parent::__construct( $columnName, $definitions , $value  );     // first call base class
178
      $this->falseValues = isset( $definitions['null values'] ) ? 
179
                           $definitions['null values'] : 
180
                           array ( 'f' => 1, 'F' => 1, 'n' => 1, 'N' => 1, '0' => 1, '' => 1 );
181
   } // function __construct
182
 
183
   public function makeSafeSQLValue ( $value = null ) {
184
      // definition of values which will be interpreted as false
185
      if ($value) {
186
         $this->value = ($value == 'null' ? '' : $value);
187
      }
188
      if ($this->value) {
189
         $this->value = $this->falseValues[$this->value] ? '0' : '1';
190
      } else {
191
         $this->value = ($this->nullable ? 'null' : "'0'");
192
      }
193
      return $this->value;
194
   } // function makeSafeSQLValue
195
 
196
   /*
197
    * override default to return a function that displays "true" or "false"
198
    */ 
199
    public function getQueryColumn ( $tableName = null, $useDisplayName = false ) {
200
      $columnName = ($tableName ? "$tableName." : '' ) . $this->columnName;
201
      return array(  'fieldnames'  => "if ($columnName = 1, 'true','false')" .
202
                     ( $useDisplayName ? " '$this->displayName'" : '' )
203
                  );
204
   } // getQueryColumn
205
 
206
} // class DBColumnBool
207
 
208
/* ============================================================================================
209
   class DBColumnDate
210
   holds a date only (ie, no time)
211
      html display is yyyy-mm-dd (can be overridden)
212
      input uses advanced library
213
      stored in a date
214
*/
215
class DBColumnDate extends DBColumn {
216
 
217
   function makeSafeSQLValue ( $value = null ) {
218
      if ($value) {
219
         $this->value = ($value == 'null' ? '' : $value);
220
      }
221
      if ( $result = strtotime( $value ) ) {
222
         $this->value = "'" . Date( 'Y-m-d', $result) . "'";
223
      } else {
224
         $this->value = ($this->nullable ? 'null' : "'0000-00-00'");
225
      }
226
      return $this->value;
227
   }
228
 
229
} // class DBColumnDate
230
 
231
/* ============================================================================================
232
   class DBColumnDateTime
233
   holds a date time stamp
234
      html display is yyyy-mm-dd hh:mm:ss (can be overridden)
235
      input uses advanced library
236
      stored in datetime
237
*/
238
class DBColumnDateTime extends DBColumn {
239
 
240
   function makeSafeSQLValue ( $value = null ) {
241
      if ($value) {
242
         $this->value = ($value == 'null' ? '' : $value);
243
      }
244
      if ( $result = strtotime( $this->value ) ) {
245
         $this->value = "'" . Date( 'Y-m-d H:i:s', $result) . "'";
246
      } else {
247
         $this->value = ($this->nullable ? 'null' : "'0000-00-00 00:00:00'");
248
      }
249
      return $this->value;
250
   }
251
 
252
 
253
 
254
} // class DBColumnDateTime
255
 
256
/* ============================================================================================
257
   class DBColumnInt
258
   holds an integer
259
      html display is right justified
260
      input verifies numerics only
261
      stored in int (may be stored in int unsigned)
262
*/
263
class DBColumnInt extends DBColumn {
264
   public      $range;                 // if defined, number must be within this range
265
   public      $signed = true;        // if false, uses int unsigned
266
 
267
   public function __construct( $columnName, $definitions = null, $value = null) {
268
      parent::__construct( $columnName, $definitions, $value );     // first call base class
269
      $this->range['min'] = isset( $definitions['min'] ) ? $definitions['min'] : null;
270
      $this->range['max'] = isset( $definitions['max'] ) ? $definitions['max'] : null;
271
      $this->signed = isset( $definitions['signed'] ) ? $definitions['signed'] : true;
272
   } // function __construct
273
 
274
   function makeSafeSQLValue ( $value = null ) { // simply remove all non-numerics
275
      if ($value) {
276
         $this->value = $value == 'null' ? '' : $value;
277
      }
278
      $this->value = preg_replace( '/[^0-9]/', '', $this->value );
279
      if ( ! strlen($this->value) ) {
280
         $this->value = $this->nullable ? 'null' : '0';
281
      }
282
      return $this->value;
283
   }
284
 
285
} // class DBColumnInt 
286
 
287
/* ============================================================================================
288
   class DBColumnReal
289
   holds a floating point number
290
      html display is right justified
291
      may be padded
292
      input verfies floating point number
293
      stored in float
294
*/
295
class DBColumnReal extends DBColumnInt {
296
   public      $decimalPlaces;
297
 
298
   public function __construct( $columnName, $definitions = null, $value = null) {
299
      parent::__construct( $columnName, $definitions, $value );     // first call base class
300
      $this->decimalPlaces = $definition['decimal places'] ? $definition['decimal places'] : 0;
301
   } // function __construct
302
 
303
   function makeSafeSQLValue ( $value = null ) { // same as int version, but allows periods
304
      if ($value) {
305
         $this->value = $value = 'null' ? '' : $value;
306
      }
307
      $this->value = preg_replace( '/[^0-9.]/', '', $this->value );
308
      if ( ! strlen($this->value) ) {
309
         $this->value = $this->nullable ? 'null' : '0';
310
      }
311
      return $this->value;
312
   }
313
 
314
} // class DBColumnReal
315
 
316
/* ============================================================================================
317
   class DBColumnPassword
318
   holds a hash of a password
319
      HTML display is a series of 8 stars
320
      html input is type='password'
321
      stored in a char(32). This is the MD5 sum of the password
322
*/
323
class DBColumnPassword extends DBColumn {
324
 
325
   function makeSafeSQLValue ( $value = null ) {
326
      parent::__makeSafeSQLValue( $value ); // first, process as a string
327
       // then, set it to call the MD5 function MySQL, PostgreSQL, Oracle. MS SQL-Server does
328
       // not have this function
329
      $this->value = 'MD5(' . $this->value . ')';
330
   }
331
 
332
   function __toString() {
333
      return '****************';
334
   } // function __toString override
335
 
336
   /*
337
    * override default to return just a string of 8 asterisks
338
    */ 
339
    public function getQueryColumn ( $tableName = null, $useDisplayName = false ) {
340
      return array(  'fieldnames'  => "'********'" .
341
               ($useDisplayName ? " '$this->displayName'" : '' )
342
               );;
343
   } // getQueryColumn
344
 
345
 
346
} // class DBColumnPassword
347
 
348
/* ============================================================================================
349
   class DBColumnFile
350
   holds file
351
      html display is file name, click to download
352
      input is type='file'
353
      stored either in a blob, or in a varchar as the filename on disk
354
*/
355
class DBColumnFile extends DBColumn {
356
   public      $viewable; // if true, can be displayed on screen, ie pictures
357
   // if false, this is a blob column which contains the file. If true, it is a varchar which 
358
   // contains the path
359
   public      $onDisk = true;
360
 
361
   public function __construct( $columnName, $definitions = null, $value = null) {
362
      parent::__construct( $columnName, $definitions, $value );     // first call base class
363
      $this->viewable = $definition['viewable'];
364
   } // function __construct
365
 
366
} // class DBColumnFile
367
 
368
/* ============================================================================================
369
   class DBColumnLookup
370
   Holds a foreign key value
371
      html display is target entry
372
      input is a dropdown
373
      stored as an int unsigned foreign key (tablename.primary key)
374
  The display will be a drop down box which will be filled with display values from the foreign
375
  table
376
  Basically, will generate the query
377
   select $this->$remotePrimaryKey,$this->remoteDisplayField 
378
   from $this->remoteTableName 
379
   where $this->filter
380
   sort by $this->sortOrder
381
 
382
  where
383
   '$this->filter' will only be used if it is non-null (otherwise, there will be no 
384
          where clause)
385
   'sort by $this->sortOrder' will only be used if $sortOrder is non-null (otherwise, 
386
         there will be no sort by)
387
   $remoteDisplayField must resolve to a single column in the query result, ie concat(field1,
388
         field2), not field1,field2
389
 
390
  The <select> box will have the remote field highlighted that corresponds to $this->value
391
  If $this->value is null and this is a nullable field, the special text "No Value" 
392
     is added to the <select> box
393
  If $this->value is null and this is not a nullable field, the first item in the drop down 
394
     will be selected.
395
*/
396
class DBColumnLookup extends DBColumn {
397
   // the actual table name in the database used for lookups
398
   protected   $remoteTableName;
399
   // column name in $remoteTableName that matches this columns value
400
   protected   $remotePrimaryKey;
401
   // a string used in select statement to build a display
402
   protected   $remoteDisplayField; 
403
   // if set, will generate an additional where clause (anded) to limit the display of fields
404
   protected   $filter;   
405
   // if set, will generate a sort by clause to determine display order          
406
   protected   $sortOrder;
407
   // used for self-referential joins
408
   protected   $remoteTableAlias;
409
 
410
   public function __construct( $columnName, $definitions = null, $value = null) {
411
      parent::__construct( $columnName, $definitions, $value );     // first call base class
412
      $this->remoteTableName = isset( $definitions['table'] ) ?
413
                                 $definitions['table'] : null;
414
      $this->remotePrimaryKey = isset($definitions['keyfield']) ? 
415
                                 $definitions['keyfield'] : null;
416
      $this->remoteDisplayField = isset( $definitions['display_field']) ? 
417
                                 $definitions['display_field'] : null;
418
      $this->remoteTableAlias = isset( $definitions['table alias'] ) ? 
419
                                 $definitions['table alias'] : null;
420
      $this->filter = isset( $definitions['filter'] ) ? $definitions['filter'] : null;
421
      $this->sortOrder = isset( $definitions['sort by'] ) ? $definitions['sort by'] : null;
422
   } // function __construct
423
 
424
   /*
425
    * override default to return a function that displays "true" or "false"
426
    */ 
427
   public function getQueryColumn ( $tableName = null, $useDisplayName = false ) {
428
      $return = array();
429
      if ( $tableName == $this->remoteTableName ) {
430
         // this is a self-referential table!! Everything must be done via an alias
431
         // verify an alias has been created
432
         if ( ! isset( $this->remoteTableAlias ) )
433
            throw new Exception ("DBColumnLookup->getQueryColumn has self referential table, " .
434
                                 "but 'table alias' not defined for " .
435
                                 "$tableName.$this->columnName" );
436
         $return['from']["$this->remoteTableName as $this->remoteTableAlias"]['on'] = 
437
               "$tableName.$this->columnName = $this->remoteTableAlias.$this->remotePrimaryKey";
438
         $return['fieldnames'] = 
439
               "$this->remoteTableAlias.$this->remoteDisplayField '$this->displayName'";
440
      } else { // standard join
441
         $return['from'][$this->remoteTableName]['on'] = 
442
               "$tableName.$this->columnName = $this->remoteTableName.$this->remotePrimaryKey";
443
         $return['fieldnames'] = 
444
               "$this->remoteTableName.$this->remoteDisplayField" .
445
                     ( $useDisplayName ? " '$this->displayName'" : '' );
446
      }
447
      return $return;
448
   } // getQueryColumn
449
 
450
} // class DBColumnLookup
451
 
452
/* ============================================================================================
453
   class DBColumnManyToMany
454
   simply indicates a one to many relationship
455
      HTML display is a list of remote table values
456
      input is a multi-select
457
      stored as a secondary table with 
458
         one set of columns containing the primary key of this table
459
         second set of column containing the primary key of a second table.
460
*/
461
class DBColumnManyToMany extends DBColumn {
462
} // class DBColumnManyToMany
463
 
464
 
465
 
466
/*
467
 * A table is made up of columns, has relationships with other tables,,
468
 * has an array (possibly null) of columns that make up the primary key
469
 * the queries property is an array of arrays. The array has (currenty)
470
 * two possible values, list and record. Each row contains an 
471
 * associative array with rows
472
 *   fieldnames - array of field names for select clause
473
 *   from       - array of tables to select from
474
 *   where      - array of clauses for the where part of the query
475
 *   order      - array of field names used for order by
476
 * the form of the generated query is
477
 * select {fieldnames} from {tables} where {where} order by {order}
478
 * See makeQueryDefinition for the method used to dynamically create this
479
 * NOTE: from is a complex array. It keys off of the table name, and 
480
 * optionally has two sub-arrays, 'join' and 'on'. 'join' gives the 
481
 * type of join (default is left outer) and on gives the conditions. The
482
 * on conditions are exactly as placed in the parentheses, ie 
483
 * fieldname=fieldname
484
 * 
485
 * $columns is an indexed array of DBColumn or one of its descendan
486
 * classes. Thus, $columns['columnname']->_toString() will have different
487
 * output depending on the actual type of the underlying object.
488
*/
489
class DBTable {
490
   public    $tableName;      // name of the table in the database
491
   public    $columns;        // array of columns in the table
492
   public    $displayName;    // display name of the table (human readable)
493
   public    $displayColumns; // list of columns which should be displayed. DEPRECATED
494
   public    $displayQuery;   // query for creating a list. DEPRECATED
495
   public    $queries;        // array of queries for list and record
496
   public    $currentRecords; // contains the query, key values and data returned from a query
497
 
498
 
499
   /*
500
    * constructor. Accepts multiple parameters and creates an instance
501
    */ 
502
   public function __construct( $tableName, $definition ) {
503
      $this->tableName = $tableName;
504
      // if a display name is not passed in, use the table name
505
      $this->displayName = ($definition['display name'] ? 
506
                           $definition['display name'] : $tableName );
507
      /*
508
       *  if a list of display columns is not passed in, use the column names from the
509
       * column definition
510
      */
511
      $this->displayColumns = ( $definition['display columns'] ? 
512
                  $definition['display columns'] : array_keys($definition['field info']) );
513
      // if a display query is not passwd in, just do a select *
514
      $this->displayQuery = ($definition['display query'] ? 
515
                  $definition['display query'] : "select * from $this->tableName" );
516
      // get the column definitions
517
      $this->columnDefinitionsArrayToObject( $definition['field info'] );
518
      $this->queries = isset($definition['queries']) ? $definition['queries'] : array();
519
      $this->currentRecords = null;
520
   } // function __construct
521
 
522
 
523
 
524
   public function getRecord( $keys ) {
525
      print "<p>In getRecord, keys are</p><pre>";  print_r( $keys ); print "</pre>";
526
      $this->loadFromDatabase( $keys );
527
      //$this->currentRecords['query'] = $this->makeWhereClause($keys);
528
      //$this->currentRecord = new DBRecord( $this, $keys );
529
      //$this->currentRecord->loadFromDatabase();
530
   } // function getRecord
531
 
532
 
533
   public function makeWhereClause ( $keys ) {
534
      $return = array();
535
      foreach ( $keys as $keyField => $value ) {
536
         if ( isset( $this->columns[$keyField] ) ) {
537
            $return[] = "$keyField = '$value'";
538
         }
539
      }
540
      return ' where ' . implode( ' and ', $return );
541
   }
542
 
543
 
544
   /* 
545
    * loads record from database. either $this->keyValues or the
546
    * parameter $keyValues must contain enough information to uniquely
547
    * identify this record.
548
    * Upon successful query, the primary keys are stored in 
549
    * $this->keyValues, and an array of all returned columns
550
    * is placed in $this->values.
551
    */ 
552
   public function loadFromDatabase ( $keys, $searchValues = null ) {
553
 
554
      if ( ! isset( $keys ) ) {
555
         throw new Exception ( 'in DBRecord->loadFromDatabase, no record has been' . 
556
                                 ' loaded and no key fields have been defined' );
557
      } else {
558
         require_once( 'DBQuery.class.php' );
559
         $this->currentRecords['query'] = $this->SQLQuery( 'record' ) . 
560
                                          $this->makeWhereClause($keys);
561
         $this->currentRecords['query'] = new DBQuery( $this->currentRecords['query'] );
562
         $this->currentRecords['query']->useAssociativeArray = true;
563
         try {
564
            if ( $this->currentRecords['query']->run() ) {
565
               if ( $this->currentRecords['query']->rowsAffected == 1 ) {
566
                  // if ( DEBUG ) print_r( $query);
567
                  foreach ($this->currentRecords['query']->returnData[0] as 
568
                           $thisColumn => $thisValue ) {
569
                     $this->currentRecords['data'][$thisColumn]->value = $thisValue;
570
                  }
571
               } else {
572
                  throw new Exception ('DBRecord->loadFromDatabase returned ' . 
573
                                       $info['count'] . ' rows, expected 1, query was ' . 
574
                                       $this->table->getSelectQuery( 'record',  
575
                                                                  $this->keyValues ) );
576
               }
577
            } else {
578
               print "Error executing query $temp\n$query->error\n";
579
            } // if $query->run .. else
580
         } catch (  Exception $e ) {
581
            print "Error executing query $temp\n$query->error\n";
582
         }
583
 
584
      } // if..else
585
   } // loadFromDatabase
586
 
587
 
588
   /*
589
    * iterates through an array. For each row in the array, determines
590
    * the type of the column and generates an instance of the 
591
    * appropriate class for it, adding it to the $columns property of
592
    * this class.
593
    */
594
   public function columnDefinitionsArrayToObject ( $arr ) {
595
 
596
      foreach ( $arr as $columnName => $definitions ) {
597
         switch ( $definitions['type'] ) {
598
            //case 'string'  :
599
            case 'text'    : $thisColumn = new DBColumnText($columnName, $definitions );
600
                              break;
601
            case 'date'    : $thisColumn = new DBColumnDate($columnName, $definitions );
602
                              break;
603
            case 'datetime': $thisColumn = new DBColumnDateTime($columnName, $definitions );
604
                              break;
605
            case 'int'     : $thisColumn = new DBColumnInt($columnName, $definitions );
606
                              break;
607
            case 'bool'    : $thisColumn = new DBColumnBool($columnName, $definitions );
608
                              break;
609
            case 'real'    : $thisColumn = new DBColumnReal($columnName, $definitions );
610
                              break;
611
            case 'password': $thisColumn = new DBColumnPassword($columnName, $definitions );
612
                              break;
613
            case 'file'    : $thisColumn = new DBColumnFile($columnName, $definitions );
614
                              break;
615
            case 'lookup'  : $thisColumn = new DBColumnLookup($columnName, $definitions );
616
                              break;
617
            case 'multi'   : $thisColumn = new DBColumnManyToMany($columnName, $definitions );
618
                              break;
619
            default        : $thisColumn = new DBColumn( $columnName, $definitions );
620
         } // switch
621
         $this->columns[$thisColumn->columnName] = $thisColumn;
622
      } // foreach
623
   } // function arrayToObject
624
 
625
   /*
626
    * creates a query by iterating through all columns in the current
627
    * table definition. Then stores the resulting query in property
628
    * $queries[$type]
629
    * WARNING: overwrites any existing query of the type requested
630
    */
631
   private function makeQueryDefinition( $type = 'list' ) {
632
      // insert the primary table name
633
      $this->queries[$type]['from'][$this->tableName] = array( );
634
      // process each column defined
635
      foreach (  $this->columns as  $columnName => $columnDefinition ) {
636
         // let the column tell us what should go here
637
         $temp = $columnDefinition->getQueryColumn( $this->tableName );
638
         // that returns an aray with fieldnames, where, etc..
639
         // so process each returned value
640
         foreach ( $temp as $key => $value ) {
641
            if ( $key == 'from' ) {
642
               foreach ($temp['from'] as $table => $definition ) {
643
                  $this->queries[$type][$key][$table] = $definition;
644
               }
645
            } else {
646
               $this->queries[$type][$key][] = $value;
647
            }
648
         } // foreach
649
      } // foreach
650
   } // makeQueryDefinition
651
 
652
 
653
   /*
654
    * Reads the queries structure and returns a standard
655
    * SQL Query.
656
    * $additionalClauses is an array that may contain the keys
657
    * 'fieldnames', 'from', 'where' and 'order' which will be
658
    * appended to the appropriate entries in queries structure
659
    */
660
 
661
   public function SQLQuery( $type= 'list', $additionalClauses = null ) {
662
      // if the particular type of query has not been defined, do so
663
      if ( ! ( isset( $this->queries) and count($this->queries) ) )
664
         $this->makeQueryDefinition( $type );
665
      // now, merge $this->queries and $additionalClauses
666
 
667
 
668
      foreach ( array( 'fieldnames', 'from', 'where', 'order' ) as $index ) {
669
         if ( isset( $additionalClauses[$index] ) 
670
               and count( $additionalClauses[$index] ) 
671
               and isset( $this->queries[$type][$index] ) 
672
               and count( $this->queries[$type][$index] ) ) {
673
           $additionalClauses[$index] = array_merge( 
674
                                                      (array)$this->queries[$type][$index], 
675
                                                      (array)$additionalClauses[$index] );
676
         } elseif ( isset( $this->queries[$type][$index] ) 
677
                     and count( $this->queries[$type][$index] ) ) {
678
           $additionalClauses[$index] = $this->queries[$type][$index];
679
         } // if, note that if $this doesn't exist then it simply takes on the 
680
           //value of $addtionalClauses
681
      } // foreach
682
 
683
      $query = 'select ' . implode( ",\n   ", $additionalClauses['fieldnames'] );
684
      $from = '';
685
      foreach ( $additionalClauses['from'] as $table => $join ) {
686
         if ( $from ) {
687
            $from = " ($from)\n    ";
688
            $from .= (isset( $join['join'] ) ? $join['join'] : 'left outer join');
689
         }
690
         $from .= " $table ";
691
 
692
         $from .= ( ( isset( $join['on'] ) && $join['on']) ? 
693
                  " on ( " . $join['on'] . ") " : '' );
694
      } // foreach
695
      $query .= "\nfrom $from\n";
696
      if (isset( $additionalClauses['where'] ) and count( $additionalClauses['where'] ) ) 
697
         $query .= 'where ' . implode( "\n   and " ,  $additionalClauses['where'] );
698
      if (isset( $additionalClauses['order'] ) and count( $additionalClauses['order'] ) ) 
699
         $query .= ' order by ' . implode( ",\n   ",  $additionalClauses['order'] );
700
 
701
      return $query;
702
   } // SQLQuery
703
 
704
   /*
705
    * function returns as an array all data returned by a query.
706
    * The returned array is an indexed array of associativeArray, 
707
    * with a row containing an associative array of data, where keys 
708
    * are the field names and the value is the value.
709
    * The outer array is indexed in order returned by the query, so
710
    * order by clauses in the SQL are honored
711
    */ 
712
   public function toArray( $parameters = null ) {
713
      $query = $this->SQLQuery( 'list', $parameters );
714
      $this->currentRecords['query'] = $query;
715
      return $this->currentRecords;
716
      $queryResults = new DBQuery( $query );
717
      $queryResults->useAssociativeArray = true;
718
      $queryResults->run();
719
      if ( DEBUG ) $_SESSION['debug'][] = $query;
720
      $this->currentRecords = $queryResults->returnData;
721
      return $this->currentRecords;
722
   }
723
 
724
   /*
725
    * function will generate two types of return. If "id" is passed in
726
    * it is assumed to be an array of values that will be used to select
727
    * a unique row from the table, in which case a new DBRecord is created
728
    * and it's toHTML is called.
729
    * 
730
    * If $id is empty, will return a list of all records in the table
731
    * based on the display record entry for this table. It calls
732
    * DBQueryHTML:queryToTable on that instance to generate the table
733
    */ 
734
   function toHTML( $type = 'view', $parameters = '' ) {
735
      $return = '';
736
      switch ( $type ) {
737
        case 'view': $return = "<tr><td>$this->columnName</td><td>$this->value</td></tr>";
738
                     break;
739
        case 'list': $queryResults = new DBQuery( $this->displayQuery, $parameters['where'], $parameters['order'] );
740
                     $queryResults->run();
741
                     if ( $queryResults->rowsAffected ) {
742
                        foreach ( $queryResults->returnData as $key => $row ) {
743
                           $return .= '<tr>';
744
                           foreach ( $row as $fieldName => $value ) {
745
                              $thisColumn = $this->columns[$fieldName];
746
                              $thisColumn->value = $value;
747
                              $return .= $thisColumn->toHTML('list', $parameters['link']);
748
                              //$thisColumn->toHTML( 'list' ),true );
749
                           } // foreach
750
                           $return .= '</tr>';
751
                        } // foreach
752
                     } // if
753
                     $return = '<table>' . $return . '</table>';
754
 
755
                     //new DBQueryHTML( $this->displayQuery );
756
                     //return print_r($queryResults,true);
757
//                    return $queryResults->queryToTable('',true,"<a href=" . $_SERVER['PHP_SELF'] . 
758
//                                                "?table=$this->tableName&id=%id%>View</a>");
759
                     break;
760
        case 'edit': $return = "<tr><td>$this->columnName</td><td>$this->value</td></tr>";
761
                     break;
762
      } // switch
763
      return $return;
764
   } // toHTML;
765
 
766
 
767
/*   function toHTML( $type = 'view' ) {
768
      if ( isset( $id ) && $id ) {
769
         $theRow = new DBRecord( $this, array( 'id' => $id ) );
770
         return $theRow->toHTML();
771
      } else {
772
         $queryResults = new DBQueryHTML( $this->displayQuery );
773
         return $queryResults->queryToTable('',true,"<a href=" . $_SERVER['PHP_SELF'] . 
774
                                                "?table=$this->tableName&id=%id%>View</a>");
775
      } // if..else
776
   } // function toHTML
777
*/
778
} // class DBTable
779
 
780
 
781
class DBDatabase {
782
   public $databaseName;
783
   protected $tables;
784
   public $defaultTable;
785
 
786
   public function __construct( $databaseName, $tableDefinitionArray = null) {
787
      $this->databaseName = $databaseName;
788
      if ( $tableDefinitionArray ) $this->loadTables ( $tableDefinitionArray );;
789
      $this->defaultTable = DEFAULT_TABLE;
790
   } // function __construct
791
 
792
   /*
793
    * function accepts an array of table definitions. For each element
794
    * in the array, creates a new DBTable and inserts it into the 
795
    * $tables property
796
   */
797
   public function loadTables ( $tableDefinitionArray ) {
798
      foreach ( $tableDefinitionArray as $tableName => $definition ) {
799
         $this->tables[$tableName] = new DBTable($tableName, $definition );
800
      }
801
   } // function loadTables
802
 
803
   /*
804
    * Function accepts a string name of a table, and returns the 
805
    * appropriate DBTable instance from the tables array
806
   */ 
807
   public function getTable( $name ) {
808
      return ( $this->tables[$name] ? $this->tables[$name] : null );
809
   }
810
 
811
    /*
812
     * Function which returns a very basic HTML element to the caller
813
     * which contains an unordered list. Each element of that list
814
     * is the name of a table, and a link to the calling program
815
     * with the name of the table as a parameter
816
   */
817
   function toHTML (  ) {
818
      $output = '<ul>';
819
      foreach ( $this->tables as $name  => $values ) {
820
         $output .= "<li><a href=" . $_SERVER['PHP_SELF'] . "?table=$name>$name</a></li>"; 
821
      }
822
      $output .= '</ul>';
823
      return $output;
824
   } // toHTML
825
 
826
   /*
827
    * function returns an array with the names of all tables in the
828
    * database definition
829
    */
830
 
831
   public function toArray() {
832
      return array_keys( $this->tables );
833
   }
834
 
835
   /*
836
    * function returns all table names in a space separated one line
837
    * string
838
    */ 
839
   public function __toString() {
840
      return implode( ' ', $this->toArray() );
841
   }
842
 
843
   /* 
844
    * function will create a quick and dirty set of edit/update screens, allowing untrained 
845
    * user to modify database files will give list of tables as links. If link is clicked, 
846
    * will display tabular list of table contents. table contents list will have edit/delete 
847
    * buttons, and an Add button at the top. If edit button is clicked, will show one row of
848
    * information, allow user to modify it, then update the database
849
    * If add button is clicked, will show one row of information, with defaults filled in, 
850
    * then values will be inserted into database
851
   */
852
   public function doAdmin( $returnPage = 'admin.php' ) {
853
 
854
      $html = "<ul class='db_database_admin_tablelist'>\n";
855
      foreach ( $this->tables as $tableEntry ) {
856
         $html .= "<li><a href='$returnPage?command=showtable&tablename=".
857
                  "$tableEntry->tableName'>$tableEntry->displayName</a></li>\n";
858
      }
859
      $html .= "</ul>";
860
      return $html;
861
   }
862
} // class DBDatabase
863
 
864
 
865
?>
866
 
867