Subversion Repositories oidplus

Rev

Rev 1220 | Rev 1222 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1219 daniel-mar 1
<?php
2
 
3
/*
4
 * OIDplus 2.0
5
 * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
6
 *
7
 * Licensed under the Apache License, Version 2.0 (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
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
 
20
namespace ViaThinkSoft\OIDplus;
21
 
22
// phpcs:disable PSR1.Files.SideEffects
23
\defined('INSIDE_OIDPLUS') or die;
24
// phpcs:enable PSR1.Files.SideEffects
25
 
26
class OIDplusDatabaseConnectionADO extends OIDplusDatabaseConnection {
27
        /**
28
         * @var mixed|null
29
         */
30
        private $conn = null;
31
 
32
        /**
33
         * @var string|null
34
         */
35
        private $last_error = null; // do the same like MySQL+PDO, just to be equal in the behavior
36
 
37
        /**
38
         * @param string $sql
39
         * @param array|null $prepared_args
40
         * @return OIDplusQueryResultADO
41
         * @throws OIDplusConfigInitializationException
42
         * @throws OIDplusException
43
         * @throws OIDplusSQLException
44
         */
45
        protected function doQueryPrepareEmulation(string $sql, array $prepared_args=null): OIDplusQueryResultADO {
46
                $sql = str_replace('?', chr(1), $sql);
47
                foreach ($prepared_args as $arg) {
48
                        $needle = chr(1);
49
                        if (is_bool($arg)) {
50
                                if ($this->slangDetectionDone) {
51
                                        $replace = $this->getSlang()->getSQLBool($arg);
52
                                } else {
53
                                        $replace = $arg ? '1' : '0';
54
                                }
55
                        } else if (is_int($arg)) {
56
                                $replace = $arg;
57
                        } else if (is_float($arg)) {
58
                                $replace = number_format($arg, 10, '.', '');
59
                        } else {
60
                                // TODO: More types?
61
                                if ($this->slangDetectionDone) {
62
                                        $replace = "'".$this->getSlang()->escapeString($arg ?? '')."'";
63
                                } else {
64
                                        $replace = "'".str_replace("'", "''", $arg)."'";
65
                                }
66
                        }
67
                        $pos = strpos($sql, $needle);
68
                        if ($pos !== false) {
69
                                $sql = substr_replace($sql, $replace, $pos, strlen($needle));
70
                        }
71
                }
72
                $sql = str_replace(chr(1), '?', $sql);
73
                return $this->doQuery($sql, null);
74
        }
75
 
76
        /**
77
         * @param string $sql
78
         * @param array|null $prepared_args
79
         * @return OIDplusQueryResultADO
80
         * @throws OIDplusException
81
         */
82
        public function doQuery(string $sql, array $prepared_args=null): OIDplusQueryResult {
83
                $this->last_error = null;
84
                if (is_null($prepared_args)) {
85
                        $res = new \COM("ADODB.Recordset");
86
 
87
                        try {
88
                                /** @phpstan-ignore-next-line */
89
                                $res->Open($sql, $this->conn, 3, 3);  // adOpenStatic, adLockOptimistic
90
                        } catch (\Exception $e) {
91
                                $this->last_error = $e->getMessage();
92
                                throw new OIDplusSQLException($sql, $this->error());
93
                        }
94
 
95
                        /** @phpstan-ignore-next-line */
96
                        if ($res->State == 0) {
97
                                // It was an INSERT or UPDATE command (i.e. dataset is closed now)
98
                                return new OIDplusQueryResultADO(true);
99
                        } else {
100
                                return new OIDplusQueryResultADO($res);
101
                        }
102
                } else {
103
                        return $this->doQueryPrepareEmulation($sql, $prepared_args);
104
                }
105
        }
106
 
107
        /**
108
         * @return string
109
         */
