Subversion Repositories php_users

Rev

Rev 17 | Rev 21 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
4 rodolico 1
<?php
2
 
3
/*
7 rodolico 4
   Copyright (c) 2021, Daily Data, Inc. Redistribution and use in 
5
   source and binary forms, with or without modification, are permitted
6
   provided that the following conditions are met:
7
 
8
   * Redistributions of source code must retain the above copyright 
9
     notice, this list of conditions and the following disclaimer.
10
   * Redistributions in binary form must reproduce the above copyright 
11
     notice, this list of conditions and the following disclaimer in the 
12
     documentation and/or other materials provided with the distribution.
13
 
14
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
15
   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17
   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18
   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19
   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
20
   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
 
26
*/
27
 
28
/*
29
 * UsersDataSourceMySQLi.class.php
30
 * 
31
 * Authors: R. W. Rodolico
32
 * 
4 rodolico 33
 */
34
 
7 rodolico 35
/**
36
 * usersDataSource class
37
 * 
38
 * usersDataSource provides the data access capabilities for the Users
39
 * class.
40
 * 
41
 * To build a data access class for Users, the following 5 methods must
42
 * exist.
43
 * getPassword(username)
44
 * getRecord(username)
45
 * getAllUsers()
46
 * getARecord
47
 * update
48
 * 
49
 * Additionally, where appropriate, the following function is useful
50
 * buildTable()
51
 * 
52
 * This particular instance provides an interface to MySQL using
53
 * the mysqli libraries.
54
 * 
55
 * Create an instance of this, then pass the variable to several Users
56
 * calls.
57
 * 
58
 * @author R. W. Rodolico <rodo@unixservertech.com>
59
 * 
60
 * @version 0.9.0 (beta)
61
 * @copyright 2021 Daily Data, Inc.
62
 * 
63
 */
64
 
