Completed
Push — master ( 34b090...af3ce8 )
by Stefan
04:50
created

DBConnection::exec()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 13
c 1
b 0
f 0
nc 4
nop 2
dl 0
loc 22
rs 8.9197
1
<?php
2
3
/* * ********************************************************************************
4
 * (c) 2011-15 GÉANT on behalf of the GN3, GN3plus and GN4 consortia
5
 * License: see the LICENSE file in the root directory
6
 * ********************************************************************************* */
7
?>
8
<?php
9
10
/**
11
 * This file contains the DBConnection singleton.
12
 * 
13
 * @author Stefan Winter <[email protected]>
14
 * @author Tomasz Wolniewicz <[email protected]>
15
 * 
16
 * @package Developer
17
 */
18
require_once('Helper.php');
19
require_once('Profile.php');
20
require_once('IdP.php');
21
22
/**
23
 * This class is a singleton for establishing a connection to the database
24
 *
25
 * @author Stefan Winter <[email protected]>
26
 * @author Tomasz Wolniewicz <[email protected]>
27
 *
28
 * @license see LICENSE file in root directory
29
 *
30
 * @package Developer
31
 */
32
class DBConnection {
33
34
    /**
35
     * This is the actual constructor for the singleton. It creates a database connection if it is not up yet, and returns a handle to the database connection on every call.
36
     * @return DBConnection the (only) instance of this class
37
     */
38
    private static function handle($db) {
39
        switch (strtoupper($db)) {
40
            case "INST":
41
                if (!isset(self::$instance_inst)) {
42
                    $c = __CLASS__;
43
                    self::$instance_inst = new $c($db);
44
                }
45
                return self::$instance_inst;
46
            case "USER":
47
                if (!isset(self::$instance_user)) {
48
                    $c = __CLASS__;
49
                    self::$instance_user = new $c($db);
50
                }
51
                return self::$instance_user;
52
            case "EXTERNAL":
53
                if (!isset(self::$instance_external)) {
54
                    $c = __CLASS__;
55
                    self::$instance_external = new $c($db);
56
                }
57
                return self::$instance_external;
58
            default:
59
                return FALSE;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return FALSE; (false) is incompatible with the return type documented by DBConnection::handle of type DBConnection.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
60
        }
61
    }
62
63
    /**
64
     * Implemented for safety reasons only. Cloning is forbidden and will tell the user so.
65
     */
66
    public function __clone() {
67
        trigger_error('Clone is not allowed.', E_USER_ERROR);
68
    }
69
70
    /**
71
     * 
72
     * @param string $db The database to do escapting for
73
     * @param string $value The value to escape
74
     * @return string
75
     */
76
    public static function escape_value($db, $value) {
77
        $handle = DBConnection::handle($db);
78
        debug(5, "Escaping $value for DB $db to get a safe query value.\n");
79
        $escaped = mysqli_real_escape_string($handle->connection, $value);
80
        debug(5, "This is the result: $escaped .\n");
81
        return $escaped;
82
    }
83
84
    /**
85
     * executes a query and triggers logging to the SQL audit log if it's not a SELECT
86
     * @param string $querystring the query to be executed
87
     * @return mixed the query result as mysqli_result object; or TRUE on non-return-value statements
88
     */
89
    public static function exec($db, $querystring) {
90
        // log exact query to debug log, if log level is at 5
91
        debug(5, "DB ATTEMPT: " . $querystring . "\n");
92
93
        $instance = DBConnection::handle($db);
94
        if ($instance->connection == FALSE) {
95
            debug(1, "ERROR: Cannot send query to $db database (no connection)!");
96
            return FALSE;
97
        }
98
99
        $result = mysqli_query($instance->connection, $querystring);
0 ignored issues
show
Security SQL Injection introduced by
$querystring can contain request data and is used in sql context(s) leading to a potential security vulnerability.

11 paths for user data to reach this point

  1. Path: Read from $_POST, and $_POST[display_name($a) . '-priority'] is passed to Profile::addSupportedEapMethod() in web/admin/edit_profile_result.php on line 189
  1. Read from $_POST, and $_POST[display_name($a) . '-priority'] is passed to Profile::addSupportedEapMethod()
    in web/admin/edit_profile_result.php on line 189
  2. 'INSERT INTO supported_eap (profile_id, eap_method_id, preference) VALUES (' . $this->identifier . ', ' . \EAP::EAPMethodIdFromArray($type) . ', ' . $preference . ')' is passed to DBConnection::exec()
    in core/Profile.php on line 417
  2. Path: Read from $_GET, and $_GET['fed'] is passed to valid_Fed() in web/admin/action_fedcheck.php on line 162
  1. Read from $_GET, and $_GET['fed'] is passed to valid_Fed()
    in web/admin/action_fedcheck.php on line 162
  2. $input is passed to Federation::__construct()
    in web/admin/inc/input_validation.inc.php on line 24
  3. EntityWithDBProperties::$identifier is assigned
    in core/Federation.php on line 122
  4. Tainted property EntityWithDBProperties::$identifier is read
    in web/admin/action_fedcheck.php on line 38
  5. 'UPDATE profile SET ' . 'status_dns = ' . RETVAL_SKIPPED . ', ' . 'status_cert = ' . RETVAL_SKIPPED . ', ' . 'status_reachability = ' . RETVAL_SKIPPED . ', ' . 'status_TLS = ' . RETVAL_SKIPPED . ', ' . 'last_status_check = NOW() ' . 'WHERE profile_id = ' . $profile->identifier is passed to DBConnection::exec()
    in web/admin/action_fedcheck.php on line 32
  3. Path: Read from $_POST, and $_POST['fed_id'] is passed to valid_Fed() in web/admin/edit_federation.php on line 22
  1. Read from $_POST, and $_POST['fed_id'] is passed to valid_Fed()
    in web/admin/edit_federation.php on line 22
  2. $input is passed to Federation::__construct()
    in web/admin/inc/input_validation.inc.php on line 24
  3. EntityWithDBProperties::$identifier is assigned
    in core/Federation.php on line 122
  4. Tainted property EntityWithDBProperties::$identifier is read
    in web/admin/action_fedcheck.php on line 38
  5. 'UPDATE profile SET ' . 'status_dns = ' . RETVAL_SKIPPED . ', ' . 'status_cert = ' . RETVAL_SKIPPED . ', ' . 'status_reachability = ' . RETVAL_SKIPPED . ', ' . 'status_TLS = ' . RETVAL_SKIPPED . ', ' . 'last_status_check = NOW() ' . 'WHERE profile_id = ' . $profile->identifier is passed to DBConnection::exec()
    in web/admin/action_fedcheck.php on line 32
  4. Path: Read from $_GET, and $_GET['fed_id'] is passed to valid_Fed() in web/admin/edit_federation_result.php on line 26
  1. Read from $_GET, and $_GET['fed_id'] is passed to valid_Fed()
    in web/admin/edit_federation_result.php on line 26
  2. $input is passed to Federation::__construct()
    in web/admin/inc/input_validation.inc.php on line 24
  3. EntityWithDBProperties::$identifier is assigned
    in core/Federation.php on line 122
  4. Tainted property EntityWithDBProperties::$identifier is read
    in web/admin/action_fedcheck.php on line 38
  5. 'UPDATE profile SET ' . 'status_dns = ' . RETVAL_SKIPPED . ', ' . 'status_cert = ' . RETVAL_SKIPPED . ', ' . 'status_reachability = ' . RETVAL_SKIPPED . ', ' . 'status_TLS = ' . RETVAL_SKIPPED . ', ' . 'last_status_check = NOW() ' . 'WHERE profile_id = ' . $profile->identifier is passed to DBConnection::exec()
    in web/admin/action_fedcheck.php on line 32
  5. Path: Read from $_GET, and $_GET['fed_id'] is passed to valid_Fed() in web/admin/edit_federation_result.php on line 43
  1. Read from $_GET, and $_GET['fed_id'] is passed to valid_Fed()
    in web/admin/edit_federation_result.php on line 43
  2. $input is passed to Federation::__construct()
    in web/admin/inc/input_validation.inc.php on line 24
  3. EntityWithDBProperties::$identifier is assigned
    in core/Federation.php on line 122
  4. Tainted property EntityWithDBProperties::$identifier is read
    in web/admin/action_fedcheck.php on line 38
  5. 'UPDATE profile SET ' . 'status_dns = ' . RETVAL_SKIPPED . ', ' . 'status_cert = ' . RETVAL_SKIPPED . ', ' . 'status_reachability = ' . RETVAL_SKIPPED . ', ' . 'status_TLS = ' . RETVAL_SKIPPED . ', ' . 'last_status_check = NOW() ' . 'WHERE profile_id = ' . $profile->identifier is passed to DBConnection::exec()
    in web/admin/action_fedcheck.php on line 32
  6. Path: Read from $_POST in web/admin/inc/sendinvite.inc.php on line 83
  1. Read from $_POST
    in web/admin/inc/sendinvite.inc.php on line 83
  2. Data is passed through iconv(), and Data is passed through trim()
    in vendor/web/admin/inc/input_validation.inc.php on line 87
  3. Data is passed through filter_var()
    in vendor/web/admin/inc/input_validation.inc.php on line 89
  4. Data is passed through preg_replace()
    in vendor/web/admin/inc/input_validation.inc.php on line 93
  5. $newcountry is assigned
    in web/admin/inc/sendinvite.inc.php on line 83
  6. $newcountry is passed to Federation::__construct()
    in web/admin/inc/sendinvite.inc.php on line 85
  7. EntityWithDBProperties::$identifier is assigned
    in core/Federation.php on line 122
  8. Tainted property EntityWithDBProperties::$identifier is read
    in web/admin/action_fedcheck.php on line 38
  9. 'UPDATE profile SET ' . 'status_dns = ' . RETVAL_SKIPPED . ', ' . 'status_cert = ' . RETVAL_SKIPPED . ', ' . 'status_reachability = ' . RETVAL_SKIPPED . ', ' . 'status_TLS = ' . RETVAL_SKIPPED . ', ' . 'last_status_check = NOW() ' . 'WHERE profile_id = ' . $profile->identifier is passed to DBConnection::exec()
    in web/admin/action_fedcheck.php on line 32
  7. Path: Read from $_REQUEST, and $_REQUEST['country'] is passed through strtoupper(), and $c is assigned in web/basic.php on line 59
  1. Read from $_REQUEST, and $_REQUEST['country'] is passed through strtoupper(), and $c is assigned
    in web/basic.php on line 59
  2. $c is passed to Federation::__construct()
    in web/basic.php on line 71
  3. EntityWithDBProperties::$identifier is assigned
    in core/Federation.php on line 122
  4. Tainted property EntityWithDBProperties::$identifier is read
    in web/admin/action_fedcheck.php on line 38
  5. 'UPDATE profile SET ' . 'status_dns = ' . RETVAL_SKIPPED . ', ' . 'status_cert = ' . RETVAL_SKIPPED . ', ' . 'status_reachability = ' . RETVAL_SKIPPED . ', ' . 'status_TLS = ' . RETVAL_SKIPPED . ', ' . 'last_status_check = NOW() ' . 'WHERE profile_id = ' . $profile->identifier is passed to DBConnection::exec()
    in web/admin/action_fedcheck.php on line 32
  8. Path: Read from $_REQUEST, and $_REQUEST['idp'] is passed to IdP::__construct() in web/basic.php on line 84
  1. Read from $_REQUEST, and $_REQUEST['idp'] is passed to IdP::__construct()
    in web/basic.php on line 84
  2. EntityWithDBProperties::$identifier is assigned
    in core/IdP.php on line 69
  3. Tainted property EntityWithDBProperties::$identifier is read
    in web/admin/action_fedcheck.php on line 38
  4. 'UPDATE profile SET ' . 'status_dns = ' . RETVAL_SKIPPED . ', ' . 'status_cert = ' . RETVAL_SKIPPED . ', ' . 'status_reachability = ' . RETVAL_SKIPPED . ', ' . 'status_TLS = ' . RETVAL_SKIPPED . ', ' . 'last_status_check = NOW() ' . 'WHERE profile_id = ' . $profile->identifier is passed to DBConnection::exec()
    in web/admin/action_fedcheck.php on line 32
  9. Path: Read from $_REQUEST, and $id is assigned in web/user/API.php on line 22
  1. Read from $_REQUEST, and $id is assigned
    in web/user/API.php on line 22
  2. $idp is assigned
    in web/user/API.php on line 59
  3. $idp is passed to UserAPI::JSON_listProfiles()
    in web/user/API.php on line 61
  4. $idp_id is passed to IdP::__construct()
    in core/UserAPI.php on line 331
  5. EntityWithDBProperties::$identifier is assigned
    in core/IdP.php on line 69
  6. Tainted property EntityWithDBProperties::$identifier is read
    in web/admin/action_fedcheck.php on line 38
  7. 'UPDATE profile SET ' . 'status_dns = ' . RETVAL_SKIPPED . ', ' . 'status_cert = ' . RETVAL_SKIPPED . ', ' . 'status_reachability = ' . RETVAL_SKIPPED . ', ' . 'status_TLS = ' . RETVAL_SKIPPED . ', ' . 'last_status_check = NOW() ' . 'WHERE profile_id = ' . $profile->identifier is passed to DBConnection::exec()
    in web/admin/action_fedcheck.php on line 32
  10. Path: Read from $_REQUEST, and $idp is assigned in web/user/API.php on line 25
  1. Read from $_REQUEST, and $idp is assigned
    in web/user/API.php on line 25
  2. $idp is passed to UserAPI::JSON_listProfiles()
    in web/user/API.php on line 61
  3. $idp_id is passed to IdP::__construct()
    in core/UserAPI.php on line 331
  4. EntityWithDBProperties::$identifier is assigned
    in core/IdP.php on line 69
  5. Tainted property EntityWithDBProperties::$identifier is read
    in web/admin/action_fedcheck.php on line 38
  6. 'UPDATE profile SET ' . 'status_dns = ' . RETVAL_SKIPPED . ', ' . 'status_cert = ' . RETVAL_SKIPPED . ', ' . 'status_reachability = ' . RETVAL_SKIPPED . ', ' . 'status_TLS = ' . RETVAL_SKIPPED . ', ' . 'last_status_check = NOW() ' . 'WHERE profile_id = ' . $profile->identifier is passed to DBConnection::exec()
    in web/admin/action_fedcheck.php on line 32
  11. Path: Read from $_REQUEST, and $id is assigned in web/user/cat_back.php on line 22
  1. Read from $_REQUEST, and $id is assigned
    in web/user/cat_back.php on line 22
  2. $id is passed to UserAPI::JSON_listProfiles()
    in web/user/cat_back.php on line 46
  3. $idp_id is passed to IdP::__construct()
    in core/UserAPI.php on line 331
  4. EntityWithDBProperties::$identifier is assigned
    in core/IdP.php on line 69
  5. Tainted property EntityWithDBProperties::$identifier is read
    in web/admin/action_fedcheck.php on line 38
  6. 'UPDATE profile SET ' . 'status_dns = ' . RETVAL_SKIPPED . ', ' . 'status_cert = ' . RETVAL_SKIPPED . ', ' . 'status_reachability = ' . RETVAL_SKIPPED . ', ' . 'status_TLS = ' . RETVAL_SKIPPED . ', ' . 'last_status_check = NOW() ' . 'WHERE profile_id = ' . $profile->identifier is passed to DBConnection::exec()
    in web/admin/action_fedcheck.php on line 32

Preventing SQL Injection

There are two options to prevent SQL injection. Generally, it is recommended to use parameter binding:

$stmt = mysqli_prepare("SELECT * FROM users WHERE name = ?");
$stmt->bind_param("s", $taintedUserName);

An alternative – although generally not recommended – is to escape your data manually:

$mysqli = new mysqli('localhost', 'user', 'pass', 'dbname');

$escaped = $mysqli->real_escape_string($taintedUserName);
$mysqli->query("SELECT * FROM users WHERE name = '".$escaped."'");

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
100
        if ($result == FALSE) {
101
            debug(1, "ERROR: Cannot execute query in $db database - (hopefully escaped) query was '$querystring'!");
102
            return FALSE;
103
        }
104
105
        // log exact query to audit log, if it's not a SELECT
106
        if (preg_match("/^SELECT/i", $querystring) == 0) {
107
            CAT::writeSQLAudit("[DB: " . strtoupper($db) . "] " . $querystring);
108
        }
109
        return $result;
110
    }
111
112
    /**
113
     * Retrieves data from the underlying tables, for situations where instantiating the IdP or Profile object is inappropriate
114
     * 
115
     * @param type $table institution_option or profile_option
116
     * @param type $row rowindex
117
     * @return boolean
118
     */
119
    public static function fetchRawDataByIndex($table, $row) {
120
        // only for select tables!
121
        if ($table != "institution_option" && $table != "profile_option" && $table != "federation_option") {
122
            return FALSE;
123
        }
124
        $blob_query = DBConnection::exec("INST", "SELECT option_value from $table WHERE row = $row");
125
        while ($a = mysqli_fetch_object($blob_query)) {
126
            $blob = $a->option_value;
127
        }
128
        if (!isset($blob)) {
129
            return FALSE;
130
        }
131
        return $blob;
132
    }
133
134
    /**
135
     * Checks if a raw data pointer is public data (return value FALSE) or if 
136
     * yes who the authorised admins to view it are (return array of user IDs)
137
     */
138
    public static function isDataRestricted($table, $row) {
139
        if ($table != "institution_option" && $table != "profile_option" && $table != "federation_option") {
140
            return []; // better safe than sorry: that's an error, so assume nobody is authorised to act on that data
141
        }
142
        switch ($table) {
143
            case "profile_option":
144
                $blob_query = DBConnection::exec("INST", "SELECT profile_id from $table WHERE row = $row");
145
                while ($profileIdQuery = mysqli_fetch_object($blob_query)) { // only one row
146
                    $blobprofile = $profileIdQuery->profile_id;
147
                }
148
                // is the profile in question public?
149
                $profile = new Profile($blobprofile);
0 ignored issues
show
Bug introduced by
The variable $blobprofile does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
150
                if ($profile->getShowtime() == TRUE) { // public data
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
151
                    return FALSE;
152
                } else {
153
                    $inst = new IdP($profile->institution);
154
                    return $inst->owner();
155
                }
156
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
157
            case "institution_option":
158
                $blob_query = DBConnection::exec("INST", "SELECT institution_id from $table WHERE row = $row");
159
                while ($instIdQuery = mysqli_fetch_object($blob_query)) { // only one row
160
                    $blobinst = $instIdQuery->institution_id;
161
                }
162
                $inst = new IdP($blobinst);
0 ignored issues
show
Bug introduced by
The variable $blobinst does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
163
                // if at least one of the profiles belonging to the inst is public, the data is public
164
                if ($inst->isOneProfileShowtime()) { // public data
165
                    return FALSE;
166
                } else {
167
                    return $inst->owner();
168
                }
169
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
170
            case "federation_option":
171
                // federation metadata is always public
172
                return FALSE;
173
            default:
174
                return []; // better safe than sorry: that's an error, so assume nobody is authorised to act on that data
175
        }
176
    }
177
178
    /**
179
     * Retrieves the last auto-id of an INSERT. Needs to be called immediately after the corresponding exec() call
180
     * @return int the last autoincrement-ID
181
     */
182
    public static function lastID($db) {
183
        $instance = DBConnection::handle($db);
184
        return mysqli_insert_id($instance->connection);
185
    }
186
187
    /**
188
     * Holds the singleton instance reference
189
     * 
190
     * @var DBConnection 
191
     */
192
    private static $instance_user;
193
    private static $instance_inst;
194
    private static $instance_external;
195
196
    /**
197
     * The connection to the DB server
198
     * 
199
     * @var mysqli
200
     */
201
    private $connection;
202
203
    /**
204
     * Class constructor. Cannot be called directly; use handle()
205
     */
206
    private function __construct($db) {
207
        $DB = strtoupper($db);
208
        $this->connection = mysqli_connect(Config::$DB[$DB]['host'], Config::$DB[$DB]['user'], Config::$DB[$DB]['pass'], Config::$DB[$DB]['db']) or die("ERROR: Unable to connect to $DB database! This is a fatal error, giving up.");
209
        if ($this->connection == FALSE) {
210
            echo "ERROR: Unable to connect to $db database! This is a fatal error, giving up.";
211
            exit(1);
212
        }
213
214
        if ($db == "EXTERNAL" && Config::$CONSORTIUM['name'] == "eduroam" && isset(Config::$CONSORTIUM['deployment-voodoo']) && Config::$CONSORTIUM['deployment-voodoo'] == "Operations Team")
215
            mysqli_query($this->connection, "SET NAMES 'latin1'");
216
    }
217
218
}