110
        public function error(): string {
111
                $err = $this->last_error;
112
                if ($err == null) $err = '';
113
 
114
                $err = html_to_text($err); // The original ADO Exception is HTML
115
 
116
                return vts_utf8_encode($err); // UTF-8 encode, because ADO might output weird stuff ...
117
        }
118
 
119
        /**
120
         * @return void
121
         * @throws OIDplusConfigInitializationException
122
         * @throws OIDplusException
123
         */
124
        protected function doConnect()/*: void*/ {
125
                if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
126
                        throw new OIDplusConfigInitializationException(_L('Functionality only available on Windows systems'));
127
                }
128
 
129
                if (!class_exists('COM')) {
1221 daniel-mar 130
                        throw new OIDplusConfigInitializationException(_L('To use %1, please enable the lines "extension=%2" and "extension_dir=ext" in the configuration file %3.',get_class(),'com_dotnet',php_ini_loaded_file() ? php_ini_loaded_file() : 'PHP.ini'));
1219 daniel-mar 131
                }
132
 
133
                // Try connecting to the database
134
 
135
                $conn = new \COM("ADODB.Connection", NULL, 65001/*CP_UTF8*/);
136
 
1220 daniel-mar 137
                $connStr = OIDplus::baseConfig()->getValue('ADO_CONNECTION_STRING', 'Provider=MSOLEDBSQL;Data Source=LOCALHOST\SQLEXPRESS;Initial Catalog=oidplus;Integrated Security=SSPI');
1219 daniel-mar 138
 
139
                try {
140
                        if (stripos($connStr, "charset=") === false) {
141
                                // Try to extend DSN with charset
142
                                // Note: For MySQL, must be utf8 or utf8, and not UTF-8
143
                                try {
144
                                        /** @phpstan-ignore-next-line */
145
                                        $conn->Open("$connStr;charset=utf8mb4");
146
                                        $this->conn = $conn;
147
                                } catch (\Exception $e1) {
148
                                        try {
149
                                                /** @phpstan-ignore-next-line */
150
                                                $conn->Open("$connStr;charset=utf8");
151
                                                $this->conn = $conn;
152
                                        } catch (\Exception $e2) {
153
                                                try {
154
                                                        /** @phpstan-ignore-next-line */
155
                                                        $conn->Open("$connStr;charset=UTF-8");
156
                                                        $this->conn = $conn;
157
                                                } catch (\Exception $e3) {
158
                                                        /** @phpstan-ignore-next-line */
159
                                                        $conn->Open($connStr);
160
                                                        $this->conn = $conn;
161
                                                }
162
                                        }
163
                                }
164
                        } else {
165
                                /** @phpstan-ignore-next-line */
166
                                $conn->Open($connStr);
167
                                $this->conn = $conn;
168
                        }
169
                } catch (\Exception $e) {
170
                        $message = $e->getMessage();
171
                        $message = vts_utf8_encode($message); // Make UTF-8 if it is NOT already UTF-8. Important for German Microsoft Access.
172
                        throw new OIDplusConfigInitializationException(trim(_L('Connection to the database failed!').' '.$message));
173
                }
174
 
175
                $this->last_error = null;
176
 
177
                try {
178
                        /** @phpstan-ignore-next-line */
179
                        $this->conn->Execute( "SET NAMES 'UTF-8'"); // Does most likely NOT work with ADO. Try adding ";CHARSET=UTF8" (or similar) to the DSN
180
                } catch (\Exception $e) {
181
                }
182
 
183
                try {
184
                        /** @phpstan-ignore-next-line */
185
                        $this->conn->Execute("SET CHARACTER SET 'UTF-8'"); // Does most likely NOT work with ADO. Try adding ";CHARSET=UTF8" (or similar) to the DSN
186
                } catch (\Exception $e) {
187
                }
188
 
189
                try {
190
                        /** @phpstan-ignore-next-line */
191
                        $this->conn->Execute("SET NAMES 'utf8mb4'"); // Does most likely NOT work with ADO. Try adding ";CHARSET=UTF8" (or similar) to the DSN
192
                } catch (\Exception $e) {
193
                }
