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 | } |