Passed
Push — master ( 00692a...9cce05 )
by rugk
03:20
created

lib/Data/Database.php (1 issue)

Severity
1
<?php
2
/**
3
 * PrivateBin
4
 *
5
 * a zero-knowledge paste bin
6
 *
7
 * @link      https://github.com/PrivateBin/PrivateBin
8
 * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
9
 * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
10
 * @version   1.2.1
11
 */
12
13
namespace PrivateBin\Data;
14
15
use Exception;
16
use PDO;
17
use PDOException;
18
use PrivateBin\Controller;
19
use stdClass;
20
21
/**
22
 * Database
23
 *
24
 * Model for database access, implemented as a singleton.
25
 */
26
class Database extends AbstractData
27
{
28
    /**
29
     * cache for select queries
30
     *
31
     * @var array
32
     */
33
    private static $_cache = array();
34
35
    /**
36
     * instance of database connection
37
     *
38
     * @access private
39
     * @static
40
     * @var PDO
41
     */
42
    private static $_db;
43
44
    /**
45
     * table prefix
46
     *
47
     * @access private
48
     * @static
49
     * @var string
50
     */
51
    private static $_prefix = '';
52
53
    /**
54
     * database type
55
     *
56
     * @access private
57
     * @static
58
     * @var string
59
     */
60
    private static $_type = '';
61
62
    /**
63
     * get instance of singleton
64
     *
65
     * @access public
66
     * @static
67
     * @param  array $options
68
     * @throws Exception
69
     * @return Database
70
     */
71 71
    public static function getInstance($options = null)
72
    {
73
        // if needed initialize the singleton
74 71
        if (!(self::$_instance instanceof self)) {
75 41
            self::$_instance = new self;
76
        }
77
78 71
        if (is_array($options)) {
79
            // set table prefix if given
80 71
            if (array_key_exists('tbl', $options)) {
81 50
                self::$_prefix = $options['tbl'];
82
            }
83
84
            // initialize the db connection with new options
85
            if (
86 71
                array_key_exists('dsn', $options) &&
87 71
                array_key_exists('usr', $options) &&
88 71
                array_key_exists('pwd', $options) &&
89 71
                array_key_exists('opt', $options)
90
            ) {
91
                // set default options
92 71
                $options['opt'][PDO::ATTR_ERRMODE]          = PDO::ERRMODE_EXCEPTION;
93 71
                $options['opt'][PDO::ATTR_EMULATE_PREPARES] = false;
94 71
                $options['opt'][PDO::ATTR_PERSISTENT]       = true;
95 71
                $db_tables_exist                            = true;
96
97
                // setup type and dabase connection
98 71
                self::$_type = strtolower(
99 71
                    substr($options['dsn'], 0, strpos($options['dsn'], ':'))
100
                );
101 71
                $tableQuery = self::_getTableQuery(self::$_type);
102 71
                self::$_db  = new PDO(
103 71
                    $options['dsn'],
104 71
                    $options['usr'],
105 71
                    $options['pwd'],
106 71
                    $options['opt']
107
                );
108
109
                // check if the database contains the required tables
110 71
                $tables = self::$_db->query($tableQuery)->fetchAll(PDO::FETCH_COLUMN, 0);
111
112
                // create paste table if necessary
113 71
                if (!in_array(self::_sanitizeIdentifier('paste'), $tables)) {
114 43
                    self::_createPasteTable();
115 43
                    $db_tables_exist = false;
116
                }
117
118
                // create comment table if necessary
119 71
                if (!in_array(self::_sanitizeIdentifier('comment'), $tables)) {
120 43
                    self::_createCommentTable();
121 43
                    $db_tables_exist = false;
122
                }
123
124
                // create config table if necessary
125 71
                $db_version = Controller::VERSION;
126 71
                if (!in_array(self::_sanitizeIdentifier('config'), $tables)) {
127 43
                    self::_createConfigTable();
128
                    // if we only needed to create the config table, the DB is older then 0.22
129 43
                    if ($db_tables_exist) {
130 43
                        $db_version = '0.21';
131
                    }
132
                } else {
133 62
                    $db_version = self::_getConfig('VERSION');
134
                }
135
136
                // update database structure if necessary
137 71
                if (version_compare($db_version, Controller::VERSION, '<')) {
138 71
                    self::_upgradeDatabase($db_version);
139
                }
140
            } else {
141 4
                throw new Exception(
142 4
                    'Missing configuration for key dsn, usr, pwd or opt in the section model_options, please check your configuration file', 6
143
                );
144
            }
145
        }
146
147 71
        return self::$_instance;
148
    }
149
150
    /**
151
     * Create a paste.
152
     *
153
     * @access public
154
     * @param  string $pasteid
155
     * @param  array  $paste
156
     * @return bool
157
     */
158 42
    public function create($pasteid, $paste)
159
    {
160
        if (
161 42
            array_key_exists($pasteid, self::$_cache)
162
        ) {
163 42
            if (false !== self::$_cache[$pasteid]) {
164 2
                return false;
165
            } else {
166 42
                unset(self::$_cache[$pasteid]);
167
            }
168
        }
169
170 42
        $opendiscussion = $burnafterreading = false;
171 42
        $attachment     = $attachmentname     = '';
172 42
        $meta           = $paste['meta'];
173 42
        unset($meta['postdate']);
174 42
        $expire_date = 0;
175 42
        if (array_key_exists('expire_date', $paste['meta'])) {
176 10
            $expire_date = (int) $paste['meta']['expire_date'];
177 10
            unset($meta['expire_date']);
178
        }
179 42
        if (array_key_exists('opendiscussion', $paste['meta'])) {
180 29
            $opendiscussion = (bool) $paste['meta']['opendiscussion'];
181 29
            unset($meta['opendiscussion']);
182
        }
183 42
        if (array_key_exists('burnafterreading', $paste['meta'])) {
184 4
            $burnafterreading = (bool) $paste['meta']['burnafterreading'];
185 4
            unset($meta['burnafterreading']);
186
        }
187 42
        if (array_key_exists('attachment', $paste['meta'])) {
188 2
            $attachment = $paste['meta']['attachment'];
189 2
            unset($meta['attachment']);
190
        }
191 42
        if (array_key_exists('attachmentname', $paste['meta'])) {
192 2
            $attachmentname = $paste['meta']['attachmentname'];
193 2
            unset($meta['attachmentname']);
194
        }
195 42
        return self::_exec(
196 42
            'INSERT INTO ' . self::_sanitizeIdentifier('paste') .
197 42
            ' VALUES(?,?,?,?,?,?,?,?,?)',
198
            array(
199 42
                $pasteid,
200 42
                $paste['data'],
201 42
                $paste['meta']['postdate'],
202 42
                $expire_date,
203 42
                (int) $opendiscussion,
204 42
                (int) $burnafterreading,
205 42
                json_encode($meta),
206 42
                $attachment,
207 42
                $attachmentname,
208
            )
209
        );
210
    }
211
212
    /**
213
     * Read a paste.
214
     *
215
     * @access public
216
     * @param  string $pasteid
217
     * @return stdClass|false
218
     */
219 58
    public function read($pasteid)
220
    {
221
        if (
222 58
            !array_key_exists($pasteid, self::$_cache)
223
        ) {
224 58
            self::$_cache[$pasteid] = false;
225 58
            $paste                  = self::_select(
226 58
                'SELECT * FROM ' . self::_sanitizeIdentifier('paste') .
227 58
                ' WHERE dataid = ?', array($pasteid), true
228
            );
229
230 58
            if (false !== $paste) {
0 ignored issues
show
The condition false !== $paste is always true.
Loading history...
231
                // create object
232 41
                self::$_cache[$pasteid]       = new stdClass;
233 41
                self::$_cache[$pasteid]->data = $paste['data'];
234
235 41
                $meta = json_decode($paste['meta']);
236 41
                if (!is_object($meta)) {
237
                    $meta = new stdClass;
238
                }
239
240
                // support older attachments
241 41
                if (property_exists($meta, 'attachment')) {
242 1
                    self::$_cache[$pasteid]->attachment = $meta->attachment;
243 1
                    unset($meta->attachment);
244 1
                    if (property_exists($meta, 'attachmentname')) {
245 1
                        self::$_cache[$pasteid]->attachmentname = $meta->attachmentname;
246 1
                        unset($meta->attachmentname);
247
                    }
248
                }
249
                // support current attachments
250 40
                elseif (array_key_exists('attachment', $paste) && strlen($paste['attachment'])) {
251 2
                    self::$_cache[$pasteid]->attachment = $paste['attachment'];
252 2
                    if (array_key_exists('attachmentname', $paste) && strlen($paste['attachmentname'])) {
253 2
                        self::$_cache[$pasteid]->attachmentname = $paste['attachmentname'];
254
                    }
255
                }
256 41
                self::$_cache[$pasteid]->meta           = $meta;
257 41
                self::$_cache[$pasteid]->meta->postdate = (int) $paste['postdate'];
258 41
                $expire_date                            = (int) $paste['expiredate'];
259
                if (
260 41
                    $expire_date > 0
261
                ) {
262 11
                    self::$_cache[$pasteid]->meta->expire_date = $expire_date;
263
                }
264
                if (
265 41
                    $paste['opendiscussion']
266
                ) {
267 28
                    self::$_cache[$pasteid]->meta->opendiscussion = true;
268
                }
269
                if (
270 41
                    $paste['burnafterreading']
271
                ) {
272 4
                    self::$_cache[$pasteid]->meta->burnafterreading = true;
273
                }
274
            }
275
        }
276
277 58
        return self::$_cache[$pasteid];
278
    }
279
280
    /**
281
     * Delete a paste and its discussion.
282
     *
283
     * @access public
284
     * @param  string $pasteid
285
     */
286 22
    public function delete($pasteid)
287
    {
288 22
        self::_exec(
289 22
            'DELETE FROM ' . self::_sanitizeIdentifier('paste') .
290 22
            ' WHERE dataid = ?', array($pasteid)
291
        );
292 22
        self::_exec(
293 22
            'DELETE FROM ' . self::_sanitizeIdentifier('comment') .
294 22
            ' WHERE pasteid = ?', array($pasteid)
295
        );
296
        if (
297 22
            array_key_exists($pasteid, self::$_cache)
298
        ) {
299 20
            unset(self::$_cache[$pasteid]);
300
        }
301 22
    }
302
303
    /**
304
     * Test if a paste exists.
305
     *
306
     * @access public
307
     * @param  string $pasteid
308
     * @return bool
309
     */
310 57
    public function exists($pasteid)
311
    {
312
        if (
313 57
            !array_key_exists($pasteid, self::$_cache)
314
        ) {
315 57
            self::$_cache[$pasteid] = $this->read($pasteid);
316
        }
317 57
        return (bool) self::$_cache[$pasteid];
318
    }
319
320
    /**
321
     * Create a comment in a paste.
322
     *
323
     * @access public
324
     * @param  string $pasteid
325
     * @param  string $parentid
326
     * @param  string $commentid
327
     * @param  array  $comment
328
     * @return bool
329
     */
330 9
    public function createComment($pasteid, $parentid, $commentid, $comment)
331
    {
332 9
        foreach (array('nickname', 'vizhash') as $key) {
333 9
            if (!array_key_exists($key, $comment['meta'])) {
334 9
                $comment['meta'][$key] = null;
335
            }
336
        }
337 9
        return self::_exec(
338 9
            'INSERT INTO ' . self::_sanitizeIdentifier('comment') .
339 9
            ' VALUES(?,?,?,?,?,?,?)',
340
            array(
341 9
                $commentid,
342 9
                $pasteid,
343 9
                $parentid,
344 9
                $comment['data'],
345 9
                $comment['meta']['nickname'],
346 9
                $comment['meta']['vizhash'],
347 9
                $comment['meta']['postdate'],
348
            )
349
        );
350
    }
351
352
    /**
353
     * Read all comments of paste.
354
     *
355
     * @access public
356
     * @param  string $pasteid
357
     * @return array
358
     */
359 18
    public function readComments($pasteid)
360
    {
361 18
        $rows = self::_select(
362 18
            'SELECT * FROM ' . self::_sanitizeIdentifier('comment') .
363 18
            ' WHERE pasteid = ?', array($pasteid)
364
        );
365
366
        // create comment list
367 18
        $comments = array();
368 18
        if (count($rows)) {
369 7
            foreach ($rows as $row) {
370 7
                $i                            = $this->getOpenSlot($comments, (int) $row['postdate']);
371 7
                $comments[$i]                 = new stdClass;
372 7
                $comments[$i]->id             = $row['dataid'];
373 7
                $comments[$i]->parentid       = $row['parentid'];
374 7
                $comments[$i]->data           = $row['data'];
375 7
                $comments[$i]->meta           = new stdClass;
376 7
                $comments[$i]->meta->postdate = (int) $row['postdate'];
377 7
                foreach (array('nickname', 'vizhash') as $key) {
378 7
                    if (array_key_exists($key, $row) && !empty($row[$key])) {
379 7
                        $comments[$i]->meta->$key = $row[$key];
380
                    }
381
                }
382
            }
383 7
            ksort($comments);
384
        }
385 18
        return $comments;
386
    }
387
388
    /**
389
     * Test if a comment exists.
390
     *
391
     * @access public
392
     * @param  string $pasteid
393
     * @param  string $parentid
394
     * @param  string $commentid
395
     * @return bool
396
     */
397 12
    public function existsComment($pasteid, $parentid, $commentid)
398
    {
399 12
        return (bool) self::_select(
400 12
            'SELECT dataid FROM ' . self::_sanitizeIdentifier('comment') .
401 12
            ' WHERE pasteid = ? AND parentid = ? AND dataid = ?',
402 12
            array($pasteid, $parentid, $commentid), true
403
        );
404
    }
405
406
    /**
407
     * Returns up to batch size number of paste ids that have expired
408
     *
409
     * @access private
410
     * @param  int $batchsize
411
     * @return array
412
     */
413 15
    protected function _getExpiredPastes($batchsize)
414
    {
415 15
        $pastes = array();
416 15
        $rows   = self::_select(
417 15
            'SELECT dataid FROM ' . self::_sanitizeIdentifier('paste') .
418 15
            ' WHERE expiredate < ? AND expiredate != ? LIMIT ?', array(time(), 0, $batchsize)
419
        );
420 15
        if (count($rows)) {
421 2
            foreach ($rows as $row) {
422 2
                $pastes[] = $row['dataid'];
423
            }
424
        }
425 15
        return $pastes;
426
    }
427
428
    /**
429
     * execute a statement
430
     *
431
     * @access private
432
     * @static
433
     * @param  string $sql
434
     * @param  array $params
435
     * @throws PDOException
436
     * @return bool
437
     */
438 58
    private static function _exec($sql, array $params)
439
    {
440 58
        $statement = self::$_db->prepare($sql);
441 58
        $result    = $statement->execute($params);
442 58
        $statement->closeCursor();
443 58
        return $result;
444
    }
445
446
    /**
447
     * run a select statement
448
     *
449
     * @access private
450
     * @static
451
     * @param  string $sql
452
     * @param  array $params
453
     * @param  bool $firstOnly if only the first row should be returned
454
     * @throws PDOException
455
     * @return array
456
     */
457 70
    private static function _select($sql, array $params, $firstOnly = false)
458
    {
459 70
        $statement = self::$_db->prepare($sql);
460 70
        $statement->execute($params);
461 70
        $result = $firstOnly ?
462 70
            $statement->fetch(PDO::FETCH_ASSOC) :
463 70
            $statement->fetchAll(PDO::FETCH_ASSOC);
464 70
        $statement->closeCursor();
465 70
        return $result;
466
    }
467
468
    /**
469
     * get table list query, depending on the database type
470
     *
471
     * @access private
472
     * @static
473
     * @param  string $type
474
     * @throws Exception
475
     * @return string
476
     */
477 71
    private static function _getTableQuery($type)
478
    {
479
        switch ($type) {
480 71
            case 'ibm':
481 1
                $sql = 'SELECT tabname FROM SYSCAT.TABLES ';
482 1
                break;
483 71
            case 'informix':
484 1
                $sql = 'SELECT tabname FROM systables ';
485 1
                break;
486 71
            case 'mssql':
487
                $sql = 'SELECT name FROM sysobjects '
488 1
                     . "WHERE type = 'U' ORDER BY name";
489 1
                break;
490 71
            case 'mysql':
491 1
                $sql = 'SHOW TABLES';
492 1
                break;
493 71
            case 'oci':
494 1
                $sql = 'SELECT table_name FROM all_tables';
495 1
                break;
496 71
            case 'pgsql':
497
                $sql = 'SELECT c.relname AS table_name '
498
                     . 'FROM pg_class c, pg_user u '
499
                     . "WHERE c.relowner = u.usesysid AND c.relkind = 'r' "
500
                     . 'AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) '
501
                     . "AND c.relname !~ '^(pg_|sql_)' "
502
                     . 'UNION '
503
                     . 'SELECT c.relname AS table_name '
504
                     . 'FROM pg_class c '
505
                     . "WHERE c.relkind = 'r' "
506
                     . 'AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) '
507
                     . 'AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) '
508 1
                     . "AND c.relname !~ '^pg_'";
509 1
                break;
510 71
            case 'sqlite':
511
                $sql = "SELECT name FROM sqlite_master WHERE type='table' "
512
                     . 'UNION ALL SELECT name FROM sqlite_temp_master '
513 71
                     . "WHERE type='table' ORDER BY name";
514 71
                break;
515
            default:
516 1
                throw new Exception(
517 1
                    "PDO type $type is currently not supported.", 5
518
                );
519
        }
520 71
        return $sql;
521
    }
522
523
    /**
524
     * get a value by key from the config table
525
     *
526
     * @access private
527
     * @static
528
     * @param  string $key
529
     * @throws PDOException
530
     * @return string
531
     */
532 62
    private static function _getConfig($key)
533
    {
534 62
        $row = self::_select(
535 62
            'SELECT value FROM ' . self::_sanitizeIdentifier('config') .
536 62
            ' WHERE id = ?', array($key), true
537
        );
538 62
        return $row['value'];
539
    }
540
541
    /**
542
     * get the primary key clauses, depending on the database driver
543
     *
544
     * @access private
545
     * @static
546
     * @param string $key
547
     * @return array
548
     */
549 43
    private static function _getPrimaryKeyClauses($key = 'dataid')
550
    {
551 43
        $main_key = $after_key = '';
552 43
        if (self::$_type === 'mysql') {
553
            $after_key = ", PRIMARY KEY ($key)";
554
        } else {
555 43
            $main_key = ' PRIMARY KEY';
556
        }
557 43
        return array($main_key, $after_key);
558
    }
559
560
    /**
561
     * create the paste table
562
     *
563
     * @access private
564
     * @static
565
     */
566 43
    private static function _createPasteTable()
567
    {
568 43
        list($main_key, $after_key) = self::_getPrimaryKeyClauses();
569 43
        $dataType                   = self::$_type === 'pgsql' ? 'TEXT' : 'BLOB';
570 43
        self::$_db->exec(
571 43
            'CREATE TABLE ' . self::_sanitizeIdentifier('paste') . ' ( ' .
572 43
            "dataid CHAR(16) NOT NULL$main_key, " .
573 43
            "data $dataType, " .
574 43
            'postdate INT, ' .
575 43
            'expiredate INT, ' .
576 43
            'opendiscussion INT, ' .
577 43
            'burnafterreading INT, ' .
578 43
            'meta TEXT, ' .
579 43
            'attachment ' . (self::$_type === 'pgsql' ? 'TEXT' : 'MEDIUMBLOB') . ', ' .
580 43
            "attachmentname $dataType$after_key );"
581
        );
582 43
    }
583
584
    /**
585
     * create the paste table
586
     *
587
     * @access private
588
     * @static
589
     */
590 43
    private static function _createCommentTable()
591
    {
592 43
        list($main_key, $after_key) = self::_getPrimaryKeyClauses();
593 43
        $dataType                   = self::$_type === 'pgsql' ? 'text' : 'BLOB';
594 43
        self::$_db->exec(
595 43
            'CREATE TABLE ' . self::_sanitizeIdentifier('comment') . ' ( ' .
596 43
            "dataid CHAR(16) NOT NULL$main_key, " .
597 43
            'pasteid CHAR(16), ' .
598 43
            'parentid CHAR(16), ' .
599 43
            "data $dataType, " .
600 43
            "nickname $dataType, " .
601 43
            "vizhash $dataType, " .
602 43
            "postdate INT$after_key );"
603
        );
604 43
        self::$_db->exec(
605
            'CREATE INDEX IF NOT EXISTS comment_parent ON ' .
606 43
            self::_sanitizeIdentifier('comment') . '(pasteid);'
607
        );
608 43
    }
609
610
    /**
611
     * create the paste table
612
     *
613
     * @access private
614
     * @static
615
     */
616 43
    private static function _createConfigTable()
617
    {
618 43
        list($main_key, $after_key) = self::_getPrimaryKeyClauses('id');
619 43
        self::$_db->exec(
620 43
            'CREATE TABLE ' . self::_sanitizeIdentifier('config') .
621 43
            " ( id CHAR(16) NOT NULL$main_key, value TEXT$after_key );"
622
        );
623 43
        self::_exec(
624 43
            'INSERT INTO ' . self::_sanitizeIdentifier('config') .
625 43
            ' VALUES(?,?)',
626 43
            array('VERSION', Controller::VERSION)
627
        );
628 43
    }
629
630
    /**
631
     * sanitizes identifiers
632
     *
633
     * @access private
634
     * @static
635
     * @param  string $identifier
636
     * @return string
637
     */
638 71
    private static function _sanitizeIdentifier($identifier)
639
    {
640 71
        return preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier);
641
    }