194
        }
195
 
196
        /**
197
         * @return void
198
         */
199
        protected function doDisconnect()/*: void*/ {
200
                if (!is_null($this->conn)) {
201
                        try {
202
                                $this->conn->Close();
203
                        } catch (\Exception $e) {
204
                                // For some reason, in test_database_plugins.php (tested with ODBC-MSSQL), the disconnection method raises the Exception (TODO?)
205
                                //    Source: ADODB.Recordset
206
                                //    Description: Der Vorgang ist fuer ein geschlossenes Objekt nicht zugelassen.
207
                        }
208
                        $this->conn = null;
209
                }
210
        }
211
 
212
        /**
213
         * @return array
214
         */
215
        private function connectionProperties(): array {
216
                $ary = array();
217
                for ($i=0; $i<$this->conn->Properties->Count; $i++) {
218
                        $ary[$this->conn->Properties->Item($i)->Name] = $this->conn->Properties->Item($i)->Value;
219
                }
220
                return $ary;
221
        }
222
 
223
        /**
224
         * @var int
225
         */
226
        private $trans_level = 0;
227
 
228
        /**
229
         * @return bool
230
         */
231
        public function transaction_supported(): bool {
232
                // DBPROPVAL_TC_NONE 0 TAs werden nicht unterstützt
233
                // DBPROPVAL_TC_DML 1 TAs können nur DML ausführen. DDLs verursachen Fehler.
234
                // DBPROPVAL_TC_DDL_COMMIT 2 TAs können nur DML ausführen. DDLs bewirken einen COMMIT.
235
                // DBPROPVAL_TC_DDL_IGNORE 4 TAs können nur DML statements enthalten. DDL statements werden ignoriert.
236
                // DBPROPVAL_TC_ALL 8 TAs werden vollständig unterstützt.
237
                // DBPROPVAL_TC_DDL_LOCK 16 TAs können DML+DDL statements sein. Tabellen oder Indices erhalten bei Modifikation aber eine Lock für die Dauer der TA.
238
                $props = $this->connectionProperties();
239
                return $props['Transaction DDL'] >= 8;
240
        }
241
 
242
        /**
243
         * @return int
244
         */
245
        public function transaction_level(): int {
246
                if (!$this->transaction_supported()) {
247
                        // TODO?
248
                        return 0;
249
                }
250
                return $this->trans_level;
251
        }
252
 
253
        /**
254
         * @return void
255
         * @throws OIDplusException
256
         */
257
        public function transaction_begin()/*: void*/ {
258
                if (!$this->transaction_supported()) {
259
                        // TODO?
260
                        return;
261
                }
262
                if ($this->trans_level > 0) throw new OIDplusException(_L('Nested transactions are not supported by this database plugin.'));
263
                $this->trans_level = $this->conn->BeginTrans();
264
        }
265
 
266
        /**
267
         * @return void
268
         */
269
        public function transaction_commit()/*: void*/ {
270
                if (!$this->transaction_supported()) {
271
                        // TODO?
272
                        return;
273
                }
274
                $this->conn->CommitTrans();
275
                $this->trans_level--;
276
        }
277
 
278
        /**
279
         * @return void
280
         */
281
        public function transaction_rollback()/*: void*/ {
282
                if (!$this->transaction_supported()) {
283
                        // TODO?
284
                        return;
285
                }
286
                $this->conn->RollbackTrans();
287
                $this->trans_level--;
288
        }
1220 daniel-mar 289
 
290
        /**
291
         * @return array
292
         */
293
        public function getExtendedInfo(): array {
1221 daniel-mar 294
                $props = $this->connectionProperties();
295
                if (isset($props['Password'])) $props['Password'] = '['._L('redacted').']';
296
                return $props;
1220 daniel-mar 297
        }
298
 
1219 daniel-mar 299
}