Subversion Repositories oidplus

Rev

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

Rev Author Line No. Line
295 daniel-mar 1
<?php
2
 
3
/*
4
 * OIDplus 2.0
1086 daniel-mar 5
 * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
295 daniel-mar 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
 
1050 daniel-mar 20
namespace ViaThinkSoft\OIDplus;
511 daniel-mar 21
 
1086 daniel-mar 22
// phpcs:disable PSR1.Files.SideEffects
23
\defined('INSIDE_OIDPLUS') or die;
24
// phpcs:enable PSR1.Files.SideEffects
25
 
730 daniel-mar 26
abstract class OIDplusDatabaseConnection extends OIDplusBaseClass {
295 daniel-mar 27
        protected /*bool*/ $connected = false;
28
        protected /*?bool*/ $html = null;
29
        protected /*?string*/ $last_query = null;
502 daniel-mar 30
        protected /*bool*/ $slangDetectionDone = false;
295 daniel-mar 31
 
1116 daniel-mar 32
        /**
33
         * @param string $sql
34
         * @param array|null $prepared_args
35
         * @return OIDplusQueryResult
36
         * @throws OIDplusException
37
         */
38
        protected abstract function doQuery(string $sql, array $prepared_args=null): OIDplusQueryResult;
39
 
40
        /**
41
         * @return string
42
         */
295 daniel-mar 43
        public abstract function error(): string;
1116 daniel-mar 44
 
45
        /**
46
         * @return void
47
         */
295 daniel-mar 48
        public abstract function transaction_begin()/*: void*/;
1116 daniel-mar 49
 
50
        /**
51
         * @return void
52
         */
295 daniel-mar 53
        public abstract function transaction_commit()/*: void*/;
1116 daniel-mar 54
 
55
        /**
56
         * @return void
57
         */
295 daniel-mar 58
        public abstract function transaction_rollback()/*: void*/;
1116 daniel-mar 59
 
60
        /**
61
         * @return bool
62
         */
295 daniel-mar 63
        public abstract function transaction_supported(): bool;
1116 daniel-mar 64
 
65
        /**
66
         * @return int
67
         */
295 daniel-mar 68
        public abstract function transaction_level(): int;
1116 daniel-mar 69
 
70
        /**
71
         * @return void
72
         */
295 daniel-mar 73
        protected abstract function doConnect()/*: void*/;
1116 daniel-mar 74
 
75
        /**
76
         * @return void
77
         */
295 daniel-mar 78
        protected abstract function doDisconnect()/*: void*/;
79
 
1116 daniel-mar 80
        /**
81
         * @return OIDplusDatabasePlugin|null
82
         */
817 daniel-mar 83
        public function getPlugin()/*: ?OIDplusDatabasePlugin*/ {
84
                $res = null;
85
                $plugins = OIDplus::getDatabasePlugins();
86
                foreach ($plugins as $plugin) {
1116 daniel-mar 87
                        if (get_class($this) == get_class($plugin::newConnection())) {
817 daniel-mar 88
                                return $plugin;
89
                        }
90
                }
91
                return $res;
92
        }
93
 
1116 daniel-mar 94
        /**
95
         * @return int
96
         * @throws OIDplusException
97
         */
295 daniel-mar 98
        public function insert_id(): int {
99
                // This is the "fallback" variant. If your database provider (e.g. PDO) supports
100
                // a function to detect the last inserted id, please override this
101
                // function in order to use that specialized function (since it is usually
102
                // more reliable).
316 daniel-mar 103
                return $this->getSlang()->insert_id($this);
295 daniel-mar 104
        }
105
 
1116 daniel-mar 106
        /**
107
         * @param string $sql
108
         * @return array[]
109
         * @throws OIDplusException
110
         */
111
        public final function getTable(string $sql): array {
990 daniel-mar 112
                $out = array();
989 daniel-mar 113
                $res = $this->query($sql);
990 daniel-mar 114
                while ($row = $res->fetch_array()) {
115
                        $out[] = $row;
116
                }
117
                return $out;
118
        }
119
 
1116 daniel-mar 120
        /**
121
         * @param string $sql
122
         * @return mixed|null
123
         * @throws OIDplusException
124
         */
990 daniel-mar 125
        public final function getScalar(string $sql) {
126
                $res = $this->query($sql);
989 daniel-mar 127
                $row = $res->fetch_array();
990 daniel-mar 128
                return $row ? reset($row) : null;
989 daniel-mar 129
        }
130
 
1116 daniel-mar 131
        /**
132
         * @param string $sql
133
         * @param array|null $prepared_args
134
         * @return OIDplusQueryResult
135
         * @throws OIDplusException
136
         */
