Subversion Repositories php_users

Rev

Rev 42 | 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
 * getAllUsers()
45
 * getARecord
46
 * update
47
 * 
48
 * Additionally, where appropriate, the following function is useful
49
 * buildTable()
50
 * 
51
 * This particular instance provides an interface to MySQL using
52
 * the mysqli libraries.
53
 * 
54
 * Create an instance of this, then pass the variable to several Users
55
 * calls.
56
 * 
57
 * @author R. W. Rodolico <rodo@unixservertech.com>
58
 * 
59
 * @version 0.9.0 (beta)
60
 * @copyright 2021 Daily Data, Inc.
61
 * 
62
 */
21 rodolico 63
require_once( 'UsersDataSource.class.php' );
7 rodolico 64
 
21 rodolico 65
class usersDataSourceMySQLi extends usersDataSource {
4 rodolico 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,
21 rodolico 88
                     'required'  => true,
89
                     'unique'    => true
4 rodolico 90
                     ),
91
               'pass'   => array( 
92
                     'dbColumn'  => 'password',    // password column name
93
                     'type'   => 'varchar',
94
                     'size'      => 128,
10 rodolico 95
                     'required'  => true
4 rodolico 96
                     ),
97
               'admin'  => array(
98
                     'dbColumn'  => 'isAdmin',
99
                     'type'      => 'boolean',
100
                     'required'  => true,
101
                     'default'   => '0'
102
                     ),
103
               'enabled'   => array(
104
                     'dbColumn'  => 'enabled',
105
                     'type'      => 'boolean',
106
                     'required'  => true,
107
                     'default'   => '1'
108
                     )
109
               )
110
            )
111
         )
112
      );
7 rodolico 113
      /** @var mysqli $dbConnection Holds the mysqli database connection */
16 rodolico 114
      protected $dbConnection = false;
4 rodolico 115
 
7 rodolico 116
      /**
117
       * constructor for an instance of the class
118
       * 
119
       * If $dbConnection is not null, will be used for database access
120
       * If $dbLoginInfo is not null, will override $dbConnection, make
121
       * a new connection and use that.
122
       * 
16 rodolico 123
       * If $dbDef is set, will be merged with $configuration
7 rodolico 124
       * 
21 rodolico 125
       * @param mysqli $dbConnection Existing mysqli database connection or array with login information
16 rodolico 126
       * @param string[] $dbDef Array to be merged with $configuration
7 rodolico 127
       * 
128
       * @return null
129
       * 
130
       */
21 rodolico 131
      public function __construct( $connection, $customFields = array() ) {
132
         parent::__construct( $customFields );
133
         if ( is_array( $connection ) ) { // they sent us some login values
134
            $this->setDBConnection( $connection );
32 rodolico 135
         } elseif ( $connection instanceof mysqli ) { // mysqli, or some extension
21 rodolico 136
            $this->dbConnection = $connection;
137
         } else {
138
            throw new Exception( 'Can not open database using; must give open mysqli class or array of login information' );
4 rodolico 139
         }
140
      }
141
 
7 rodolico 142
      /**
143
       * Make string safe for MySQL
144
       * 
145
       * If the string is completely numeric, returns it, otherwise 
146
       * puts single quotes around it
147
       * 
148
       * @param string $string The string to be fixed
149
       * @return string A copy of the string, ready for SQL
150
       */
16 rodolico 151
      protected function escapeString ( $string ) {
46 rodolico 152
         if ( strlen( $string ) == 0 ) {
153
            $string = 'null';
154
         } elseif ( ! is_numeric( $string ) ) {
17 rodolico 155
            $string = $this->dbConnection->real_escape_string( $string );
4 rodolico 156
            $string = "'$string'";
17 rodolico 157
         }
4 rodolico 158
         return $string;
159
      }
160
 
161
      /**
7 rodolico 162
       * Create a query to retrieve info from database
4 rodolico 163
       * 
164
       * Builds a query to retrieve records from the database. With all
165
       * parameters set to null, will retrieve all columns and records
166
       * Setting $field and $toFind create a where clause, and setting
167
       * $fieldList as a has (ie, 'fieldname' => 1) will limit the 
168
       * fields returned
169
       * 
16 rodolico 170
       * @param string $field A valid field definition from $configuration
4 rodolico 171
       * @param string $toFind The string to find, ie where $field = $username
172
       * @param string[] $fieldList a hash where the keys make a list of columns to return. If empty, returns all columns
173
       * 
174
       * @return string A cleaned and formatted SQL Query
175
       * 
176
      */
