Subversion Repositories php_users

Rev

Rev 7 | Rev 16 | 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
   /**
68
    * @var string[] $dbDefinition Contains the configuration for the class
69
    * 
70
    * May be modified by the calling program. Must be replicated in userDataSource class
71
    */
4 rodolico 72
   private $dbDefinition = array(
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 */
4 rodolico 113
      private $dbConnection = false;
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
       * 
122
       * If $dbDef is set, will be merged with $dbDefinition
123
       * 
124
       * @param mysqli $dbConnection Existing mysqli database connection
125
       * @param string[] $dbDef Array to be merged with $dbDefinition
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 ) {
134
            $this->dbDefinition = array_merge_recursive( $this->dbDefinition, $dbDef );
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
       */
4 rodolico 150
      private function escapeString ( $string ) {
151
         $string = $this->dbConnection->real_escape_string( $string );
152
         if ( ! is_numeric( $string ) )
153
            $string = "'$string'";
154
         return $string;
155
      }
156
 
157
      /**
7 rodolico 158
       * Create a query to retrieve info from database
4 rodolico 159
       * 
160
       * Builds a query to retrieve records from the database. With all
161
       * parameters set to null, will retrieve all columns and records
162
       * Setting $field and $toFind create a where clause, and setting
163
       * $fieldList as a has (ie, 'fieldname' => 1) will limit the 
164
       * fields returned
165
       * 
7 rodolico 166
       * @param string $field A valid field definition from $dbDefinition
4 rodolico 167
       * @param string $toFind The string to find, ie where $field = $username
168
       * @param string[] $fieldList a hash where the keys make a list of columns to return. If empty, returns all columns
169
       * 
170
       * @return string A cleaned and formatted SQL Query
171
       * 
172
      */
173
      private function buildQuery( $whereFields, $fieldList = null ) {
174
         // always get the ID field
175
         $fields = array( $this->dbDefinition['tables']['users']['id'] . ' id');
176
         // Get the rest of the available fields
177
         foreach ( $this->dbDefinition['tables']['users']['fields'] as $key => $value ) {
178
            // do not use this one if $fieldList doesn't have it
179
            if ( ! isset( $fieldList ) || isset( $fieldList[$key] ) )
180
               $fields[] = $value['dbColumn'] . ' ' . $key;
181
         }
182
         // Change it into something SQL can handle
183
         $query = implode( ',', $fields );
184
         // now, build the rest of the query
185
         $query = "select $query from " . $this->dbDefinition['tables']['users']['table'];
186
         if ( isset( $whereFields ) ) {
187
            $temp = array();
188
            foreach ( $whereFields as $key => $value ) {
189
               $temp[] = ( 
190
                  $key == 'id' ? 
191
                  $this->dbDefinition['tables']['users']['id'] : 
192
                  $this->dbDefinition['tables']['users']['fields'][$key]['dbColumn']
193
                  ) . '= ' . $this->escapeString( $value );
194
            }
195
            $query .= ' where ' . implode( ' and ', $temp );
196
         }
197
         //print "<p>$query</p>";
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);
217
         $result = $this->dbConnection->query( $this->buildQuery( $whereFields, $fieldList ) );
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
       * Gets the entire record for a user
239
       * 
240
       * NOTE: this does not actually get all columns. getARecord only gets
241
       * the columns defined in $dbDefinition
242
       * 
243
       * @param string $username the value of the login field to find
244
       * 
245
       * @return string[] fieldname=>value array of found record
246
       */
4 rodolico 247
      public function getRecord ( $username ) {
248
         return $this->getARecord( array( 'login' => $username ) );
249
      }
250
 
7 rodolico 251
      /**
252
       * Make the database connection
253
       * 
254
       * @param string[] $parameters Parameters for makeing the connection
255
       * @return mysqli|false
256
       */
4 rodolico 257
      private function setDBConnection ( $parameters ) {
258
         if ( !isset($parameters['username'], $parameters['password'],$parameters['database']  )) {
259
            return false;
260
         }
261
         if ( !isset( $parameters['host'] ) ) {
262
            $parameters['host'] = 'localhost';
263
         }
264
         mysqli_report( MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT );
265
         $this->dbConnection = new mysqli( $parameters['host'], $parameters['username'], $parameters['password'],$parameters['database'] );
266
      }
267
 
268
      /**
269
       * Convenience function to create the table
270
       * 
271
       * Using $dbDefinition, build the table (replacing the current one)
272
       * then add $username with $password, setting as admin
273
       * 
274
       * @param string $username The username to create
275
       * @param string $password The password for this record
276
       * @param bool $admin Whether the user is an admin or not
277
       * 
278
       * @return bool true if table was created
279
       * 
280
      */
281
      public function buildTable( $username, $password, $admin = true ) {
282
         if ( $this->dbConnection ) {
283
            $password = password_hash( $password, PASSWORD_DEFAULT );
284
            $fields = array( $this->dbDefinition['tables']['users']['id'] . ' int unsigned not null auto_increment' );
285
            foreach ( $this->dbDefinition['tables']['users']['fields'] as $key => $record ) {
286
               $fieldDef = $record['dbColumn'];
287
               $fieldDef .= ' ' . $record['type'];
288
               if ( isset( $record['size'] ) ) {
289
                  $fieldDef .= '(' . $record['size'] . ')';
290
               }
291
               if ( isset( $record['required'] ) ) {
292
                  $fieldDef .= $record['required'] ? ' not null ' : '';
293
               }
294
               if ( isset( $record['default'] ) ) {
295
                  $fieldDef .= sprintf( " default '%s'", $record['default'] );
296
               }
297
               if ( isset( $record['comment'] ) ) {
298
                  $fieldDef .= "comment '" . $record['comment'] . "'";
299
               }
300
               $fields[] = $fieldDef;
301
            }
302
            $fields[] = 'primary key (' . $this->dbDefinition['tables']['users']['id'] . ')';
303
            $query = implode( ',', $fields );
304
            $query = 'create or replace table ' . $this->dbDefinition['tables']['users']['table'] .
305
                  "($query)";
306
            $this->dbConnection->query( $query );
307
            $query = "insert into _users( login, password, isAdmin ) values ( '$username', '$password', '$admin' )";
308
            $this->dbConnection->query( $query );
309
         }
310
         return false;
311
      } // buildTable
312
 
313
      /**
314
       * Tests that the database connection works and the table is built
7 rodolico 315
       *
316
       * @return boolean True if table exists (does not verify columns)
4 rodolico 317
       */
318
      public function test() {
319
         $result = $this->dbConnection->query( sprintf( "show tables like '%s'", $this->dbDefinition['tables']['users']['table'] ) );
320
         return $result !== false && $result->num_rows;
321
      } // test
322
 
7 rodolico 323
      /**
324
       * updates row in database with $newData
325
       * 
326
       * @param string[] $newData fieldname/value pairs to be updated in table
327
       * 
328
       * @return mysqli_result|bool The mysqli result from a query
329
       */
4 rodolico 330
      public function update ( $newData ) {
331
         $query = '';
332
         foreach ( $newData as $key => $value ) {
333
            $newData[$key] = $this->escapeString( $value );
334
         }
335
         if ( $newData ) { // make sure they sent us something
336
            if ( $newData['id'] > 0 ) { // we are doing an update
337
               $fields = array();
338
               foreach ( $this->dbDefinition['tables']['users']['fields'] as $key => $record ) {
339
                  if ( isset( $newData[$key] ) ) {
340
                     $fields[] = $record['dbColumn'] . " = $newData[$key]";
341
                  } // if
342
               }
343
               $query = 'update ' . $this->dbDefinition['tables']['users']['table'] . ' set ' .
344
                  implode( ',', $fields ) .
345
                  ' where ' . $this->dbDefinition['tables']['users']['id'] . ' = ' . 
346
                  $this->dbConnection->real_escape_string( $newData['id'] );
347
            } else { // we are doing an insert
348
               $columns = array();
349
               $values = array();
350
               foreach ( $this->dbDefinition['tables']['users']['fields'] as $key => $record ) {
351
                  if ( isset( $newData[$key] ) ) {
352
                     $columns[] = $record['dbColumn'];
353
                     $values[] = $newData[$key];
354
                  } // if
355
               }
356
               $query = 'insert into ' . $this->dbDefinition['tables']['users']['table'] . 
357
                  '(' . implode( ',', $columns ) . ') values (' .
358
                  implode( ',', $values ) . ')';
359
            }
360
            //print "<p>$query</p>";
361
            return $this->dbConnection->query( $query );
362
         }
363
      } // update
364
 
7 rodolico 365
      /**
366
       * retrieves all users from the database
367
       * 
368
       * Retrieves all data for all users from table
369
       * 
370
       * @return string[] array of array of rows/columns
371
       */
4 rodolico 372
      public function getAllUsers() {
373
         $query = $this->buildQuery( null, null, array('login' => 1) );
374
         //print "<p>$query</p>\n";
375
         mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
376
         $result = $this->dbConnection->query( $query );
377
         if ( $result ) {
378
            return $result->fetch_all(MYSQLI_ASSOC);
379
         }
380
         return array();
381
      }
382
 
383
}
384
 
385
?>