Subversion Repositories oidplus

Rev

Rev 1234 | Rev 1240 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1234 Rev 1235
1
<?php
1
<?php
2
 
2
 
3
/*
3
/*
4
 * OIDplus 2.0
4
 * OIDplus 2.0
5
 * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
5
 * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
6
 *
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
9
 * You may obtain a copy of the License at
10
 *
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
17
 * limitations under the License.
18
 */
18
 */
19
 
19
 
20
namespace ViaThinkSoft\OIDplus;
20
namespace ViaThinkSoft\OIDplus;
21
 
21
 
22
// phpcs:disable PSR1.Files.SideEffects
22
// phpcs:disable PSR1.Files.SideEffects
23
\defined('INSIDE_OIDPLUS') or die;
23
\defined('INSIDE_OIDPLUS') or die;
24
// phpcs:enable PSR1.Files.SideEffects
24
// phpcs:enable PSR1.Files.SideEffects
25
 
25
 
26
class OIDplusDatabaseConnectionSqlSrv extends OIDplusDatabaseConnection {
26
class OIDplusDatabaseConnectionSqlSrv extends OIDplusDatabaseConnection {
27
 
27
 
28
        /**
28
        /**
29
         * @var mixed|null
29
         * @var mixed|null
30
         */
30
         */
31
        private $conn = null;
31
        private $conn = null;
32
 
32
 
33
        /**
33
        /**
34
         * @var string|null
34
         * @var string|null
35
         */
35
         */
36
        private $last_error = null;
36
        private $last_error = null;
37
 
37
 
38
        /**
38
        /**
39
         * @var int
39
         * @var int
40
         */
40
         */
41
        private $rowsAffected = 0;
41
        private $rowsAffected = 0;
42
 
42
 
43
        /**
43
        /**
44
         * @return int
44
         * @return int
45
         */
45
         */
46
        public function rowsAffected(): int {
46
        public function rowsAffected(): int {
47
                return $this->rowsAffected;
47
                return $this->rowsAffected;
48
        }
48
        }
49
 
49
 
50
        /**
50
        /**
51
         * @param string $sql
51
         * @param string $sql
52
         * @param array|null $prepared_args
52
         * @param array|null $prepared_args
53
         * @return OIDplusQueryResultSqlSrv
53
         * @return OIDplusQueryResultSqlSrv
54
         * @throws OIDplusException
54
         * @throws OIDplusException
55
         */
55
         */
56
        public function doQuery(string $sql, array $prepared_args=null): OIDplusQueryResult {
56
        public function doQuery(string $sql, array $prepared_args=null): OIDplusQueryResult {
57
                $this->last_error = null;
57
                $this->last_error = null;
58
                try {
58
                try {
59
                        $res = sqlsrv_query($this->conn, $sql, $prepared_args,
59
                        $res = sqlsrv_query($this->conn, $sql, $prepared_args,
60
                                array(
60
                                array(
61
                                        // SQLSRV_CURSOR_FORWARD ('forward', default)
61
                                        // SQLSRV_CURSOR_FORWARD ('forward', default)
62
                                        // Lets you move one row at a time starting at the first row of the result set until you reach the end of the result set.
62
                                        // Lets you move one row at a time starting at the first row of the result set until you reach the end of the result set.
63
                                        // => Does not work with sqlsrv_num_rows();
63
                                        // => Does not work with sqlsrv_num_rows();
64
 
64
 
65
                                        // SQLSRV_CURSOR_STATIC ('static')
65
                                        // SQLSRV_CURSOR_STATIC ('static')
66
                                        // Lets you access rows in any order but will not reflect changes in the database.
66
                                        // Lets you access rows in any order but will not reflect changes in the database.
67
                                        // => Does not work with transaction rollback?! (Testcase failed)
67
                                        // => Does not work with transaction rollback?! (Testcase failed)
68
 
68
 
69
                                        // SQLSRV_CURSOR_DYNAMIC
69
                                        // SQLSRV_CURSOR_DYNAMIC
70
                                        // Lets you access rows in any order and will reflect changes in the database.
70
                                        // Lets you access rows in any order and will reflect changes in the database.
71
                                        // => Does not work with sqlsrv_num_rows();
71
                                        // => Does not work with sqlsrv_num_rows();
72
 
72
 
73
                                        // SQLSRV_CURSOR_KEYSET ('keyset')
73
                                        // SQLSRV_CURSOR_KEYSET ('keyset')
74
                                        // Lets you access rows in any order. However, a keyset cursor does not update the row count if a row is deleted from the table (a deleted row is returned with no values).
74
                                        // Lets you access rows in any order. However, a keyset cursor does not update the row count if a row is deleted from the table (a deleted row is returned with no values).
75
                                        // => Does not work with transaction rollback?! (Testcase failed)
75
                                        // => Does not work with transaction rollback?! (Testcase failed)
76
 
76
 
77
                                        // SQLSRV_CURSOR_CLIENT_BUFFERED ('buffered')
77
                                        // SQLSRV_CURSOR_CLIENT_BUFFERED ('buffered')
78
                                        // Lets you access rows in any order. Creates a client-side cursor query.
78
                                        // Lets you access rows in any order. Creates a client-side cursor query.
79
                                        // => Seems to work fine
79
                                        // => Seems to work fine
80
 
80
 
81
                                        'Scrollable' => SQLSRV_CURSOR_CLIENT_BUFFERED
81
                                        'Scrollable' => SQLSRV_CURSOR_CLIENT_BUFFERED
82
                                )
82
                                )
83
                        );
83
                        );
84
                } catch (\Exception $e) {
84
                } catch (\Exception $e) {
85
                        $this->last_error = $e->getMessage();
85
                        $this->last_error = $e->getMessage();
86
                        throw new OIDplusSQLException($sql, $e->getMessage());
86
                        throw new OIDplusSQLException($sql, $e->getMessage());
87
                }
87
                }
88
 
88
 
89
                if ($res === false) {
89
                if ($res === false) {
90
                        $this->last_error = print_r(sqlsrv_errors(), true);
90
                        $this->last_error = print_r(sqlsrv_errors(), true);
91
                        throw new OIDplusSQLException($sql, $this->error());
91
                        throw new OIDplusSQLException($sql, $this->error());
92
                } else {
92
                } else {
93
                        if (str_starts_with(trim(strtolower($sql)),'select')) {
93
                        if (str_starts_with(trim(strtolower($sql)),'select')) {
94
                                $this->rowsAffected = sqlsrv_num_rows($res);
94
                                $this->rowsAffected = sqlsrv_num_rows($res);
95
                        } else {
95
                        } else {
96
                                $this->rowsAffected = sqlsrv_rows_affected($res);
96
                                $this->rowsAffected = sqlsrv_rows_affected($res);
97
                        }
97
                        }
98
                        return new OIDplusQueryResultSqlSrv($res);
98
                        return new OIDplusQueryResultSqlSrv($res);
99
                }
99
                }
100
        }
100
        }
101
 
101
 
102
 
102
 
103
        /**
103
        /**
104
         * @return string
104
         * @return string
105
         */
105
         */
106
        public function error(): string {
106
        public function error(): string {
107
                $err = $this->last_error;
107
                $err = $this->last_error;
108
                if ($err === null) $err = '';
108
                if ($err === null) $err = '';
109
                return $err;
109
                return $err;
110
        }
110
        }
111
 
111
 
112
        /**
112
        /**
113
         * @return string
113
         * @return string
114
         */
114
         */
115
        private static function get_sqlsrv_dll_name(): string {
115
        private static function get_sqlsrv_dll_name(): string {
116
                ob_start();
116
                ob_start();
117
                phpinfo(INFO_GENERAL);
117
                phpinfo(INFO_GENERAL);
118
                $x = ob_get_contents();
118
                $x = ob_get_contents();
119
                ob_end_clean();
119
                ob_end_clean();
120
 
120
 
121
                $architecture =
121
                $architecture =
122
                        preg_match('@Architecture.+(x86|x64)@', $x, $m) ? $m[1] : '*';
122
                        preg_match('@Architecture.+(x86|x64)@', $x, $m) ? $m[1] : '*';
123
 
123
 
124
                $threadsafety =
124
                $threadsafety =
125
                        preg_match('@Thread Safety.+(enabled|disabled)@', $x, $m)
125
                        preg_match('@Thread Safety.+(enabled|disabled)@', $x, $m)
126
                        ? ($m[1] == 'enabled' ? 'ts' : 'nts') : '*';
126
                        ? ($m[1] == 'enabled' ? 'ts' : 'nts') : '*';
127
 
127
 
128
                $m = explode('.',phpversion());
128
                $m = explode('.',phpversion());
129
                $version = $m[0].$m[1];
129
                $version = $m[0].$m[1];
130
 
130
 
131
                // e.g. php_sqlsrv_82_ts_x64.dll
131
                // e.g. php_sqlsrv_82_ts_x64.dll
132
                return "php_sqlsrv_${version}_${threadsafety}_${architecture}.dll";
132
                return "php_sqlsrv_{$version}_{$threadsafety}_{$architecture}.dll";
133
        }
133
        }
134
 
134
 
135
        /**
135
        /**
136
         * @return void
136
         * @return void
137
         * @throws OIDplusConfigInitializationException
137
         * @throws OIDplusConfigInitializationException
138
         * @throws OIDplusException
138
         * @throws OIDplusException
139
         */
139
         */
140
        protected function doConnect()/*: void*/ {
140
        protected function doConnect()/*: void*/ {
141
                // Download here: https://learn.microsoft.com/en-us/sql/connect/php/download-drivers-php-sql-server?view=sql-server-ver16
141
                // Download here: https://learn.microsoft.com/en-us/sql/connect/php/download-drivers-php-sql-server?view=sql-server-ver16
142
                if (!function_exists('sqlsrv_connect')) throw new OIDplusException(_L('PHP extension "%1" not installed',self::get_sqlsrv_dll_name()));
142
                if (!function_exists('sqlsrv_connect')) throw new OIDplusException(_L('PHP extension "%1" not installed',self::get_sqlsrv_dll_name()));
143
 
143
 
144
                // Try connecting to the database
144
                // Try connecting to the database
145
                $servername = OIDplus::baseConfig()->getValue('SQLSRV_SERVER',   'localhost\oidplus');
145
                $servername = OIDplus::baseConfig()->getValue('SQLSRV_SERVER',   'localhost\oidplus');
146
                $username   = OIDplus::baseConfig()->getValue('SQLSRV_USERNAME', '');
146
                $username   = OIDplus::baseConfig()->getValue('SQLSRV_USERNAME', '');
147
                $password   = OIDplus::baseConfig()->getValue('SQLSRV_PASSWORD', '');
147
                $password   = OIDplus::baseConfig()->getValue('SQLSRV_PASSWORD', '');
148
                $database   = OIDplus::baseConfig()->getValue('SQLSRV_DATABASE', 'oidplus');
148
                $database   = OIDplus::baseConfig()->getValue('SQLSRV_DATABASE', 'oidplus');
149
                $options    = OIDplus::baseConfig()->getValue('SQLSRV_OPTIONS',  array());
149
                $options    = OIDplus::baseConfig()->getValue('SQLSRV_OPTIONS',  array());
150
 
150
 
151
                if (!isset($options['Database'])) $options['Database'] = $database;
151
                if (!isset($options['Database'])) $options['Database'] = $database;
152
                if (!isset($options['CharacterSet'])) $options['CharacterSet'] = 'UTF-8';
152
                if (!isset($options['CharacterSet'])) $options['CharacterSet'] = 'UTF-8';
153
                if ($username != '') {
153
                if ($username != '') {
154
                        if (!isset($options['UID'])) $options['UID'] = $username;
154
                        if (!isset($options['UID'])) $options['UID'] = $username;
155
                        if (!isset($options['PWD'])) $options['PWD'] = $password;
155
                        if (!isset($options['PWD'])) $options['PWD'] = $password;
156
                }
156
                }
157
 
157
 
158
                $this->conn = @sqlsrv_connect($servername, $options);
158
                $this->conn = @sqlsrv_connect($servername, $options);
159
 
159
 
160
                if (!$this->conn) {
160
                if (!$this->conn) {
161
                        $message = print_r(sqlsrv_errors(), true);
161
                        $message = print_r(sqlsrv_errors(), true);
162
                        throw new OIDplusConfigInitializationException(trim(_L('Connection to the database failed!').' '.$message));
162
                        throw new OIDplusConfigInitializationException(trim(_L('Connection to the database failed!').' '.$message));
163
                }
163
                }
164
 
164
 
165
                $this->last_error = null;
165
                $this->last_error = null;
166
        }
166
        }
167
 
167
 
168
        /**
168
        /**
169
         * @return void
169
         * @return void
170
         */
170
         */
171
        protected function doDisconnect()/*: void*/ {
171
        protected function doDisconnect()/*: void*/ {
172
                if (!is_null($this->conn)) {
172
                if (!is_null($this->conn)) {
173
                        sqlsrv_close($this->conn);
173
                        sqlsrv_close($this->conn);
174
                        $this->conn = null;
174
                        $this->conn = null;
175
                }
175
                }
176
        }
176
        }
177
 
177
 
178
        /**
178
        /**
179
         * @var bool
179
         * @var bool
180
         */
180
         */
181
        private $intransaction = false;
181
        private $intransaction = false;
182
 
182
 
183
        /**
183
        /**
184
         * @return bool
184
         * @return bool
185
         */
185
         */
186
        public function transaction_supported(): bool {
186
        public function transaction_supported(): bool {
187
                return true;
187
                return true;
188
        }
188
        }
189
 
189
 
190
        /**
190
        /**
191
         * @return int
191
         * @return int
192
         */
192
         */
193
        public function transaction_level(): int {
193
        public function transaction_level(): int {
194
                return $this->intransaction ? 1 : 0;
194
                return $this->intransaction ? 1 : 0;
195
        }
195
        }
196
 
196
 
197
        /**
197
        /**
198
         * @return void
198
         * @return void
199
         * @throws OIDplusException
199
         * @throws OIDplusException
200
         */
200
         */
201
        public function transaction_begin()/*: void*/ {
201
        public function transaction_begin()/*: void*/ {
202
                if ($this->intransaction) throw new OIDplusException(_L('Nested transactions are not supported by this database plugin.'));
202
                if ($this->intransaction) throw new OIDplusException(_L('Nested transactions are not supported by this database plugin.'));
203
                if (sqlsrv_begin_transaction($this->conn)) $this->intransaction = true;
203
                if (sqlsrv_begin_transaction($this->conn)) $this->intransaction = true;
204
        }
204
        }
205
 
205
 
206
        /**
206
        /**
207
         * @return void
207
         * @return void
208
         */
208
         */
209
        public function transaction_commit()/*: void*/ {
209
        public function transaction_commit()/*: void*/ {
210
                if (sqlsrv_commit($this->conn)) $this->intransaction = false;
210
                if (sqlsrv_commit($this->conn)) $this->intransaction = false;
211
        }
211
        }
212
 
212
 
213
        /**
213
        /**
214
         * @return void
214
         * @return void
215
         */
215
         */
216
        public function transaction_rollback()/*: void*/ {
216
        public function transaction_rollback()/*: void*/ {
217
                if (sqlsrv_rollback($this->conn)) $this->intransaction = false;
217
                if (sqlsrv_rollback($this->conn)) $this->intransaction = false;
218
        }
218
        }
219
 
219
 
220
        /**
220
        /**
221
         * @param bool $mustExist
221
         * @param bool $mustExist
222
         * @return OIDplusSqlSlangPlugin|null
222
         * @return OIDplusSqlSlangPlugin|null
223
         * @throws OIDplusConfigInitializationException
223
         * @throws OIDplusConfigInitializationException
224
         */
224
         */
225
        protected function doGetSlang(bool $mustExist=true)/*: ?OIDplusSqlSlangPlugin*/ {
225
        protected function doGetSlang(bool $mustExist=true)/*: ?OIDplusSqlSlangPlugin*/ {
226
                $slang = OIDplus::getSqlSlangPlugin('mssql');
226
                $slang = OIDplus::getSqlSlangPlugin('mssql');
227
                if (is_null($slang)) {
227
                if (is_null($slang)) {
228
                        throw new OIDplusConfigInitializationException(_L('SQL-Slang plugin "%1" is missing. Please check if it exists in the directory "plugin/sqlSlang". If it is not existing, please recover it from an SVN snapshot or OIDplus TAR.GZ file.','mssql'));
228
                        throw new OIDplusConfigInitializationException(_L('SQL-Slang plugin "%1" is missing. Please check if it exists in the directory "plugin/sqlSlang". If it is not existing, please recover it from an SVN snapshot or OIDplus TAR.GZ file.','mssql'));
229
                }
229
                }
230
                return $slang;
230
                return $slang;
231
        }
231
        }
232
 
232
 
233
        /**
233
        /**
234
         * @return array
234
         * @return array
235
         */
235
         */
236
        public function getExtendedInfo(): array {
236
        public function getExtendedInfo(): array {
237
                $servername = OIDplus::baseConfig()->getValue('SQLSRV_SERVER',   'localhost\oidplus');
237
                $servername = OIDplus::baseConfig()->getValue('SQLSRV_SERVER',   'localhost\oidplus');
238
                $username   = OIDplus::baseConfig()->getValue('SQLSRV_USERNAME', '');
238
                $username   = OIDplus::baseConfig()->getValue('SQLSRV_USERNAME', '');
239
                $password   = OIDplus::baseConfig()->getValue('SQLSRV_PASSWORD', '');
239
                $password   = OIDplus::baseConfig()->getValue('SQLSRV_PASSWORD', '');
240
                $database   = OIDplus::baseConfig()->getValue('SQLSRV_DATABASE', 'oidplus');
240
                $database   = OIDplus::baseConfig()->getValue('SQLSRV_DATABASE', 'oidplus');
241
                $options    = OIDplus::baseConfig()->getValue('SQLSRV_OPTIONS',  array());
241
                $options    = OIDplus::baseConfig()->getValue('SQLSRV_OPTIONS',  array());
242
 
242
 
243
                $ary_info = array(
243
                $ary_info = array(
244
                        _L('Hostname') => $servername,
244
                        _L('Hostname') => $servername,
245
                        _L('Username') => $username,
245
                        _L('Username') => $username,
246
                        _L('Password') => $password != '' ? '('._L('redacted').')' : '',
246
                        _L('Password') => $password != '' ? '('._L('redacted').')' : '',
247
                        _L('Database') => $database
247
                        _L('Database') => $database
248
                );
248
                );
249
                foreach ($options as $name => $val) {
249
                foreach ($options as $name => $val) {
250
                        $ary_info[_L('Option %1',$name)] = '"'.$val.'"';
250
                        $ary_info[_L('Option %1',$name)] = '"'.$val.'"';
251
                }
251
                }
252
                return $ary_info;
252
                return $ary_info;
253
        }
253
        }
254
}
254
}
255
 
255