Issues (2756)

includes/Database/YDB.php (1 issue)

1
<?php
2
3
/**
4
 * Aura SQL wrapper for YOURLS that creates the allmighty YDB object.
5
 *
6
 * A fine example of a "class that knows too much" (see https://en.wikipedia.org/wiki/God_object)
7
 *
8
 * Note to plugin authors: you most likely SHOULD NOT use directly methods and properties of this class. Use instead
9
 * function wrappers (eg don't use $ydb->option, or $ydb->set_option(), use yourls_*_options() functions instead).
10
 *
11
 * @since 1.7.3
12
 */
13
14
namespace YOURLS\Database;
15
16
use YOURLS\Admin\Logger;
17
use Aura\Sql\ExtendedPdo;
18
use PDO;
19
20
class YDB extends ExtendedPdo {
21
22
    /**
23
     * Debug mode, default false
24
     * @var bool
25
     */
26
    protected $debug = false;
27
28
    /**
29
     * Page context (ie "infos", "bookmark", "plugins"...)
30
     * @var string
31
     */
32
    protected $context = '';
33
34
    /**
35
     * Information related to a short URL keyword (eg timestamp, long URL, ...)
36
     *
37
     * @var array
38
     *
39
     */
40
    protected $infos = [];
41
42
    /**
43
     * Is YOURLS installed and ready to run?
44
     * @var bool
45
     */
46
    protected $installed = false;
47
48
    /**
49
     * Options
50
     * @var array
51
     */
52
    protected $option = [];
53
54
    /**
55
     * Plugin admin pages informations
56
     * @var array
57
     */
58
    protected $plugin_pages = [];
59
60
    /**
61
     * Plugin informations
62
     * @var array
63
     */
64
    protected $plugins = [];
65
66
    /**
67
     * Are we emulating prepare statements ?
68
     * @var bool
69
     */
70
    protected $is_emulate_prepare;
71
72
    /**
73
     * @since 1.7.3
74
     * @param string $dsn         The data source name
75
     * @param string $user        The username
76
     * @param string $pass        The password
77
     * @param array  $options     Driver-specific options
78
     * @param array  $attributes  Attributes to set after a connection
79
     */
80
    public function __construct($dsn, $user, $pass, $options, $attributes) {
81
        parent::__construct($dsn, $user, $pass, $options, $attributes);
82
    }
83
84
    /**
85
     * Init everything needed
86
     *
87
     * Everything we need to set up is done here in init(), not in the constructor, so even
88
     * when the connection fails (eg config error or DB dead), the constructor has worked
89
     * and we have a $ydb object properly instantiated (and for instance yourls_die() can
90
     * correctly die, even if using $ydb methods)
91
     *
92
     * @since  1.7.3
93
     * @return void
94
     */
95
    public function init() {
96
        $this->connect_to_DB();
97
98
        $this->set_emulate_state();
99
100
        $this->start_profiler();
101
    }
102
103
    /**
104
     * Check if we emulate prepare statements, and set bool flag accordingly
105
     *
106
     * Check if current driver can PDO::getAttribute(PDO::ATTR_EMULATE_PREPARES)
107
     * Some combinations of PHP/MySQL don't support this function. See
108
     * https://travis-ci.org/YOURLS/YOURLS/jobs/271423782#L481
109
     *
110
     * @since  1.7.3
111
     * @return void
112
     */
113
    public function set_emulate_state() {
114
        try {
115
            $this->is_emulate_prepare = $this->getAttribute(PDO::ATTR_EMULATE_PREPARES);
116
        } catch (\PDOException $e) {
117
            $this->is_emulate_prepare = false;
118
        }
119
    }
120
121
    /**
122
     * Get emulate status
123
     *
124
     * @since  1.7.3
125
     * @return bool
126
     */
127 38
    public function get_emulate_state() {
128 38
        return $this->is_emulate_prepare;
129
    }
130
131
    /**
132
     * Initiate real connection to DB server
133
     *
134
     * This is to check that the server is running and/or the config is OK
135
     *
136
     * @since  1.7.3
137
     * @return void
138
     * @throws \PDOException
139
     */
140
    public function connect_to_DB() {
141
        try {
142
            $this->connect();
143
        } catch ( \Exception $e ) {
144
            $this->dead_or_error($e);
145
        }
146
    }
147
148
    /**
149
     * Die with an error message
150
     *
151
     * @since  1.7.3
152
     *
153
     * @param \Exception $exception
154
     *
155
     * @return void
156
     */
157
    public function dead_or_error(\Exception $exception) {
158
        // Use any /user/db_error.php file
159
        if( file_exists( YOURLS_USERDIR . '/db_error.php' ) ) {
160
            include_once( YOURLS_USERDIR . '/db_error.php' );
161
            die();
162
        }
163
164
        $message  = yourls__( 'Incorrect DB config, or could not connect to DB' );
165
        $message .= '<br/>' . get_class($exception) .': ' . $exception->getMessage();
166
167
        yourls_die( yourls__( $message ), yourls__( 'Fatal error' ), 503 );
168
        die();
169
    }
170
171
    /**
172
     * Start a Message Logger
173
     *
174
     * @since  1.7.3
175
     * @see    \YOURLS\Admin\Logger
176
     * @see    \Aura\Sql\Profiler
177
     * @return void
178
     */
179
    public function start_profiler() {
180
        $this->profiler = new Logger($this);
181
    }
182
183
    /**
184
     * @param string $context
185
     */
186 1
    public function set_html_context($context) {
187 1
        $this->context = $context;
188 1
    }
189
190
    /**
191
     * @return string
192
     */
193 1
    public function get_html_context() {
194 1
        return $this->context;
195
    }
196
197
    // Options low level functions, see \YOURLS\Database\Options
198
199
    /**
200
     * @param string $name
201
     * @param mixed  $value
202
     */
203 21
    public function set_option($name, $value) {
204 21
        $this->option[$name] = $value;
205 21
    }
206
207
    /**
208
     * @param  string $name
209
     * @return bool
210
     */
211 24
    public function has_option($name) {
212 24
        return array_key_exists($name, $this->option);
213
    }
214
215
    /**
216
     * @param  string $name
217
     * @return string
218
     */
219 24
    public function get_option($name) {
220 24
        return $this->option[$name];
221
    }
222
223
    /**
224
     * @param string $name
225
     */
226 2
    public function delete_option($name) {
227 2
        unset($this->option[$name]);
228 2
    }
229
230
231
    // Infos (related to keyword) low level functions
232
233
    /**
234
     * @param string $keyword
235
     * @param mixed  $infos
236
     */
237 8
    public function set_infos($keyword, $infos) {
238 8
        $this->infos[$keyword] = $infos;
239 8
    }
240
241
    /**
242
     * @param  string $keyword
243
     * @return bool
244
     */
245 9
    public function has_infos($keyword) {
246 9
        return array_key_exists($keyword, $this->infos);
247
    }
248
249
    /**
250
     * @param  string $keyword
251
     * @return array
252
     */
253 5
    public function get_infos($keyword) {
254 5
        return $this->infos[$keyword];
255
    }
256
257
    /**
258
     * @param string $keyword
259
     */
260
    public function delete_infos($keyword) {
261
        unset($this->infos[$keyword]);
262
    }
263
264
    /**
265
     * @todo: infos & options are working the same way here. Abstract this.
266
     */
267
268
269
    // Plugin low level functions, see functions-plugins.php
270
271
    /**
272
     * @return array
273
     */
274 16
    public function get_plugins() {
275 16
        return $this->plugins;
276
    }
277
278
    /**
279
     * @param array $plugins
280
     */
281 2
    public function set_plugins(array $plugins) {
282 2
        $this->plugins = $plugins;
283 2
    }
284
285
    /**
286
     * @param string $plugin  plugin filename
287
     */
288 2
    public function add_plugin($plugin) {
289 2
        $this->plugins[] = $plugin;
290 2
    }
291
292
    /**
293
     * @param string $plugin  plugin filename
294
     */
295
    public function remove_plugin($plugin) {
296
        unset($this->plugins[$plugin]);
297
    }
298
299
300
    // Plugin Pages low level functions, see functions-plugins.php
301
302
    /**
303
     * @return array
304
     */
305 5
    public function get_plugin_pages() {
306 5
        return is_array( $this->plugin_pages ) ? $this->plugin_pages : [];
0 ignored issues
show
The condition is_array($this->plugin_pages) is always true.
Loading history...
307
    }
308
309
    /**
310
     * @param array $pages
311
     */
312 9
    public function set_plugin_pages(array $pages) {
313 9
        $this->plugin_pages = $pages;
314 9
    }
315
316
    /**
317
     * @param string   $slug
318
     * @param string   $title
319
     * @param callable $function
320
     */
321 4
    public function add_plugin_page( $slug, $title, $function ) {
322 4
        $this->plugin_pages[ $slug ] = [
323 4
            'slug'     => $slug,
324 4
            'title'    => $title,
325 4
            'function' => $function,
326
        ];
327 4
    }
328
329
    /**
330
     * @param string $slug
331
     */
332
    public function remove_plugin_page( $slug ) {
333
        unset( $this->plugin_pages[ $slug ] );
334
    }
335
336
337
    /**
338
     * Return count of SQL queries performed
339
     *
340
     * @since  1.7.3
341
     * @return int
342
     */
343 2
    public function get_num_queries() {
344 2
        return count( (array) $this->get_queries() );
345
    }
346
347
    /**
348
     * Return SQL queries performed
349
     *
350
     * Aura\Sql\Profiler logs every PDO command issued. But depending on PDO::ATTR_EMULATE_PREPARES, some are
351
     * actually sent to the mysql server or not :
352
     *  - if PDO::ATTR_EMULATE_PREPARES is true, prepare() statements are not sent to the server and are performed
353
     *    internally, so they are removed from the logger
354
     *  - if PDO::ATTR_EMULATE_PREPARES is false, prepare() statements are actually performed by the mysql server,
355
     *    and count as an actual query
356
     *
357
     * Resulting array is something like:
358
     *   array (
359
     *      0 => array (
360
     *           'duration' => 1.0010569095611572265625,
361
     *           'function' => 'connect',
362
     *           'statement' => NULL,
363
     *           'bind_values' => array (),
364
     *           'trace' => ...back trace...,
365
     *       ),
366
     *       // key index might not be sequential if 'prepare' function are filtered out
367
     *       2 => array (
368
     *           'duration' => 0.000999927520751953125,
369
     *           'function' => 'perform',
370
     *           'statement' => 'SELECT option_value FROM yourls_options WHERE option_name = :option_name LIMIT 1',
371
     *           'bind_values' => array ( 'option_name' => 'test_option' ),
372
     *           'trace' => ...back trace...,
373
     *       ),
374
     *   );
375
     *
376
     * @since  1.7.3
377
     * @return array
378
     */
379 2
    public function get_queries() {
380 2
        $queries = $this->getProfiler()->getProfiles();
381
382 2
        if ($this->get_emulate_state()) {
383
            // keep queries if $query['function'] != 'prepare'
384
            $queries = array_filter($queries, function($query) {return $query['function'] !== 'prepare';});
385
        }
386
387 2
        return $queries;
388
    }
389
390
    /**
391
     * Set YOURLS installed state
392
     *
393
     * @since  1.7.3
394
     * @param  bool $bool
395
     * @return void
396
     */
397
    public function set_installed($bool) {
398
        $this->installed = $bool;
399
    }
400
401
    /**
402
     * Get YOURLS installed state
403
     *
404
     * @since  1.7.3
405
     * @return bool
406
     */
407 2
    public function is_installed() {
408 2
        return $this->installed;
409
    }
410
411
    /**
412
     * Return standardized DB version
413
     *
414
     * The regex removes everything that's not a number at the start of the string, or remove anything that's not a number and what
415
     * follows after that.
416
     *   'omgmysql-5.5-ubuntu-4.20' => '5.5'
417
     *   'mysql5.5-ubuntu-4.20'     => '5.5'
418
     *   '5.5-ubuntu-4.20'          => '5.5'
419
     *   '5.5-beta2'                => '5.5'
420
     *   '5.5'                      => '5.5'
421
     *
422
     * @since  1.7.3
423
     * @return string
424
     */
425 10
    public function mysql_version() {
426 10
        $version = $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
427 10
        return $version;
428
    }
429
430
    /**
431
     * Deprecated properties since 1.7.3, unused in 3rd party plugins as far as I know
432
     *
433
     * $ydb->DB_driver
434
     * $ydb->captured_errors
435
     * $ydb->dbh
436
     * $ydb->result
437
     * $ydb->rows_affected
438
     * $ydb->show_errors
439
     */
440
441
    /**
442
     * Deprecated functions since 1.7.3
443
     */
444
445
    // @codeCoverageIgnoreStart
446
447
    public function escape($string) {
448
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
449
        // This will escape using PDO->quote(), but then remove the enclosing quotes
450
        return substr($this->quote($string), 1, -1);
451
    }
452
453
    public function get_col($query) {
454
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
455
        yourls_debug_log('LEGACY SQL: '.$query);
456
        return $this->fetchCol($query);
457
    }
458
459
    public function get_results($query) {
460
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
461
        yourls_debug_log('LEGACY SQL: '.$query);
462
        $stm = parent::query($query);
463
        return($stm->fetchAll(PDO::FETCH_OBJ));
464
    }
465
466
    public function get_row($query) {
467
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
468
        yourls_debug_log('LEGACY SQL: '.$query);
469
        $row = $this->fetchObjects($query);
470
        return isset($row[0]) ? $row[0] : null;
471
    }
472
473
    public function get_var($query) {
474
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
475
        yourls_debug_log('LEGACY SQL: '.$query);
476
        return $this->fetchValue($query);
477
    }
478
479
    public function query($query) {
480
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
481
        yourls_debug_log('LEGACY SQL: '.$query);
482
        return $this->fetchAffected($query);
483
    }
484
    // @codeCoverageIgnoreEnd
485
}
486