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

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