642
643
    /**
644
     * upgrade the database schema from an old version
645
     *
646
     * @access private
647
     * @static
648
     * @param  string $oldversion
649
     */
650 1
    private static function _upgradeDatabase($oldversion)
651
    {
652 1
        $dataType = self::$_type === 'pgsql' ? 'TEXT' : 'BLOB';
653
        switch ($oldversion) {
654 1
            case '0.21':
655
                // create the meta column if necessary (pre 0.21 change)
656
                try {
657 1
                    self::$_db->exec('SELECT meta FROM ' . self::_sanitizeIdentifier('paste') . ' LIMIT 1;');
658 1
                } catch (PDOException $e) {
659 1
                    self::$_db->exec('ALTER TABLE ' . self::_sanitizeIdentifier('paste') . ' ADD COLUMN meta TEXT;');
660
                }
661
                // SQLite only allows one ALTER statement at a time...
662 1
                self::$_db->exec(
663 1
                    'ALTER TABLE ' . self::_sanitizeIdentifier('paste') .
664 1
                    ' ADD COLUMN attachment ' .
665 1
                    (self::$_type === 'pgsql' ? 'TEXT' : 'MEDIUMBLOB') . ';'
666
                );
667 1
                self::$_db->exec(
668 1
                    'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . " ADD COLUMN attachmentname $dataType;"
669
                );
670
                // SQLite doesn't support MODIFY, but it allows TEXT of similar
671
                // size as BLOB, so there is no need to change it there
672 1
                if (self::$_type !== 'sqlite') {
673
                    self::$_db->exec(
674
                        'ALTER TABLE ' . self::_sanitizeIdentifier('paste') .
675
                        ' ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType;'
676
                    );
677
                    self::$_db->exec(
678
                        'ALTER TABLE ' . self::_sanitizeIdentifier('comment') .
679
                        " ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType, " .
680
                        "MODIFY COLUMN nickname $dataType, MODIFY COLUMN vizhash $dataType;"
681
                    );
682
                } else {
683 1
                    self::$_db->exec(
684
                        'CREATE UNIQUE INDEX IF NOT EXISTS paste_dataid ON ' .
685 1
                        self::_sanitizeIdentifier('paste') . '(dataid);'
686
                    );
687 1
                    self::$_db->exec(
688
                        'CREATE UNIQUE INDEX IF NOT EXISTS comment_dataid ON ' .
689 1
                        self::_sanitizeIdentifier('comment') . '(dataid);'
690
                    );
691
                }
692 1
                self::$_db->exec(
693
                    'CREATE INDEX IF NOT EXISTS comment_parent ON ' .
694 1
                    self::_sanitizeIdentifier('comment') . '(pasteid);'
695
                );
696
                // no break, continue with updates for 0.22 and later
697
            default:
698 1
                self::_exec(
699 1
                    'UPDATE ' . self::_sanitizeIdentifier('config') .
700 1
                    ' SET value = ? WHERE id = ?',
701 1
                    array(Controller::VERSION, 'VERSION')
702
                );
703
        }
704 1
    }
705
}
706