Completed
Push — master ( 9309cf...551ccf )
by Sean
10s
created

MSSQLDatabaseConfigurationHelper::getPDODriver()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 11
rs 9.2
cc 4
eloc 7
nc 4
nop 0
1
<?php
2
3
namespace SilverStripe\MSSQL;
4
5
use DatabaseConfigurationHelper;
6
use PDO;
7
use Exception;
8
use DatabaseAdapterRegistry;
9
10
/**
11
 * This is a helper class for the SS installer.
12
 *
13
 * It does all the specific checking for MSSQLDatabase
14
 * to ensure that the configuration is setup correctly.
15
 *
16
 * @package mssql
17
 */
18
class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
19
{
20
21
    protected function isAzure($databaseConfig)
22
    {
23
        /** @skipUpgrade */
24
        return $databaseConfig['type'] === 'MSSQLAzureDatabase';
25
    }
26
27
    /**
28
     * Create a connection of the appropriate type
29
     *
30
     * @param array $databaseConfig
31
     * @param string $error Error message passed by value
32
     * @return mixed|null Either the connection object, or null if error
33
     */
34
    protected function createConnection($databaseConfig, &$error)
35
    {
36
        $error = null;
37
        /** @skipUpgrade */
38
        try {
39
            switch ($databaseConfig['type']) {
40
                case 'MSSQLDatabase':
41
                case 'MSSQLAzureDatabase':
42
                    $parameters = array(
43
                        'UID' => $databaseConfig['username'],
44
                        'PWD' => $databaseConfig['password']
45
                    );
46
47
                    // Azure has additional parameter requirements
48
                    if ($this->isAzure($databaseConfig)) {
49
                        $parameters['database'] = $databaseConfig['database'];
50
                        $parameters['multipleactiveresultsets'] = 0;
51
                    }
52
                    $conn = @sqlsrv_connect($databaseConfig['server'], $parameters);
53
                    if ($conn) {
54
                        return $conn;
55
                    }
56
57
                    // Get error
58
                    if ($errors = sqlsrv_errors()) {
59
                        $error = '';
60
                        foreach ($errors as $detail) {
61
                            $error .= "{$detail['message']}\n";
62
                        }
63
                    } else {
64
                        $error = 'Unknown connection error';
65
                    }
66
                    return null;
67
                case 'MSSQLPDODatabase':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
68
                    $driver = $this->getPDODriver();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $driver is correct as $this->getPDODriver() (which targets SilverStripe\MSSQL\MSSQL...nHelper::getPDODriver()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
69
                    if (!$driver) {
70
                        $error = 'No supported PDO driver';
71
                        return null;
72
                    }
73
74
                    // May throw a PDOException if fails
75
                    $conn = @new PDO($driver.':Server='.$databaseConfig['server'], $databaseConfig['username'], $databaseConfig['password']);
76
                    if ($conn) {
77
                        return $conn;
78
                    } else {
79
                        $error = 'Unknown connection error';
80
                        return null;
81
                    }
82
                default:
83
                    $error = 'Invalid connection type';
84
                    return null;
85
            }
86
        } catch (Exception $ex) {
87
            $error = $ex->getMessage();
88
            return null;
89
        }
90
    }
91
92
    /**
93
     * Get supported PDO driver
94
     *
95
     * @return null
96
     */
97
    public static function getPDODriver() {
98
        if (!class_exists('PDO')) {
99
            return null;
100
        }
101
        foreach(PDO::getAvailableDrivers() as $driver) {
102
            if(in_array($driver, array('sqlsrv', 'dblib'))) {
103
                return $driver;
104
            }
105
        }
106
        return null;
107
    }
108
109
    /**
110
     * Helper function to quote a string value
111
     *
112
     * @param mixed $conn Connection object/resource
113
     * @param string $value Value to quote
114
     * @return string Quoted strieng
115
     */
116
    protected function quote($conn, $value)
117
    {
118
        if ($conn instanceof PDO) {
119
            return $conn->quote($value);
120
        } elseif (is_resource($conn)) {
121
            $value = str_replace("'", "''", $value);
122
            $value = str_replace("\0", "[NULL]", $value);
123
            return "N'$value'";
124
        } else {
125
            user_error('Invalid database connection', E_USER_ERROR);
126
        }
127
    }
128
129
    /**
130
     * Helper function to execute a query
131
     *
132
     * @param mixed $conn Connection object/resource
133
     * @param string $sql SQL string to execute
134
     * @return array List of first value from each resulting row
135
     */
136
    protected function query($conn, $sql)
137
    {
138
        $items = array();
139
        if ($conn instanceof PDO) {
140
            $result = $conn->query($sql);
141
            if ($result) {
142
                foreach ($result as $row) {
143
                    $items[] = $row[0];
144
                }
145
            }
146
        } elseif (is_resource($conn)) {
147
            $result = sqlsrv_query($conn, $sql);
148
            if ($result) {
149
                while ($row = sqlsrv_fetch_array($result, SQLSRV_FETCH_NUMERIC)) {
150
                    $items[] = $row[0];
151
                }
152
            }
153
        }
154
        return $items;
155
    }
156
157
    public function requireDatabaseFunctions($databaseConfig)
158
    {
159
        $data = DatabaseAdapterRegistry::get_adapter($databaseConfig['type']);
160
        return !empty($data['supported']);
161
    }
162
163 View Code Duplication
    public function requireDatabaseServer($databaseConfig)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
164
    {
165
        $conn = $this->createConnection($databaseConfig, $error);
166
        $success = !empty($conn);
167
168
        return array(
169
            'success' => $success,
170
            'error' => $error
171
        );
172
    }
173
174 View Code Duplication
    public function requireDatabaseConnection($databaseConfig)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
175
    {
176
        $conn = $this->createConnection($databaseConfig, $error);
177
        $success = !empty($conn);
178
179
        return array(
180
            'success' => $success,
181
            'connection' => $conn,
182
            'error' => $error
183
        );
184
    }
185
186
    public function getDatabaseVersion($databaseConfig)
187
    {
188
        $conn = $this->createConnection($databaseConfig, $error);
189
        $result = $this->query($conn, "SELECT CONVERT(char(15), SERVERPROPERTY('ProductVersion'))");
190
        return empty($result) ? 0 : reset($result);
191
    }
192
193
    /**
194
     * Ensure that the SQL Server version is at least 10.00.2531 (SQL Server 2008 SP1).
195
     *
196
     * @see http://www.sqlteam.com/article/sql-server-versions
197
     * @param array $databaseConfig Associative array of db configuration, e.g. "server", "username" etc
198
     * @return array Result - e.g. array('success' => true, 'error' => 'details of error')
199
     */
200
    public function requireDatabaseVersion($databaseConfig)
201
    {
202
        $success = false;
203
        $error = '';
204
        $version = $this->getDatabaseVersion($databaseConfig);
205
206
        if ($version) {
207
            $success = version_compare($version, '10.00.2531', '>=');
208
            if (!$success) {
209
                $error = "Your SQL Server version is $version. It's recommended you use at least 10.00.2531 (SQL Server 2008 SP1).";
210
            }
211
        } else {
212
            $error = "Your SQL Server version could not be determined.";
213
        }
214
215
        return array(
216
            'success' => $success,
217
            'error' => $error
218
        );
219
    }
220
221
    public function requireDatabaseOrCreatePermissions($databaseConfig)
222
    {
223
        $conn = $this->createConnection($databaseConfig, $error);
224
        /** @skipUpgrade */
225
        if (empty($conn)) {
226
            $success = false;
227
            $alreadyExists = false;
228
        } elseif ($databaseConfig['type'] == 'MSSQLAzureDatabase') {
229
            // Don't bother with DB selection for azure, as it's not supported
230
            $success = true;
231
            $alreadyExists = true;
232
        } else {
233
            // does this database exist already?
234
            $list = $this->query($conn, 'SELECT NAME FROM sys.sysdatabases');
235
            if (in_array($databaseConfig['database'], $list)) {
236
                $success = true;
237
                $alreadyExists = true;
238
            } else {
239
                $permissions = $this->query($conn, "select COUNT(*) from sys.fn_my_permissions('','') where permission_name like 'CREATE ANY DATABASE' or permission_name like 'CREATE DATABASE';");
240
                $success = $permissions[0] > 0;
241
                $alreadyExists = false;
242
            }
243
        }
244
245
        return array(
246
            'success' => $success,
247
            'alreadyExists' => $alreadyExists
248
        );
249
    }
250
251
    public function requireDatabaseAlterPermissions($databaseConfig)
252
    {
253
        $success = false;
254
        $conn = $this->createConnection($databaseConfig, $error);
255
        if (!empty($conn)) {
256
            if (!$this->isAzure($databaseConfig)) {
257
                // Make sure to select the current database when checking permission against this database
258
                $this->query($conn, "USE \"{$databaseConfig['database']}\"");
259
            }
260
            $permissions = $this->query($conn, "select COUNT(*) from sys.fn_my_permissions(NULL,'DATABASE') WHERE permission_name like 'create table';");
261
            $success = $permissions[0] > 0;
262
        }
263
264
        return array(
265
            'success' => $success,
266
            'applies' => true
267
        );
268
    }
269
}
270