16 rodolico 177
      protected function buildQuery( $whereFields, $fieldList = null ) {
4 rodolico 178
         // always get the ID field
16 rodolico 179
         $fields = array( $this->configuration['tables']['users']['id'] . ' id');
4 rodolico 180
         // Get the rest of the available fields
16 rodolico 181
         foreach ( $this->configuration['tables']['users']['fields'] as $key => $value ) {
4 rodolico 182
            // do not use this one if $fieldList doesn't have it
183
            if ( ! isset( $fieldList ) || isset( $fieldList[$key] ) )
184
               $fields[] = $value['dbColumn'] . ' ' . $key;
185
         }
186
         // Change it into something SQL can handle
187
         $query = implode( ',', $fields );
188
         // now, build the rest of the query
16 rodolico 189
         $query = "select $query from " . $this->configuration['tables']['users']['table'];
4 rodolico 190
         if ( isset( $whereFields ) ) {
191
            $temp = array();
192
            foreach ( $whereFields as $key => $value ) {
193
               $temp[] = ( 
194
                  $key == 'id' ? 
16 rodolico 195
                  $this->configuration['tables']['users']['id'] : 
196
                  $this->configuration['tables']['users']['fields'][$key]['dbColumn']
4 rodolico 197
                  ) . '= ' . $this->escapeString( $value );
198
            }
199
            $query .= ' where ' . implode( ' and ', $temp );
200
         }
46 rodolico 201
         $query .= ' order by isnull(removed) desc, login';
4 rodolico 202
         return $query;
203
      }
204
 
205
      /**
206
       * Get a record from the database
207
       * 
208
       * Gets a single record from the database which matches $field containing
209
       * $username. If more than one record is returned, will return the first
210
       * one
211
       * 
212
       * @param string[] $whereFields column=>value pairs for where clause
213
       * @param string[] $fieldList a list of columns to return. If empty, returns all columns
214
       * 
7 rodolico 215
       * @return string[] a hash containing fieldname=>value pairs from fetch_assoc
4 rodolico 216
       * 
217
      */
218
      public function getARecord( $whereFields, $fieldList = null ) {
219
         // run the query, placing value in $result
220
         mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
16 rodolico 221
         $result = $this->doSQL( $this->buildQuery( $whereFields, $fieldList ) );
4 rodolico 222
         if ( $result ) { // got one, so return it
223
            return $result->fetch_assoc();
224
         }
225
         // WTFO? nothing, so return empty array
226
         return array();
227
      }
228
 
7 rodolico 229
      /**
230
       * Retrieves the password field from table
231
       * 
232
       * Note that the password is stored as a hash in the table
233
       * 
234
       * @param string $username username used to find record
235
       * @return string[] an array of values key/value pairs
236
       */
4 rodolico 237
      public function getPassword( $username ) {
238
         return $this->getARecord( array('login' => $username,'enabled' => 1), array('pass' => 1 ) );
239
      }
240
 
7 rodolico 241
      /**
21 rodolico 242
       * retrieves the userID from the table
243
       * 
244
       * @param string $username
245
       * @return integer user id
246
       */
247
      public function getUserID( $username ) {
248
         $record = $this->getARecord( array( 'login' => $username ), array('id' => 1 ) );
249
         return $record['id'];
250
      }
251
 
252
      /**
7 rodolico 253
       * Make the database connection
254
       * 
255
       * @param string[] $parameters Parameters for makeing the connection
256
       * @return mysqli|false
257
       */
16 rodolico 258
      protected function setDBConnection ( $parameters ) {
4 rodolico 259
         if ( !isset($parameters['username'], $parameters['password'],$parameters['database']  )) {
260
            return false;
261
         }
262
         if ( !isset( $parameters['host'] ) ) {
263
            $parameters['host'] = 'localhost';
264
         }
265
         mysqli_report( MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT );
266
         $this->dbConnection = new mysqli( $parameters['host'], $parameters['username'], $parameters['password'],$parameters['database'] );
267
      }
268
 
269
      /**
16 rodolico 270
       * Convenience function to create the tables defined in $configuration
4 rodolico 271
       * 
16 rodolico 272
       * Using $configuration, build the table (replacing the current one)
4 rodolico 273
       * then add $username with $password, setting as admin
274
       * 
275
      */
