Completed
Pull Request — master (#23)
by Damian
08:37
created

SQLiteDatabaseConfigurationHelper   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 216
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 31
c 1
b 0
f 0
lcom 1
cbo 1
dl 0
loc 216
rs 9.8

10 Methods

Rating   Name   Duplication   Size   Complexity  
C createConnection() 0 39 7
A requireDatabaseFunctions() 0 5 1
B requireDatabaseServer() 0 21 5
B requireDatabaseConnection() 0 42 5
A getDatabaseVersion() 0 20 4
A requireDatabaseVersion() 0 18 3
A requireDatabaseOrCreatePermissions() 0 9 1
A create_db_dir() 0 4 2
A secure_db_dir() 0 4 2
A requireDatabaseAlterPermissions() 0 8 1
1
<?php
2
3
namespace SilverStripe\SQLite;
4
5
use DatabaseConfigurationHelper;
6
use SQLite3;
7
use PDO;
8
use Exception;
9
use DatabaseAdapterRegistry;
10
11
/**
12
 * This is a helper class for the SS installer.
13
 *
14
 * It does all the specific checking for SQLiteDatabase
15
 * to ensure that the configuration is setup correctly.
16
 *
17
 * @package SQLite3
18
 */
19
class SQLiteDatabaseConfigurationHelper implements DatabaseConfigurationHelper
20
{
21
22
    /**
23
     * Create a connection of the appropriate type
24
     *
25
     * @param array $databaseConfig
26
     * @param string $error Error message passed by value
27
     * @return mixed|null Either the connection object, or null if error
28
     */
29
    protected function createConnection($databaseConfig, &$error)
30
    {
31
        $error = null;
32
        try {
33
            if (!file_exists($databaseConfig['path'])) {
34
                self::create_db_dir($databaseConfig['path']);
35
                self::secure_db_dir($databaseConfig['path']);
36
            }
37
            $file = $databaseConfig['path'] . '/' . $databaseConfig['database'];
38
            $conn = null;
39
40
            switch ($databaseConfig['type']) {
41
                case 'SQLite3Database':
42
                    if (empty($databaseConfig['key'])) {
43
                        $conn = @new SQLite3($file, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE);
44
                    } else {
45
                        $conn = @new SQLite3($file, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $databaseConfig['key']);
46
                    }
47
                    break;
48
                case 'SQLite3PDODatabase':
49
                    // May throw a PDOException if fails
50
                    $conn = @new PDO("sqlite:$file");
51
                    break;
52
                default:
53
                    $error = 'Invalid connection type';
54
                    return null;
55
            }
56
57
            if ($conn) {
58
                return $conn;
59
            } else {
60
                $error = 'Unknown connection error';
61
                return null;
62
            }
63
        } catch (Exception $ex) {
64
            $error = $ex->getMessage();
65
            return null;
66
        }
67
    }
68
69
    public function requireDatabaseFunctions($databaseConfig)
70
    {
71
        $data = DatabaseAdapterRegistry::get_adapter($databaseConfig['type']);
72
        return !empty($data['supported']);
73
    }
74
75
    public function requireDatabaseServer($databaseConfig)
76
    {
77
        $path = $databaseConfig['path'];
78
        $error = '';
79
        $success = false;
80
81
        if (!$path) {
82
            $error = 'No database path provided';
83
        } elseif (is_writable($path) || (!file_exists($path) && is_writable(dirname($path)))) {
84
            // check if folder is writeable
85
            $success = true;
86
        } else {
87
            $error = "Permission denied";
88
        }
89
90
        return array(
91
            'success' => $success,
92
            'error' => $error,
93
            'path' => $path
94
        );
95
    }
96
97
    /**
98
     * Ensure a database connection is possible using credentials provided.
99
     *
100
     * @todo Validate path
101
     *
102
     * @param array $databaseConfig Associative array of db configuration, e.g. "type", "path" etc
103
     * @return array Result - e.g. array('success' => true, 'error' => 'details of error')
104
     */
105
    public function requireDatabaseConnection($databaseConfig)
106
    {
107
        // Do additional validation around file paths
108
        if (empty($databaseConfig['path'])) {
109
            return array(
110
            'success' => false,
111
            'error' => "Missing directory path"
112
        );
113
        }
114
        if (empty($databaseConfig['database'])) {
115
            return array(
116
            'success' => false,
117
            'error' => "Missing database filename"
118
        );
119
        }
120
121
        // Create and secure db directory
122
        $path = $databaseConfig['path'];
123
        $dirCreated = self::create_db_dir($path);
124
        if (!$dirCreated) {
125
            return array(
126
            'success' => false,
127
            'error' => sprintf('Cannot create path: "%s"', $path)
128
        );
129
        }
130
        $dirSecured = self::secure_db_dir($path);
131
        if (!$dirSecured) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dirSecured of type integer|false is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
132
            return array(
133
            'success' => false,
134
            'error' => sprintf('Cannot secure path through .htaccess: "%s"', $path)
135
        );
136
        }
137
138
        $conn = $this->createConnection($databaseConfig, $error);
139
        $success = !empty($conn);
140
141
        return array(
142
            'success' => $success,
143
            'connection' => $conn,
144
            'error' => $error
145
        );
146
    }
147
148
    public function getDatabaseVersion($databaseConfig)
149
    {
150
        $version = 0;
151
152
        switch ($databaseConfig['type']) {
153
            case 'SQLite3Database':
154
                $info = SQLite3::version();
155
                $version = trim($info['versionString']);
156
                break;
157
            case 'SQLite3PDODatabase':
158
                // Fallback to using sqlite_version() query
159
                $conn = $this->createConnection($databaseConfig, $error);
160
                if ($conn) {
161
                    $version = $conn->getAttribute(PDO::ATTR_SERVER_VERSION);
162
                }
163
                break;
164
        }
165
166
        return $version;
167
    }
168
169
    public function requireDatabaseVersion($databaseConfig)
170
    {
171
        $success = false;
172
        $error = '';
173
        $version = $this->getDatabaseVersion($databaseConfig);
174
175
        if ($version) {
176
            $success = version_compare($version, '3.3', '>=');
177
            if (!$success) {
178
                $error = "Your SQLite3 library version is $version. It's recommended you use at least 3.3.";
179
            }
180
        }
181
182
        return array(
183
            'success' => $success,
184
            'error' => $error
185
        );
186
    }
187
188
    public function requireDatabaseOrCreatePermissions($databaseConfig)
189
    {
190
        $conn = $this->createConnection($databaseConfig, $error);
191
        $success = $alreadyExists = !empty($conn);
192
        return array(
193
            'success' => $success,
194
            'alreadyExists' => $alreadyExists,
195
        );
196
    }
197
198
    /**
199
     * Creates the provided directory and prepares it for
200
     * storing SQLlite. Use {@link secure_db_dir()} to
201
     * secure it against unauthorized access.
202
     *
203
     * @param String $path Absolute path, usually with a hidden folder.
204
     * @return boolean
205
     */
206
    public static function create_db_dir($path)
207
    {
208
        return file_exists($path) || mkdir($path);
209
    }
210
211
    /**
212
     * Secure the provided directory via web-access
213
     * by placing a .htaccess file in it.
214
     * This is just required if the database directory
215
     * is placed within a publically accessible webroot (the
216
     * default path is in a hidden folder within assets/).
217
     *
218
     * @param String $path Absolute path, containing a SQLite datatbase
219
     * @return boolean
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
220
     */
221
    public static function secure_db_dir($path)
222
    {
223
        return (is_writeable($path)) ? file_put_contents($path . '/.htaccess', 'deny from all') : false;
224
    }
225
226
    public function requireDatabaseAlterPermissions($databaseConfig)
227
    {
228
        // no concept of table-specific permissions; If you can connect you can alter schema
229
        return array(
230
            'success' => true,
231
            'applies' => false
232
        );
233
    }
234
}
235