Subversion Repositories oidplus

Rev

Rev 1050 | Rev 1116 | 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
 
32
        protected abstract function doQuery(string $sql, /*?array*/ $prepared_args=null): OIDplusQueryResult;
33
        public abstract function error(): string;
34
        public abstract function transaction_begin()/*: void*/;
35
        public abstract function transaction_commit()/*: void*/;
36
        public abstract function transaction_rollback()/*: void*/;
37
        public abstract function transaction_supported(): bool;
38
        public abstract function transaction_level(): int;
39
        protected abstract function doConnect()/*: void*/;
40
        protected abstract function doDisconnect()/*: void*/;
41
 
817 daniel-mar 42
        public function getPlugin()/*: ?OIDplusDatabasePlugin*/ {
43
                $res = null;
44
                $plugins = OIDplus::getDatabasePlugins();
45
                foreach ($plugins as $plugin) {
46
                        if (get_class($this) == get_class($plugin::newConnection($this))) {
47
                                return $plugin;
48
                        }
49
                }
50
                return $res;
51
        }
52
 
295 daniel-mar 53
        public function insert_id(): int {
54
                // This is the "fallback" variant. If your database provider (e.g. PDO) supports
55
                // a function to detect the last inserted id, please override this
56
                // function in order to use that specialized function (since it is usually
57
                // more reliable).
316 daniel-mar 58
                return $this->getSlang()->insert_id($this);
295 daniel-mar 59
        }
60
 
990 daniel-mar 61
        public final function getTable(string $sql) {
62
                $out = array();
989 daniel-mar 63
                $res = $this->query($sql);
990 daniel-mar 64
                while ($row = $res->fetch_array()) {
65
                        $out[] = $row;
66
                }
67
                return $out;
68
        }
69
 
70
        public final function getScalar(string $sql) {
71
                $res = $this->query($sql);
989 daniel-mar 72
                $row = $res->fetch_array();
990 daniel-mar 73
                return $row ? reset($row) : null;
989 daniel-mar 74
        }
75
 