16 rodolico 276
      public function buildTable( ) {
4 rodolico 277
         if ( $this->dbConnection ) {
16 rodolico 278
            foreach ( $this->configuration['tables'] as $table => $tableRecord ) {
279
               $fields = array( $tableRecord['id'] . ' int unsigned not null auto_increment' );
280
               foreach ( $tableRecord['fields'] as $key => $record ) {
281
                  $fieldDef = $record['dbColumn'];
282
                  $fieldDef .= ' ' . $record['type'];
283
                  if ( isset( $record['size'] ) ) {
284
                     $fieldDef .= '(' . $record['size'] . ')';
285
                  }
21 rodolico 286
                  if ( isset( $record['unique'] ) ) {
287
                     $fieldDef .= ' unique ';
288
                  }
16 rodolico 289
                  if ( isset( $record['required'] ) ) {
290
                     $fieldDef .= $record['required'] ? ' not null ' : '';
291
                  }
292
                  if ( isset( $record['default'] ) ) {
293
                     $fieldDef .= sprintf( " default '%s'", $record['default'] );
42 rodolico 294
                  } elseif ( ! empty( $record['required'] ) && strtolower($record['type']) == 'date' ) {
295
                     $fieldDef .= ' default CURRENT_TIMESTAMP';
16 rodolico 296
                  }
297
                  if ( isset( $record['comment'] ) ) {
298
                     $fieldDef .= "comment '" . $record['comment'] . "'";
299
                  }
300
                  $fields[] = $fieldDef;
4 rodolico 301
               }
16 rodolico 302
               $fields[] = 'primary key (' . $tableRecord['id'] . ')';
303
               $query = implode( ',', $fields );
304
               $query = 'create or replace table ' . $tableRecord['table'] .
305
                     "($query)";
306
               $this->doSQL( $query );
4 rodolico 307
            }
16 rodolico 308
         } // foreach table
309
      } // buildTable
310
 
311
      /**
312
       * Convenience function to initialize tables to values
313
       * 
314
       * @param string[] $initValues Array of tablenames, column names and values
315
       * 
316
      */
317
      public function initTables ( $initValues ) {
318
         foreach ( $initValues as $table => $fieldDef ) {
319
            $columns = array();
320
            $values = array();
321
            foreach ( $fieldDef as $columnName => $columnValue ) {
322
               $columns[] = $this->tableColumnName( $table, $columnName );
323
               $values[] = $this->escapeString( $columnValue );
324
            }
325
            $query = sprintf( "insert into %s (%s) values (%s)", 
326
                  $this->configuration['tables'][$table]['table'],
327
                  implode( ",", $columns ), 
328
                  implode( ',', $values ) 
329
                  );
330
            $this->doSQL( $query );
4 rodolico 331
         }
16 rodolico 332
      }
4 rodolico 333
 
21 rodolico 334
      /**
335
       * Gets the actual database column name from the configuration file
336
       * 
337
       * Since we use a lot of indirection, this is a handy function which
338
       * allows us to replace something like
339
       * $this->configuration['tables']['users']['fields']['name']['dbColumn']
340
       * with
341
       * $this->tableColumnName( 'users', 'name' )
342
       * 
343
       * If called with only one parameter (the table), will return the
344
       * actual database table name
345
       * 
346
       * @param string $table Name of Table
347
       * @param string $field Name of field in $table
348
       * @param boolean $fullTableColumn If set to true, will return table.column format
349
       * 
350
       * @return string The actual name of the dbColumn in the table
351
       */
352
      protected function tableColumnName ( $table, $field = '', $fullTableColumn = false ) {
353
         if ( ! $field ) { // just return the table name
354
            $return = $this->configuration['tables'][$table]['table'];
355
         } elseif ( $field == 'id' ) { // looking for the index
356
            $return = $this->configuration['tables'][$table]['id'];
357
         } else { // return the column name
358
            $return = $this->configuration['tables'][$table]['fields'][$field]['dbColumn'];
359
         }
360
         if ( $fullTableColumn && $field ) {
361
            $return = $this->configuration['tables'][$table]['table'] . '.' . $return;
362
         }
363
         return $return;
16 rodolico 364
      }
365
 
4 rodolico 366
      /**
367
       * Tests that the database connection works and the table is built
7 rodolico 368
       *
369
       * @return boolean True if table exists (does not verify columns)
4 rodolico 370
       */
371
      public function test() {
21 rodolico 372
         $query = sprintf( "show tables like '%s'", $this->tableColumnName ( 'users' ) );
373
         $result = $this->doSQL( $query );
4 rodolico 374
         return $result !== false && $result->num_rows;
375
      } // test
376
 
