Completed
Pull Request — master (#2282)
by ྅༻ Ǭɀħ
01:40
created

YDB::get_plugins()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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 = array();
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 = array();
53
54
    /**
55
     * Plugin admin pages informations
56
     * @var array
57
     */
58
    protected $plugin_pages = array();
59
60
    /**
61
     * Plugin informations
62
     * @var array
63
     */
64
    protected $plugins = array();
65
66
    /**
67
     * Are we emulating prepare statements ?
68
     * @var bool
69
     */
70
    protected $is_emulate_prepare;
71
72
    /**
73
     * Class constructor
74
     *
75
     * Don't forget to end with a call to the parent constructor
76
     *
77
     * @since 1.7.3
78
     * @param string $dsn         The data source name
79
     * @param string $user        The username
80
     * @param string $pass        The password
81
     * @param array  $options     Driver-specific options
82
     * @param array  $attributes  Attributes to set after a connection
83
     */
84
    public function __construct($dsn, $user, $pass, $options, $attributes) {
85
        parent::__construct($dsn, $user, $pass, $options, $attributes);
86
87
        $this->connect_to_DB();
88
89
        $this->set_emulate_state();
90
91
        // Log query infos
92
        $this->start_profiler();
93
    }
94
95
    /**
96
     * Check if we emulate prepare statements, and set bool flag accordingly
97
     *
98
     * Check if current driver can PDO::getAttribute(PDO::ATTR_EMULATE_PREPARES)
99
     * Some combinations of PHP/MySQL don't support this function. See
100
     * https://travis-ci.org/YOURLS/YOURLS/jobs/271423782#L481
101
     *
102
     * @since  1.7.3
103
     * @return void
104
     */
105
    public function set_emulate_state() {
106
        try {
107
            $this->is_emulate_prepare = $this->getAttribute(PDO::ATTR_EMULATE_PREPARES);
108
        } catch (\PDOException $e) {
109
            $this->is_emulate_prepare = false;
110
        }
111
    }
112
113
    /**
114
     * Get emulate status
115
     *
116
     * @since  1.7.3
117
     * @return bool
118
     */
119
    public function get_emulate_state() {
120
        return $this->is_emulate_prepare;
121
    }
122
123
    /**
124
     * Initiate real connection to DB server
125
     *
126
     * This is to check that the server is running and/or the config is OK
127
     *
128
     * @since  1.7.3
129
     * @return void
130
     */
131
    public function connect_to_DB() {
132
        try {
133
            $this->connect();
134
        } catch ( \Exception $e ) {
135
            // Use any /user/db_error.php file
136
            if( file_exists( YOURLS_USERDIR . '/db_error.php' ) ) {
137
                include_once( YOURLS_USERDIR . '/db_error.php' );
138
                die();
139
            }
140
141
            $message  = yourls__( 'Incorrect DB config, or could not connect to DB' );
142
            $message .= '<br/>' . get_class($e) .': ' . $e->getMessage();
143
144
            yourls_die( yourls__( $message ), yourls__( 'Fatal error' ), 503 );
145
            die();
146
        }
147
    }
148
149
    /**
150
     * Start a Message Logger
151
     *
152
     * @since  1.7.3
153
     * @see    \YOURLS\Admin\Logger
154
     * @see    \Aura\Sql\Profiler
155
     * @return void
156
     */
157
    public function start_profiler() {
158
        $this->profiler = new Logger($this);
159
    }
160
161
    /**
162
     * @param string $context
163
     */
164
    public function set_html_context($context) {
165
        $this->context = $context;
166
    }
167
168
    /**
169
     * @return string
170
     */
171
    public function get_html_context() {
172
        return $this->context;
173
    }
174
175
    // Options low level functions, see \YOURLS\Database\Options
176
177
    /**
178
     * @param string $name
179
     * @param mixed  $value
180
     */
181
    public function set_option($name, $value) {
182
        $this->option[$name] = $value;
183
    }
184
185
    /**
186
     * @param  string $name
187
     * @return bool
188
     */
189
    public function has_option($name) {
190
        return array_key_exists($name, $this->option);
191
    }
192
193
    /**
194
     * @param  string $name
195
     * @return string
196
     */
197
    public function get_option($name) {
198
        return $this->option[$name];
199
    }
200
201
    /**
202
     * @param string $name
203
     */
204
    public function delete_option($name) {
205
        unset($this->option[$name]);
206
    }
207
208
209
    // Infos (related to keyword) low level functions
210
211
    /**
212
     * @param string $keyword
213
     * @param mixed  $infos
214
     */
215
    public function set_infos($keyword, $infos) {
216
        $this->infos[$keyword] = $infos;
217
    }
218
219
    /**
220
     * @param  string $keyword
221
     * @return bool
222
     */
223
    public function has_infos($keyword) {
224
        return array_key_exists($keyword, $this->infos);
225
    }
226
227
    /**
228
     * @param  string $keyword
229
     * @return array
230
     */
231
    public function get_infos($keyword) {
232
        return $this->infos[$keyword];
233
    }
234
235
    /**
236
     * @param string $keyword
237
     */
238
    public function delete_infos($keyword) {
239
        unset($this->infos[$keyword]);
240
    }
241
242
    /**
243
     * @todo: infos & options are working the same way here. Abstract this.
244
     */
245
246
247
    // Plugin low level functions, see functions-plugins.php
248
249
    /**
250
     * @return array
251
     */
252
    public function get_plugins() {
253
        return $this->plugins;
254
    }
255
256
    /**
257
     * @param array $plugins
258
     */
259
    public function set_plugins(array $plugins) {
260
        $this->plugins = $plugins;
261
    }
262
263
    /**
264
     * @param string $plugin  plugin filename
265
     */
266
    public function add_plugin($plugin) {
267
        $this->plugins[] = $plugin;
268
    }
269
270
    /**
271
     * @param string $plugin  plugin filename
272
     */
273
    public function remove_plugin($plugin) {
274
        unset($this->plugins[$plugin]);
275
    }
276
277
278
    // Plugin Pages low level functions, see functions-plugins.php
279
280
    /**
281
     * @return array
282
     */
283
    public function get_plugin_pages() {
284
        return $this->plugin_pages;
285
    }
286
287
    /**
288
     * @param array $pages
289
     */
290
    public function set_plugin_pages(array $pages) {
291
        $this->plugin_pages = $pages;
292
    }
293
294
    /**
295
     * @param string   $slug
296
     * @param string   $title
297
     * @param callable $function
298
     */
299
    public function add_plugin_page($slug, $title, $function) {
300
        $this->plugin_pages[$slug] = array(
301
            'slug'     => $slug,
302
            'title'    => $title,
303
            'function' => $function,
304
        );
305
    }
306
307
    /**
308
     * @param string $slug
309
     */
310
    public function remove_plugin_page($slug) {
311
        unset($this->plugin_pages[$slug]);
312
    }
313
314
315
    /**
316
     * Return count of SQL queries performed
317
     *
318
     * @since  1.7.3
319
     * @return int
320
     */
321
    public function get_num_queries() {
322
        return count( (array) $this->get_queries() );
323
    }
324
325
    /**
326
     * Return SQL queries performed
327
     *
328
     * Aura\Sql\Profiler logs every PDO command issued. But depending on PDO::ATTR_EMULATE_PREPARES, some are
329
     * actually sent to the mysql server or not :
330
     *  - if PDO::ATTR_EMULATE_PREPARES is true, prepare() statements are not sent to the server and are performed
331
     *    internally, so they are removed from the logger
332
     *  - if PDO::ATTR_EMULATE_PREPARES is false, prepare() statements are actually performed by the mysql server,
333
     *    and count as an actual query
334
     *
335
     * Resulting array is something like:
336
     *   array (
337
     *      0 => array (
338
     *           'duration' => 1.0010569095611572265625,
339
     *           'function' => 'connect',
340
     *           'statement' => NULL,
341
     *           'bind_values' => array (),
342
     *           'trace' => ...back trace...,
343
     *       ),
344
     *       // key index might not be sequential if 'prepare' function are filtered out
345
     *       2 => array (
346
     *           'duration' => 0.000999927520751953125,
347
     *           'function' => 'perform',
348
     *           'statement' => 'SELECT option_value FROM yourls_options WHERE option_name = :option_name LIMIT 1',
349
     *           'bind_values' => array ( 'option_name' => 'test_option' ),
350
     *           'trace' => ...back trace...,
351
     *       ),
352
     *   );
353
     *
354
     * @since  1.7.3
355
     * @return array
356
     */
357
    public function get_queries() {
358
        $queries = $this->getProfiler()->getProfiles();
359
360
        if ($this->get_emulate_state()) {
361
            // keep queries if $query['function'] != 'prepare'
362
            $queries = array_filter($queries, function($query) {return $query['function'] !== 'prepare';});
363
        }
364
365
        return $queries;
366
    }
367
368
    /**
369
     * Set YOURLS installed state
370
     *
371
     * @since  1.7.3
372
     * @param  bool $bool
373
     * @return void
374
     */
375
    public function set_installed($bool) {
376
        $this->installed = $bool;
377
    }
378
379
    /**
380
     * Get YOURLS installed state
381
     *
382
     * @since  1.7.3
383
     * @return bool
384
     */
385
    public function is_installed() {
386
        return $this->installed;
387
    }
388
389
    /**
390
     * Return standardized DB version
391
     *
392
     * 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
393
     * follows after that.
394
     *   'omgmysql-5.5-ubuntu-4.20' => '5.5'
395
     *   'mysql5.5-ubuntu-4.20'     => '5.5'
396
     *   '5.5-ubuntu-4.20'          => '5.5'
397
     *   '5.5-beta2'                => '5.5'
398
     *   '5.5'                      => '5.5'
399
     *
400
     * @since  1.7.3
401
     * @return string
402
     */
403
    public function mysql_version() {
404
        $version = $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
405
        return $version;
406
    }
407
408
    /**
409
     * Deprecated properties since 1.7.3, unused in 3rd party plugins as far as I know
410
     *
411
     * $ydb->DB_driver
412
     * $ydb->captured_errors
413
     * $ydb->dbh
414
     * $ydb->result
415
     * $ydb->rows_affected
416
     * $ydb->show_errors
417
     */
418
419
    /**
420
     * Deprecated functions since 1.7.3
421
     */
422
423
    // @codeCoverageIgnoreStart
424
425
    public function escape($string) {
426
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
427
        // This will escape using PDO->quote(), but then remove the enclosing quotes
428
        return substr($this->quote($string), 1, -1);
429
    }
430
431
    public function get_col($query) {
432
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
433
        yourls_debug_log('LEGACY SQL: '.$query);
434
        return $this->fetchCol($query);
435
    }
436
437
    public function get_results($query) {
438
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
439
        yourls_debug_log('LEGACY SQL: '.$query);
440
        $stm = parent::query($query);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (query() instead of get_results()). Are you sure this is correct? If so, you might want to change this to $this->query().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
441
        return($stm->fetchAll(PDO::FETCH_OBJ));
442
    }
443
444
    public function get_row($query) {
445
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
446
        yourls_debug_log('LEGACY SQL: '.$query);
447
        $row = $this->fetchObjects($query);
448
        return isset($row[0]) ? $row[0] : null;
449
    }
450
451
    public function get_var($query) {
452
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
453
        yourls_debug_log('LEGACY SQL: '.$query);
454
        return $this->fetchValue($query);
455
    }
456
457
    public function query($query) {
458
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
459
        yourls_debug_log('LEGACY SQL: '.$query);
460
        return $this->fetchAffected($query);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->fetchAffected($query); (integer) is incompatible with the return type declared by the interface Aura\Sql\PdoInterface::query of type PDOStatement.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
461
    }
462
    // @codeCoverageIgnoreEnd
463
}
464