295 daniel-mar 137
        public final function query(string $sql, /*?array*/ $prepared_args=null): OIDplusQueryResult {
138
 
139
                $query_logfile = OIDplus::baseConfig()->getValue('QUERY_LOGFILE', '');
140
                if (!empty($query_logfile)) {
141
                        $ts = explode(" ",microtime());
592 daniel-mar 142
                        $ts = date("Y-m-d H:i:s",intval($ts[1])).substr((string)$ts[0],1,4);
295 daniel-mar 143
                        static $log_session_id = "";
144
                        if (empty($log_session_id)) {
145
                                $log_session_id = rand(10000,99999);
146
                        }
147
                        $file = isset($_SERVER['REQUEST_URI']) ? ' | '.$_SERVER['REQUEST_URI'] : '';
148
                        file_put_contents($query_logfile, "$ts <$log_session_id$file> $sql\n", FILE_APPEND);
149
                }
150
 
151
                $this->last_query = $sql;
152
                $sql = str_replace('###', OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', ''), $sql);
502 daniel-mar 153
 
154
                if ($this->slangDetectionDone) {
155
                        $slang = $this->getSlang();
156
                        if ($slang) {
157
                                $sql = $slang->filterQuery($sql);
158
                        }
159
                }
160
 
295 daniel-mar 161
                return $this->doQuery($sql, $prepared_args);
162
        }
163
 
1116 daniel-mar 164
        /**
165
         * @return void
166
         * @throws OIDplusException
167
         */
295 daniel-mar 168
        public final function connect()/*: void*/ {
169
                if ($this->connected) return;
170
                $this->beforeConnect();
171
                $this->doConnect();
172
                $this->connected = true;
639 daniel-mar 173
                OIDplus::register_shutdown_function(array($this, 'disconnect'));
318 daniel-mar 174
                $this->afterConnectMandatory();
295 daniel-mar 175
                $this->afterConnect();
176
        }
177
 
1116 daniel-mar 178
        /**
179
         * @return void
180
         */
295 daniel-mar 181
        public final function disconnect()/*: void*/ {
182
                if (!$this->connected) return;
183
                $this->beforeDisconnect();
184
                $this->doDisconnect();
185
                $this->connected = false;
186
                $this->afterDisconnect();
187
        }
188
 
1116 daniel-mar 189
        /**
190
         * @param string $fieldname
191
         * @param string $order
192
         * @return string
193
         * @throws OIDplusException
194
         */
195
        public function natOrder(string $fieldname, string $order='asc'): string {
325 daniel-mar 196
                $slang = $this->getSlang();
197
                if (!is_null($slang)) {
198
                        return $slang->natOrder($fieldname, $order);
295 daniel-mar 199
                } else {
200
                        $order = strtolower($order);
201
                        if (($order != 'asc') && ($order != 'desc')) {
360 daniel-mar 202
                                throw new OIDplusException(_L('Invalid order "%1" (needs to be "asc" or "desc")',$order));
295 daniel-mar 203
                        }
204
 
205
                        // For (yet) unsupported DBMS, we do not offer natural sort
206
                        return "$fieldname $order";
207
                }
208
        }
209
 
1116 daniel-mar 210
        /**
211
         * @return void
212
         */
295 daniel-mar 213
        protected function beforeDisconnect()/*: void*/ {}
214
 
1116 daniel-mar 215
        /**
216
         * @return void
217
         */
295 daniel-mar 218
        protected function afterDisconnect()/*: void*/ {}
219
 
1116 daniel-mar 220
        /**
221
         * @return void
222
         */
295 daniel-mar 223
        protected function beforeConnect()/*: void*/ {}
224
 
1116 daniel-mar 225
        /**
226
         * @return void
227
         */
318 daniel-mar 228
        protected function afterConnect()/*: void*/ {}
229
 
1116 daniel-mar 230
        /**
231
         * @return void
232
         * @throws OIDplusConfigInitializationException
233
         * @throws OIDplusException
234
         */
318 daniel-mar 235
        private function afterConnectMandatory()/*: void*/ {
295 daniel-mar 236
                // Check if the config table exists. This is important because the database version is stored in it
237
                $this->initRequireTables(array('config'));
238
 
239
                // Do the database tables need an update?
240
                // It is important that we do it immediately after connecting,
241
                // because the database structure might change and therefore various things might fail.
830 daniel-mar 242
                require_once __DIR__.'/../db_updates/run.inc.php';
243
                oidplus_dbupdate($this);
295 daniel-mar 244
 
245
                // Now that our database is up-to-date, we check if database tables are existing
246
                // without config table, because it was checked above
247
                $this->initRequireTables(array('objects', 'asn1id', 'iri', 'ra'/*, 'config'*/));
316 daniel-mar 248
 
249
                // In case an auto-detection of the slang is required (for generic providers like PDO or ODBC),
250
                // we must not be inside a transaction, because the detection requires intentionally submitting
251
                // invalid queries to detect the correct DBMS. If we would be inside a transaction, providers like
252
                // PDO would automatically roll-back. Therefore, we detect the slang right at the beginning,
253
                // before any transaction is used.
254
                $this->getSlang();
295 daniel-mar 255
        }
256
 
1116 daniel-mar 257
        /**
258
         * @param string[] $tableNames
259
         * @return void
260
         * @throws OIDplusConfigInitializationException
261
         * @throws OIDplusException
262
         */
263
        private function initRequireTables(array $tableNames)/*: void*/ {
295 daniel-mar 264
                $msgs = array();
265
                foreach ($tableNames as $tableName) {
266
                        $prefix = OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', '');
267
                        if (!$this->tableExists($prefix.$tableName)) {
360 daniel-mar 268
                                $msgs[] = _L('Table %1 is missing!',$prefix.$tableName);
295 daniel-mar 269
                        }
270
                }
271
                if (count($msgs) > 0) {
272
                        throw new OIDplusConfigInitializationException(implode("\n\n",$msgs));
273
                }
274
        }
275
 
1116 daniel-mar 276
        /**
277
         * @param string $tableName
278
         * @return bool
279
         */
280
        public function tableExists(string $tableName): bool {
295 daniel-mar 281
                try {
318 daniel-mar 282
                        // Attention: This query could interrupt transactions if Rollback-On-Error is enabled
295 daniel-mar 283
                        $this->query("select 0 from ".$tableName." where 1=0");
284
                        return true;
1050 daniel-mar 285
                } catch (\Exception $e) {
295 daniel-mar 286
                        return false;
287
                }
288
        }
289
 
1116 daniel-mar 290
        /**
291
         * @return bool
292
         */
295 daniel-mar 293
        public function isConnected(): bool {
294
                return $this->connected;
295
        }
296
 
1116 daniel-mar 297
        /**
298
         * @param bool $html
299
         * @return void
300
         */
301
        public function init(bool $html = true)/*: void*/ {
295 daniel-mar 302
                $this->html = $html;
303
        }
304
 
1116 daniel-mar 305
        /**
306
         * @return string
307
         * @throws OIDplusException
308
         */
295 daniel-mar 309
        public function sqlDate(): string {
325 daniel-mar 310
                $slang = $this->getSlang();
311
                if (!is_null($slang)) {
312
                        return $slang->sqlDate();
295 daniel-mar 313
                } else {
489 daniel-mar 314
                        return "'" . date('Y-m-d H:i:s') . "'";
295 daniel-mar 315
                }
316
        }
317
 
1116 daniel-mar 318
        /**
319
         * @param bool $mustExist
320
         * @return OIDplusSqlSlangPlugin|null
321
         * @throws OIDplusConfigInitializationException
322
         * @throws OIDplusException
323
         */
502 daniel-mar 324
        protected function doGetSlang(bool $mustExist=true)/*: ?OIDplusSqlSlangPlugin*/ {
325
                $res = null;
326 daniel-mar 326
 
502 daniel-mar 327
                if (OIDplus::baseConfig()->exists('FORCE_DBMS_SLANG')) {
328
                        $name = OIDplus::baseConfig()->getValue('FORCE_DBMS_SLANG', '');
329
                        $res = OIDplus::getSqlSlangPlugin($name);
330
                        if ($mustExist && is_null($res)) {
331
                                throw new OIDplusConfigInitializationException(_L('Enforced SQL slang (via setting FORCE_DBMS_SLANG) "%1" does not exist.',$name));
332
                        }
333
                } else {
334
                        foreach (OIDplus::getSqlSlangPlugins() as $plugin) {
335
                                if ($plugin->detect($this)) {
336
                                        if (OIDplus::baseConfig()->getValue('DEBUG') && !is_null($res)) {
337
                                                throw new OIDplusException(_L('DB-Slang detection failed: Multiple slangs were detected. Use base config setting FORCE_DBMS_SLANG to define one.'));
338
                                        }
339
 
340
                                        $res = $plugin;
341
 
342
                                        if (!OIDplus::baseConfig()->getValue('DEBUG')) {
295 daniel-mar 343
                                                break;
344
                                        }
345
                                }
346
                        }
502 daniel-mar 347
                        if ($mustExist && is_null($res)) {
348
                                throw new OIDplusException(_L('Cannot determine the SQL slang of your DBMS. Your DBMS is probably not supported.'));
349
                        }
295 daniel-mar 350
                }
351
 
502 daniel-mar 352
                return $res;
353
        }
354
 
1116 daniel-mar 355
        /**
356
         * @param bool $mustExist
357
         * @return OIDplusSqlSlangPlugin|null
358
         * @throws OIDplusConfigInitializationException
359
         * @throws OIDplusException
360
         */
502 daniel-mar 361
        public final function getSlang(bool $mustExist=true)/*: ?OIDplusSqlSlangPlugin*/ {
362
                static /*?OIDplusSqlSlangPlugin*/ $slangCache = null;
363
 
364
                if ($this->slangDetectionDone) {
365
                        return $slangCache;
366
                }
367
 
368
                $slangCache = $this->doGetSlang();
369
                $this->slangDetectionDone = true;
326 daniel-mar 370
                return $slangCache;
295 daniel-mar 371
        }
489 daniel-mar 372
}