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 : [];
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'
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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