SQLiteDatabaseConfigurationHelper   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 218
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

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

10 Methods

Rating   Name   Duplication   Size   Complexity  
C createConnection() 0 40 7
A requireDatabaseFunctions() 0 5 1
B requireDatabaseServer() 0 21 5
B requireDatabaseConnection() 0 42 5
A getDatabaseVersion() 0 21 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
        /** @skipUpgrade */
33
        try {
34
            if (!file_exists($databaseConfig['path'])) {
35
                self::create_db_dir($databaseConfig['path']);
36
                self::secure_db_dir($databaseConfig['path']);
37
            }
38
            $file = $databaseConfig['path'] . '/' . $databaseConfig['database'];
39
            $conn = null;
40
41
            switch ($databaseConfig['type']) {
42
                case 'SQLite3Database':
43
                    if (empty($databaseConfig['key'])) {
44
                        $conn = @new SQLite3($file, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE);
45
                    } else {
46
                        $conn = @new SQLite3($file, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $databaseConfig['key']);
47
                    }
48
                    break;
49
                case 'SQLite3PDODatabase':
50
                    // May throw a PDOException if fails
51
                    $conn = @new PDO("sqlite:$file");
52
                    break;
53
                default:
54
                    $error = 'Invalid connection type';
55
                    return null;
56
            }
57
58
            if ($conn) {
59
                return $conn;
60
            } else {
61
                $error = 'Unknown connection error';
62
                return null;
63
            }
64
        } catch (Exception $ex) {
65
            $error = $ex->getMessage();
66
            return null;
67
        }
68
    }
69
70
    public function requireDatabaseFunctions($databaseConfig)
71
    {
72
        $data = DatabaseAdapterRegistry::get_adapter($databaseConfig['type']);
73
        return !empty($data['supported']);
74
    }
75
76
    public function requireDatabaseServer($databaseConfig)
77
    {
78
        $path = $databaseConfig['path'];
79
        $error = '';
80
        $success = false;
81
82
        if (!$path) {
83
            $error = 'No database path provided';
84
        } elseif (is_writable($path) || (!file_exists($path) && is_writable(dirname($path)))) {
85
            // check if folder is writeable
86
            $success = true;
87
        } else {
88
            $error = "Permission denied";
89
        }
90
91
        return array(
92
            'success' => $success,
93
            'error' => $error,
94
            'path' => $path
95
        );
96
    }
97
98
    /**
99
     * Ensure a database connection is possible using credentials provided.
100
     *
101
     * @todo Validate path
102
     *
103
     * @param array $databaseConfig Associative array of db configuration, e.g. "type", "path" etc
104
     * @return array Result - e.g. array('success' => true, 'error' => 'details of error')
105
     */
106
    public function requireDatabaseConnection($databaseConfig)
107
    {
108
        // Do additional validation around file paths
109
        if (empty($databaseConfig['path'])) {
110
            return array(
111
            'success' => false,
112
            'error' => "Missing directory path"
113
        );
114
        }
115
        if (empty($databaseConfig['database'])) {
116
            return array(
117
            'success' => false,
118
            'error' => "Missing database filename"
119
        );
120
        }
121
122
        // Create and secure db directory
123
        $path = $databaseConfig['path'];
124
        $dirCreated = self::create_db_dir($path);
125
        if (!$dirCreated) {
126
            return array(
127
            'success' => false,
128
            'error' => sprintf('Cannot create path: "%s"', $path)
129
        );
130
        }
131
        $dirSecured = self::secure_db_dir($path);
132
        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...
133
            return array(
134
            'success' => false,
135
            'error' => sprintf('Cannot secure path through .htaccess: "%s"', $path)
136
        );
137
        }
138
139
        $conn = $this->createConnection($databaseConfig, $error);
140
        $success = !empty($conn);
141
142
        return array(
143
            'success' => $success,
144
            'connection' => $conn,
145
            'error' => $error
146
        );
147
    }
148
149
    public function getDatabaseVersion($databaseConfig)
150
    {
151
        $version = 0;
152
153
        /** @skipUpgrade */
154
        switch ($databaseConfig['type']) {
155
            case 'SQLite3Database':
156
                $info = SQLite3::version();
157
                $version = trim($info['versionString']);
158
                break;
159
            case 'SQLite3PDODatabase':
160
                // Fallback to using sqlite_version() query
161
                $conn = $this->createConnection($databaseConfig, $error);
162
                if ($conn) {
163
                    $version = $conn->getAttribute(PDO::ATTR_SERVER_VERSION);
164
                }
165
                break;
166
        }
167
168
        return $version;
169
    }
170
171
    public function requireDatabaseVersion($databaseConfig)
172
    {
173
        $success = false;
174
        $error = '';
175
        $version = $this->getDatabaseVersion($databaseConfig);
176
177
        if ($version) {
178
            $success = version_compare($version, '3.3', '>=');
179
            if (!$success) {
180
                $error = "Your SQLite3 library version is $version. It's recommended you use at least 3.3.";
181
            }
182
        }
183
184
        return array(
185
            'success' => $success,
186
            'error' => $error
187
        );
188
    }
189
190
    public function requireDatabaseOrCreatePermissions($databaseConfig)
191
    {
192
        $conn = $this->createConnection($databaseConfig, $error);
193
        $success = $alreadyExists = !empty($conn);
194
        return array(
195
            'success' => $success,
196
            'alreadyExists' => $alreadyExists,
197
        );
198
    }
199
200
    /**
201
     * Creates the provided directory and prepares it for
202
     * storing SQLlite. Use {@link secure_db_dir()} to
203
     * secure it against unauthorized access.
204
     *
205
     * @param String $path Absolute path, usually with a hidden folder.
206
     * @return boolean
207
     */
208
    public static function create_db_dir($path)
209
    {
210
        return file_exists($path) || mkdir($path);
211
    }
212
213
    /**
214
     * Secure the provided directory via web-access
215
     * by placing a .htaccess file in it.
216
     * This is just required if the database directory
217
     * is placed within a publically accessible webroot (the
218
     * default path is in a hidden folder within assets/).
219
     *
220
     * @param String $path Absolute path, containing a SQLite datatbase
221
     * @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...
222
     */
223
    public static function secure_db_dir($path)
224
    {
225
        return (is_writeable($path)) ? file_put_contents($path . '/.htaccess', 'deny from all') : false;
226
    }
227
228
    public function requireDatabaseAlterPermissions($databaseConfig)
229
    {
230
        // no concept of table-specific permissions; If you can connect you can alter schema
231
        return array(
232
            'success' => true,
233
            'applies' => false
234
        );
235
    }
236
}
237