7 rodolico 377
      /**
378
       * updates row in database with $newData
379
       * 
380
       * @param string[] $newData fieldname/value pairs to be updated in table
381
       * 
382
       * @return mysqli_result|bool The mysqli result from a query
383
       */
4 rodolico 384
      public function update ( $newData ) {
385
         $query = '';
386
         foreach ( $newData as $key => $value ) {
387
            $newData[$key] = $this->escapeString( $value );
388
         }
389
         if ( $newData ) { // make sure they sent us something
390
            if ( $newData['id'] > 0 ) { // we are doing an update
391
               $fields = array();
16 rodolico 392
               foreach ( $this->configuration['tables']['users']['fields'] as $key => $record ) {
4 rodolico 393
                  if ( isset( $newData[$key] ) ) {
394
                     $fields[] = $record['dbColumn'] . " = $newData[$key]";
395
                  } // if
396
               }
21 rodolico 397
               $query = 'update ' . $this->tableColumnName ( 'users' ) . ' set ' .
4 rodolico 398
                  implode( ',', $fields ) .
21 rodolico 399
                  ' where ' . $this->tableColumnName ( 'users', 'id' ) . ' = ' . 
17 rodolico 400
                  $this->escapeString( $newData['id'] );
4 rodolico 401
            } else { // we are doing an insert
402
               $columns = array();
403
               $values = array();
16 rodolico 404
               foreach ( $this->configuration['tables']['users']['fields'] as $key => $record ) {
4 rodolico 405
                  if ( isset( $newData[$key] ) ) {
406
                     $columns[] = $record['dbColumn'];
407
                     $values[] = $newData[$key];
408
                  } // if
409
               }
21 rodolico 410
               $query = 'insert into ' . $this->tableColumnName ( 'users' ) . 
4 rodolico 411
                  '(' . implode( ',', $columns ) . ') values (' .
412
                  implode( ',', $values ) . ')';
413
            }
21 rodolico 414
            return $this->doSQL( $query );
4 rodolico 415
         }
416
      } // update
417
 
7 rodolico 418
      /**
419
       * retrieves all users from the database
420
       * 
421
       * Retrieves all data for all users from table
422
       * 
423
       * @return string[] array of array of rows/columns
424
       */
4 rodolico 425
      public function getAllUsers() {
426
         $query = $this->buildQuery( null, null, array('login' => 1) );
427
         //print "<p>$query</p>\n";
428
         mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
16 rodolico 429
         $result = $this->doSQL( $query );
4 rodolico 430
         if ( $result ) {
431
            return $result->fetch_all(MYSQLI_ASSOC);
432
         }
433
         return array();
434
      }
16 rodolico 435
 
436
      /**
437
       * Executes an SQL statement, returning the result
438
       * 
439
       * This simply runs mysqli::query, and returns the value of that
440
       * 
441
       * Created for testing and debugging, if the second parameter is 
442
       * true, will NOT execute the query, but will instead display the 
443
       * query passed.
444
       * 
445
       * @parameter string $query SQL Query to execute
18 rodolico 446
       * @parameter string $comment if not empty, writes comment and query to a file
16 rodolico 447
       * 
448
       * @returns mysqli_result
449
       */
18 rodolico 450
      protected function doSQL( $query, $comment = '' ) {
451
         if ( $comment ) {
452
            $handle = fopen( '/tmp/log.sql', 'a' );
453
            fwrite( $handle, "$comment\n$query\n" );
454
            fclose( $handle );
455
         }
17 rodolico 456
         mysqli_report( MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT );
457
         $result = $this->dbConnection->query( $query );
458
         return $result;
16 rodolico 459
      }
460
 
461
      /**
462
       * Gets a single field from a table
463
       * 
464
       * Builds a query similar to
465
       * select $returnColumn from $tableName where $fieldName = $value
466
       * executes it, and returns the first column of the first
467
       * row returned, or null if it does not exist.
468
       * 
469
       * @parameter string $tableName Name of database table
470
       * @parameter string $returnColumn Column to return
471
       * @parameter string $fieldName Name of column to search for value
472
       * @parameter string $value The value to match
473
       * 
474
       * @returns string $returnColumn of first row, or null if none
475
       */
476
      protected function getAField( $tableName, $returnColumn, $fieldName, $value ) {
477
         $value = $this->escapeString( $value );
478
         $result = $this->doSQL( "select $returnColumn from $tableName where $fieldName = $value" );
479
         $field = $result->fetch_array(MYSQLI_NUM);
480
         return $field ? $field[0] : null;
481
      }
4 rodolico 482
 
483
}
484
 
485
?>