Completed
Pull Request — master (#31)
by Damian
02:13
created

MSSQLDatabaseConfigurationHelper::quote()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 3
eloc 9
nc 3
nop 2
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
                    // May throw a PDOException if fails
69
                    $conn = @new PDO('sqlsrv:Server='.$databaseConfig['server'], $databaseConfig['username'], $databaseConfig['password']);
70
                    if ($conn) {
71
                        return $conn;
72
                    } else {
73
                        $error = 'Unknown connection error';
74
                        return null;
75
                    }
76
                default:
77
                    $error = 'Invalid connection type';
78
                    return null;
79
            }
80
        } catch (Exception $ex) {
81
            $error = $ex->getMessage();
82
            return null;
83
        }
84
    }
85
86
    /**
87
     * Helper function to quote a string value
88
     *
89
     * @param mixed $conn Connection object/resource
90
     * @param string $value Value to quote
91
     * @return string Quoted strieng
92
     */
93
    protected function quote($conn, $value)
94
    {
95
        if ($conn instanceof PDO) {
96
            return $conn->quote($value);
97
        } elseif (is_resource($conn)) {
98
            $value = str_replace("'", "''", $value);
99
            $value = str_replace("\0", "[NULL]", $value);
100
            return "N'$value'";
101
        } else {
102
            user_error('Invalid database connection', E_USER_ERROR);
103
        }
104
    }
105
106
    /**
107
     * Helper function to execute a query
108
     *
109
     * @param mixed $conn Connection object/resource
110
     * @param string $sql SQL string to execute
111
     * @return array List of first value from each resulting row
112
     */
113
    protected function query($conn, $sql)
114
    {
115
        $items = array();
116
        if ($conn instanceof PDO) {
117
            $result = $conn->query($sql);
118
            if ($result) {
119
                foreach ($result as $row) {
120
                    $items[] = $row[0];
121
                }
122
            }
123
        } elseif (is_resource($conn)) {
124
            $result = sqlsrv_query($conn, $sql);
125
            if ($result) {
126
                while ($row = sqlsrv_fetch_array($result, SQLSRV_FETCH_NUMERIC)) {
127
                    $items[] = $row[0];
128
                }
129
            }
130
        }
131
        return $items;
132
    }
133
134
    public function requireDatabaseFunctions($databaseConfig)
135
    {
136
        $data = DatabaseAdapterRegistry::get_adapter($databaseConfig['type']);
137
        return !empty($data['supported']);
138
    }
139
140 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...
141
    {
142
        $conn = $this->createConnection($databaseConfig, $error);
143
        $success = !empty($conn);
144
145
        return array(
146
            'success' => $success,
147
            'error' => $error
148
        );
149
    }
150
151 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...
152
    {
153
        $conn = $this->createConnection($databaseConfig, $error);
154
        $success = !empty($conn);
155
156
        return array(
157
            'success' => $success,
158
            'connection' => $conn,
159
            'error' => $error
160
        );
161
    }
162
163
    public function getDatabaseVersion($databaseConfig)
164
    {
165
        $conn = $this->createConnection($databaseConfig, $error);
166
        $result = $this->query($conn, "SELECT CONVERT(char(15), SERVERPROPERTY('ProductVersion'))");
167
        return empty($result) ? 0 : reset($result);
168
    }
169
170
    /**
171
     * Ensure that the SQL Server version is at least 10.00.2531 (SQL Server 2008 SP1).
172
     *
173
     * @see http://www.sqlteam.com/article/sql-server-versions
174
     * @param array $databaseConfig Associative array of db configuration, e.g. "server", "username" etc
175
     * @return array Result - e.g. array('success' => true, 'error' => 'details of error')
176
     */
177
    public function requireDatabaseVersion($databaseConfig)
178
    {
179
        $success = false;
180
        $error = '';
181
        $version = $this->getDatabaseVersion($databaseConfig);
182
183
        if ($version) {
184
            $success = version_compare($version, '10.00.2531', '>=');
185
            if (!$success) {
186
                $error = "Your SQL Server version is $version. It's recommended you use at least 10.00.2531 (SQL Server 2008 SP1).";
187
            }
188
        } else {
189
            $error = "Your SQL Server version could not be determined.";
190
        }
191
192
        return array(
193
            'success' => $success,
194
            'error' => $error
195
        );
196
    }
197
198
    public function requireDatabaseOrCreatePermissions($databaseConfig)
199
    {
200
        $conn = $this->createConnection($databaseConfig, $error);
201
        /** @skipUpgrade */
202
        if (empty($conn)) {
203
            $success = false;
204
            $alreadyExists = false;
205
        } elseif ($databaseConfig['type'] == 'MSSQLAzureDatabase') {
206
            // Don't bother with DB selection for azure, as it's not supported
207
            $success = true;
208
            $alreadyExists = true;
209
        } else {
210
            // does this database exist already?
211
            $list = $this->query($conn, 'SELECT NAME FROM sys.sysdatabases');
212
            if (in_array($databaseConfig['database'], $list)) {
213
                $success = true;
214
                $alreadyExists = true;
215
            } else {
216
                $permissions = $this->query($conn, "select COUNT(*) from sys.fn_my_permissions('','') where permission_name like 'CREATE ANY DATABASE' or permission_name like 'CREATE DATABASE';");
217
                $success = $permissions[0] > 0;
218
                $alreadyExists = false;
219
            }
220
        }
221
222
        return array(
223
            'success' => $success,
224
            'alreadyExists' => $alreadyExists
225
        );
226
    }
227
228
    public function requireDatabaseAlterPermissions($databaseConfig)
229
    {
230
        $success = false;
231
        $conn = $this->createConnection($databaseConfig, $error);
232
        if (!empty($conn)) {
233
            if (!$this->isAzure($databaseConfig)) {
234
                // Make sure to select the current database when checking permission against this database
235
                $this->query($conn, "USE \"{$databaseConfig['database']}\"");
236
            }
237
            $permissions = $this->query($conn, "select COUNT(*) from sys.fn_my_permissions(NULL,'DATABASE') WHERE permission_name like 'create table';");
238
            $success = $permissions[0] > 0;
239
        }
240
241
        return array(
242
            'success' => $success,
243
            'applies' => true
244
        );
245
    }
246
}
247