295 daniel-mar 76
        public final function query(string $sql, /*?array*/ $prepared_args=null): OIDplusQueryResult {
77
 
78
                $query_logfile = OIDplus::baseConfig()->getValue('QUERY_LOGFILE', '');
79
                if (!empty($query_logfile)) {
80
                        $ts = explode(" ",microtime());
592 daniel-mar 81
                        $ts = date("Y-m-d H:i:s",intval($ts[1])).substr((string)$ts[0],1,4);
295 daniel-mar 82
                        static $log_session_id = "";
83
                        if (empty($log_session_id)) {
84
                                $log_session_id = rand(10000,99999);
85
                        }
86
                        $file = isset($_SERVER['REQUEST_URI']) ? ' | '.$_SERVER['REQUEST_URI'] : '';
87
                        file_put_contents($query_logfile, "$ts <$log_session_id$file> $sql\n", FILE_APPEND);
88
                }
89
 
90
                $this->last_query = $sql;
91
                $sql = str_replace('###', OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', ''), $sql);
502 daniel-mar 92
 
93
                if ($this->slangDetectionDone) {
94
                        $slang = $this->getSlang();
95
                        if ($slang) {
96
                                $sql = $slang->filterQuery($sql);
97
                        }
98
                }
99
 
295 daniel-mar 100
                return $this->doQuery($sql, $prepared_args);
101
        }
102
 
103
        public final function connect()/*: void*/ {
104
                if ($this->connected) return;
105
                $this->beforeConnect();
106
                $this->doConnect();
107
                $this->connected = true;
639 daniel-mar 108
                OIDplus::register_shutdown_function(array($this, 'disconnect'));
318 daniel-mar 109
                $this->afterConnectMandatory();
295 daniel-mar 110
                $this->afterConnect();
111
        }
112
 
113
        public final function disconnect()/*: void*/ {
114
                if (!$this->connected) return;
115
                $this->beforeDisconnect();
116
                $this->doDisconnect();
117
                $this->connected = false;
118
                $this->afterDisconnect();
119
        }
120
 
121
        public function natOrder($fieldname, $order='asc'): string {
325 daniel-mar 122
                $slang = $this->getSlang();
123
                if (!is_null($slang)) {
124
                        return $slang->natOrder($fieldname, $order);
295 daniel-mar 125
                } else {
126
                        $order = strtolower($order);
127
                        if (($order != 'asc') && ($order != 'desc')) {
360 daniel-mar 128
                                throw new OIDplusException(_L('Invalid order "%1" (needs to be "asc" or "desc")',$order));
295 daniel-mar 129
                        }
130
 
131
                        // For (yet) unsupported DBMS, we do not offer natural sort
132
                        return "$fieldname $order";
133
                }
134
        }
135
 
136
        protected function beforeDisconnect()/*: void*/ {}
137
 
138
        protected function afterDisconnect()/*: void*/ {}
139
 
140
        protected function beforeConnect()/*: void*/ {}
141
 
318 daniel-mar 142
        protected function afterConnect()/*: void*/ {}
143
 
144
        private function afterConnectMandatory()/*: void*/ {
295 daniel-mar 145
                // Check if the config table exists. This is important because the database version is stored in it
146
                $this->initRequireTables(array('config'));
147
 
148
                // Do the database tables need an update?
149
                // It is important that we do it immediately after connecting,
150
                // because the database structure might change and therefore various things might fail.
830 daniel-mar 151
                require_once __DIR__.'/../db_updates/run.inc.php';
152
                oidplus_dbupdate($this);
295 daniel-mar 153
 
154
                // Now that our database is up-to-date, we check if database tables are existing
155
                // without config table, because it was checked above
156
                $this->initRequireTables(array('objects', 'asn1id', 'iri', 'ra'/*, 'config'*/));
316 daniel-mar 157
 
158
                // In case an auto-detection of the slang is required (for generic providers like PDO or ODBC),
159
                // we must not be inside a transaction, because the detection requires intentionally submitting
160
                // invalid queries to detect the correct DBMS. If we would be inside a transaction, providers like
161
                // PDO would automatically roll-back. Therefore, we detect the slang right at the beginning,
162
                // before any transaction is used.
163
                $this->getSlang();
295 daniel-mar 164
        }
165
 
166
        private function initRequireTables($tableNames)/*: void*/ {
167
                $msgs = array();
168
                foreach ($tableNames as $tableName) {
169
                        $prefix = OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', '');
170
                        if (!$this->tableExists($prefix.$tableName)) {
360 daniel-mar 171
                                $msgs[] = _L('Table %1 is missing!',$prefix.$tableName);
295 daniel-mar 172
                        }
173
                }
174
                if (count($msgs) > 0) {
175
                        throw new OIDplusConfigInitializationException(implode("\n\n",$msgs));
176
                }
177
        }
178
 
179
        public function tableExists($tableName): bool {
180
                try {
318 daniel-mar 181
                        // Attention: This query could interrupt transactions if Rollback-On-Error is enabled
295 daniel-mar 182
                        $this->query("select 0 from ".$tableName." where 1=0");
183
                        return true;
1050 daniel-mar 184
                } catch (\Exception $e) {
295 daniel-mar 185
                        return false;
186
                }
187
        }
188
 
189
        public function isConnected(): bool {
190
                return $this->connected;
191
        }
192
 
193
        public function init($html = true)/*: void*/ {
194
                $this->html = $html;
195
        }
196
 
197
        public function sqlDate(): string {
325 daniel-mar 198
                $slang = $this->getSlang();
199
                if (!is_null($slang)) {
200
                        return $slang->sqlDate();
295 daniel-mar 201
                } else {
489 daniel-mar 202
                        return "'" . date('Y-m-d H:i:s') . "'";
295 daniel-mar 203
                }
204
        }
205
 
502 daniel-mar 206
        protected function doGetSlang(bool $mustExist=true)/*: ?OIDplusSqlSlangPlugin*/ {
207
                $res = null;
326 daniel-mar 208
 
502 daniel-mar 209
                if (OIDplus::baseConfig()->exists('FORCE_DBMS_SLANG')) {
210
                        $name = OIDplus::baseConfig()->getValue('FORCE_DBMS_SLANG', '');
211
                        $res = OIDplus::getSqlSlangPlugin($name);
212
                        if ($mustExist && is_null($res)) {
213
                                throw new OIDplusConfigInitializationException(_L('Enforced SQL slang (via setting FORCE_DBMS_SLANG) "%1" does not exist.',$name));
214
                        }
215
                } else {
216
                        foreach (OIDplus::getSqlSlangPlugins() as $plugin) {
217
                                if ($plugin->detect($this)) {
218
                                        if (OIDplus::baseConfig()->getValue('DEBUG') && !is_null($res)) {
219
                                                throw new OIDplusException(_L('DB-Slang detection failed: Multiple slangs were detected. Use base config setting FORCE_DBMS_SLANG to define one.'));
220
                                        }
221
 
222
                                        $res = $plugin;
223
 
224
                                        if (!OIDplus::baseConfig()->getValue('DEBUG')) {
295 daniel-mar 225
                                                break;
226
                                        }
227
                                }
228
                        }
502 daniel-mar 229
                        if ($mustExist && is_null($res)) {
230
                                throw new OIDplusException(_L('Cannot determine the SQL slang of your DBMS. Your DBMS is probably not supported.'));
231
                        }
295 daniel-mar 232
                }
233
 
502 daniel-mar 234
                return $res;
235
        }
236
 
237
        public final function getSlang(bool $mustExist=true)/*: ?OIDplusSqlSlangPlugin*/ {
238
                static /*?OIDplusSqlSlangPlugin*/ $slangCache = null;
239
 
240
                if ($this->slangDetectionDone) {
241
                        return $slangCache;
242
                }
243
 
244
                $slangCache = $this->doGetSlang();
245
                $this->slangDetectionDone = true;
326 daniel-mar 246
                return $slangCache;
295 daniel-mar 247
        }
489 daniel-mar 248
}