4 rodolico 65
class usersDataSource {
66
 
7 rodolico 67
   /**
16 rodolico 68
    * @var string[] $configuration Contains the configuration for the class
7 rodolico 69
    * 
70
    * May be modified by the calling program. Must be replicated in userDataSource class
71
    */
16 rodolico 72
   protected $configuration = array(
4 rodolico 73
      'tables' => array(
74
         'users'  => array(
75
            'table'     => '_users',   // table name for user records
76
            'id'        => '_user_id', // ID column name
77
            'display'   => array(      // fields which are displayed to select
78
               'login'
79
               ),
80
            'password'  => array(      // These fields are stored encrypted
81
               'pass'
82
               ),
83
            'fields' => array(
84
               'login'  => array(
85
                     'dbColumn'  =>  'login',       // login name column name
86
                     'type'      => 'varchar',
87
                     'size'      => 64,
10 rodolico 88
                     'required'  => true
4 rodolico 89
                     ),
90
               'pass'   => array( 
91
                     'dbColumn'  => 'password',    // password column name
92
                     'type'   => 'varchar',
93
                     'size'      => 128,
10 rodolico 94
                     'required'  => true
4 rodolico 95
                     ),
96
               'admin'  => array(
97
                     'dbColumn'  => 'isAdmin',
98
                     'type'      => 'boolean',
99
                     'required'  => true,
100
                     'default'   => '0'
101
                     ),
102
               'enabled'   => array(
103
                     'dbColumn'  => 'enabled',
104
                     'type'      => 'boolean',
105
                     'required'  => true,
106
                     'default'   => '1'
107
                     )
108
               )
109
            )
110
         )
111
      );
7 rodolico 112
      /** @var mysqli $dbConnection Holds the mysqli database connection */
16 rodolico 113
      protected $dbConnection = false;
4 rodolico 114
 
7 rodolico 115
      /**
116
       * constructor for an instance of the class
117
       * 
118
       * If $dbConnection is not null, will be used for database access
119
       * If $dbLoginInfo is not null, will override $dbConnection, make
120
       * a new connection and use that.
121
       * 
16 rodolico 122
       * If $dbDef is set, will be merged with $configuration
7 rodolico 123
       * 
124
       * @param mysqli $dbConnection Existing mysqli database connection
16 rodolico 125
       * @param string[] $dbDef Array to be merged with $configuration
7 rodolico 126
       * @param string[] $dbLoginInfo Array containing username, hostname, etc.. to make mysqli connection_aborted
127
       * 
128
       * @return null
129
       * 
130
       */
4 rodolico 131
      public function __construct( $dbConnection = null, $dbDef = array(), $dbLoginInfo = array() ) {
132
         $this->dbConnection = $dbConnection;
133
         if ( $dbDef ) {
16 rodolico 134
            $this->configuration = array_merge_recursive( $this->configuration, $dbDef );
4 rodolico 135
         }
136
         if ( $dbLoginInfo ) {
137
            $this->setDBConnection( $dbLoginInfo );
138
         }
139
      }
140
 
7 rodolico 141
      /**
142
       * Make string safe for MySQL
143
       * 
144
       * If the string is completely numeric, returns it, otherwise 
145
       * puts single quotes around it
146
       * 
147
       * @param string $string The string to be fixed
148
       * @return string A copy of the string, ready for SQL
149
       */
16 rodolico 150
      protected function escapeString ( $string ) {
17 rodolico 151
         if ( ! is_numeric( $string ) ) {
152
            $string = $this->dbConnection->real_escape_string( $string );
4 rodolico 153
            $string = "'$string'";
17 rodolico 154
         }
4 rodolico 155
         return $string;
156
      }
157
 
158
      /**
7 rodolico 159
       * Create a query to retrieve info from database
4 rodolico 160
       * 
161
       * Builds a query to retrieve records from the database. With all
162
       * parameters set to null, will retrieve all columns and records
163
       * Setting $field and $toFind create a where clause, and setting
164
       * $fieldList as a has (ie, 'fieldname' => 1) will limit the 
165
       * fields returned
166
       * 
16 rodolico 167
       * @param string $field A valid field definition from $configuration
4 rodolico 168
       * @param string $toFind The string to find, ie where $field = $username
169
       * @param string[] $fieldList a hash where the keys make a list of columns to return. If empty, returns all columns
170
       * 
171
       * @return string A cleaned and formatted SQL Query
172
       * 
173
      */
16 rodolico 174
      protected function buildQuery( $whereFields, $fieldList = null ) {
4 rodolico 175
         // always get the ID field
16 rodolico 176
         $fields = array( $this->configuration['tables']['users']['id'] . ' id');
4 rodolico 177
         // Get the rest of the available fields
16 rodolico 178
         foreach ( $this->configuration['tables']['users']['fields'] as $key => $value ) {
4 rodolico 179
            // do not use this one if $fieldList doesn't have it
180
            if ( ! isset( $fieldList ) || isset( $fieldList[$key] ) )
181
               $fields[] = $value['dbColumn'] . ' ' . $key;
182
         }
183
         // Change it into something SQL can handle
184
         $query = implode( ',', $fields );
185
         // now, build the rest of the query
16 rodolico 186
         $query = "select $query from " . $this->configuration['tables']['users']['table'];
4 rodolico 187
         if ( isset( $whereFields ) ) {
188
            $temp = array();
189
            foreach ( $whereFields as $key => $value ) {
190
               $temp[] = ( 
191
                  $key == 'id' ? 
16 rodolico 192
                  $this->configuration['tables']['users']['id'] : 
193
                  $this->configuration['tables']['users']['fields'][$key]['dbColumn']
4 rodolico 194
                  ) . '= ' . $this->escapeString( $value );
195
            }
196
            $query .= ' where ' . implode( ' and ', $temp );
197
         }
198
         return $query;
199
      }
200
 
201
      /**
202
       * Get a record from the database
203
       * 
204
       * Gets a single record from the database which matches $field containing
205
       * $username. If more than one record is returned, will return the first
206
       * one
207
       * 
208
       * @param string[] $whereFields column=>value pairs for where clause
209
       * @param string[] $fieldList a list of columns to return. If empty, returns all columns
210
       * 
7 rodolico 211
       * @return string[] a hash containing fieldname=>value pairs from fetch_assoc
4 rodolico 212
       * 
213
      */
214
      public function getARecord( $whereFields, $fieldList = null ) {
215
         // run the query, placing value in $result
216
         mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
16 rodolico 217
         $result = $this->doSQL( $this->buildQuery( $whereFields, $fieldList ) );
4 rodolico 218
         if ( $result ) { // got one, so return it
219
            return $result->fetch_assoc();
220
         }
221
         // WTFO? nothing, so return empty array
222
         return array();
223
      }
224
 
7 rodolico 225
      /**
226
       * Retrieves the password field from table
227
       * 
228
       * Note that the password is stored as a hash in the table
229
       * 
230
       * @param string $username username used to find record
231
       * @return string[] an array of values key/value pairs
232
       */
4 rodolico 233
      public function getPassword( $username ) {
234
         return $this->getARecord( array('login' => $username,'enabled' => 1), array('pass' => 1 ) );
235
      }
236
 
7 rodolico 237
      /**
238
       * Make the database connection
239
       * 
240
       * @param string[] $parameters Parameters for makeing the connection
241
       * @return mysqli|false
242
       */
16 rodolico 243
      protected function setDBConnection ( $parameters ) {
4 rodolico 244
         if ( !isset($parameters['username'], $parameters['password'],$parameters['database']  )) {
245
            return false;
246
         }
247
         if ( !isset( $parameters['host'] ) ) {
248
            $parameters['host'] = 'localhost';
249
         }
250
         mysqli_report( MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT );
251
         $this->dbConnection = new mysqli( $parameters['host'], $parameters['username'], $parameters['password'],$parameters['database'] );
252
      }
253
 
254
      /**
16 rodolico 255
       * Convenience function to create the tables defined in $configuration
4 rodolico 256
       * 
16 rodolico 257
       * Using $configuration, build the table (replacing the current one)
4 rodolico 258
       * then add $username with $password, setting as admin
259
       * 
260
      */
16 rodolico 261
      public function buildTable( ) {
4 rodolico 262
         if ( $this->dbConnection ) {
16 rodolico 263
            foreach ( $this->configuration['tables'] as $table => $tableRecord ) {
264
               $fields = array( $tableRecord['id'] . ' int unsigned not null auto_increment' );
265
               foreach ( $tableRecord['fields'] as $key => $record ) {
266
                  $fieldDef = $record['dbColumn'];
267
                  $fieldDef .= ' ' . $record['type'];
268
                  if ( isset( $record['size'] ) ) {
269
                     $fieldDef .= '(' . $record['size'] . ')';
270
                  }
271
                  if ( isset( $record['required'] ) ) {
272
                     $fieldDef .= $record['required'] ? ' not null ' : '';
273
                  }
274
                  if ( isset( $record['default'] ) ) {
275
                     $fieldDef .= sprintf( " default '%s'", $record['default'] );
276
                  }
277
                  if ( isset( $record['comment'] ) ) {
278
                     $fieldDef .= "comment '" . $record['comment'] . "'";
279
                  }
280
                  $fields[] = $fieldDef;
4 rodolico 281
               }
16 rodolico 282
               $fields[] = 'primary key (' . $tableRecord['id'] . ')';
283
               $query = implode( ',', $fields );
284
               $query = 'create or replace table ' . $tableRecord['table'] .
285
                     "($query)";
286
               $this->doSQL( $query );
4 rodolico 287
            }
16 rodolico 288
         } // foreach table
289
      } // buildTable
290
 
291
      /**
292
       * Convenience function to initialize tables to values
293
       * 
294
       * @param string[] $initValues Array of tablenames, column names and values
295
       * 
296
      */
297
      public function initTables ( $initValues ) {
298
         foreach ( $initValues as $table => $fieldDef ) {
299
            $columns = array();
300
            $values = array();
301
            foreach ( $fieldDef as $columnName => $columnValue ) {
302
               $columns[] = $this->tableColumnName( $table, $columnName );
303
               $values[] = $this->escapeString( $columnValue );
304
            }
305
            $query = sprintf( "insert into %s (%s) values (%s)", 
306
                  $this->configuration['tables'][$table]['table'],
307
                  implode( ",", $columns ), 
308
                  implode( ',', $values ) 
309
                  );
310
            $this->doSQL( $query );
4 rodolico 311
         }
16 rodolico 312
      }
4 rodolico 313
 
16 rodolico 314
      protected function tableColumnName ( $table, $field ) {
315
         return $this->configuration['tables'][$table]['fields'][$field]['dbColumn'];
316
      }
317
 
4 rodolico 318
      /**
319
       * Tests that the database connection works and the table is built
7 rodolico 320
       *
321
       * @return boolean True if table exists (does not verify columns)
4 rodolico 322
       */
323
      public function test() {
16 rodolico 324
         $result = $this->doSQL( sprintf( "show tables like '%s'", $this->configuration['tables']['users']['table'] ) );
4 rodolico 325
         return $result !== false && $result->num_rows;
326
      } // test
327
 
7 rodolico 328
      /**
329
       * updates row in database with $newData
330
       * 
331
       * @param string[] $newData fieldname/value pairs to be updated in table
332
       * 
333
       * @return mysqli_result|bool The mysqli result from a query
334
       */
4 rodolico 335
      public function update ( $newData ) {
336
         $query = '';
337
         foreach ( $newData as $key => $value ) {
338
            $newData[$key] = $this->escapeString( $value );
339
         }
340
         if ( $newData ) { // make sure they sent us something
341
            if ( $newData['id'] > 0 ) { // we are doing an update
342
               $fields = array();
16 rodolico 343
               foreach ( $this->configuration['tables']['users']['fields'] as $key => $record ) {
4 rodolico 344
                  if ( isset( $newData[$key] ) ) {
345
                     $fields[] = $record['dbColumn'] . " = $newData[$key]";
346
                  } // if
347
               }
16 rodolico 348
               $query = 'update ' . $this->configuration['tables']['users']['table'] . ' set ' .
4 rodolico 349
                  implode( ',', $fields ) .
16 rodolico 350
                  ' where ' . $this->configuration['tables']['users']['id'] . ' = ' . 
17 rodolico 351
                  $this->escapeString( $newData['id'] );
4 rodolico 352
            } else { // we are doing an insert
353
               $columns = array();
354
               $values = array();
16 rodolico 355
               foreach ( $this->configuration['tables']['users']['fields'] as $key => $record ) {
4 rodolico 356
                  if ( isset( $newData[$key] ) ) {
357
                     $columns[] = $record['dbColumn'];
358
                     $values[] = $newData[$key];
359
                  } // if
360
               }
16 rodolico 361
               $query = 'insert into ' . $this->configuration['tables']['users']['table'] . 
4 rodolico 362
                  '(' . implode( ',', $columns ) . ') values (' .
363
                  implode( ',', $values ) . ')';
364
            }
18 rodolico 365
            return $this->doSQL( $query, 'update' );
4 rodolico 366
         }
367
      } // update
368
 
7 rodolico 369
      /**
370
       * retrieves all users from the database
371
       * 
372
       * Retrieves all data for all users from table
373
       * 
374
       * @return string[] array of array of rows/columns
375
       */
4 rodolico 376
      public function getAllUsers() {
377
         $query = $this->buildQuery( null, null, array('login' => 1) );
378
         //print "<p>$query</p>\n";
379
         mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
16 rodolico 380
         $result = $this->doSQL( $query );
4 rodolico 381
         if ( $result ) {
382
            return $result->fetch_all(MYSQLI_ASSOC);
383
         }
384
         return array();
385
      }
16 rodolico 386
 
387
      /**
388
       * Executes an SQL statement, returning the result
389
       * 
390
       * This simply runs mysqli::query, and returns the value of that
391
       * 
392
       * Created for testing and debugging, if the second parameter is 
393
       * true, will NOT execute the query, but will instead display the 
394
       * query passed.
395
       * 
396
       * @parameter string $query SQL Query to execute
18 rodolico 397
       * @parameter string $comment if not empty, writes comment and query to a file
16 rodolico 398
       * 
399
       * @returns mysqli_result
400
       */
18 rodolico 401
      protected function doSQL( $query, $comment = '' ) {
402
         if ( $comment ) {
403
            $handle = fopen( '/tmp/log.sql', 'a' );
404
            fwrite( $handle, "$comment\n$query\n" );
405
            fclose( $handle );
406
         }
17 rodolico 407
         mysqli_report( MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT );
408
         $result = $this->dbConnection->query( $query );
409
         return $result;
16 rodolico 410
      }
411
 
412
      /**
413
       * Gets a single field from a table
414
       * 
415
       * Builds a query similar to
416
       * select $returnColumn from $tableName where $fieldName = $value
417
       * executes it, and returns the first column of the first
418
       * row returned, or null if it does not exist.
419
       * 
420
       * @parameter string $tableName Name of database table
421
       * @parameter string $returnColumn Column to return
422
       * @parameter string $fieldName Name of column to search for value
423
       * @parameter string $value The value to match
424
       * 
425
       * @returns string $returnColumn of first row, or null if none
426
       */
427
      protected function getAField( $tableName, $returnColumn, $fieldName, $value ) {
428
         $value = $this->escapeString( $value );
429
         $result = $this->doSQL( "select $returnColumn from $tableName where $fieldName = $value" );
430
         $field = $result->fetch_array(MYSQLI_NUM);
431
         return $field ? $field[0] : null;
432
      }
4 rodolico 433
 
434
}
435
 
436
?>