Passed
Pull Request — master (#24)
by
unknown
06:34
created

SqlSchema::createAssignedPostsTable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.864

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 9
c 2
b 0
f 0
dl 0
loc 16
ccs 4
cts 10
cp 0.4
rs 9.9666
cc 2
nc 2
nop 0
crap 2.864
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Database;
4
5
use GeminiLabs\SiteReviews\Database;
6
use GeminiLabs\SiteReviews\Helpers\Str;
7
8
class SqlSchema
9
{
10
    /**
11
     * @var array|null
12
     */
13
    protected $constraints;
14
15
    /**
16
     * @var \wpdb
17
     */
18
    protected $db;
19
20
    /**
21
     * @var array|null
22
     */
23
    protected $tables;
24
25 15
    public function __construct()
26
    {
27 15
        require_once ABSPATH.'wp-admin/includes/upgrade.php';
28 15
        global $wpdb;
29 15
        $this->db = $wpdb;
30 15
    }
31
32
    /**
33
     * @return void
34
     */
35
    public function addAssignedPostsForeignConstraints()
36
    {
37
        $this->addForeignConstraint(
38
            $table = $this->table('assigned_posts'),
39
            $constraint = $this->foreignConstraint('assigned_posts_rating_id'),
40
            $foreignKey = 'rating_id',
41
            $foreignTable = $this->table('ratings'), 
42
            $foreignColumn = 'ID'
43
        );
44
        $this->addForeignConstraint(
45
            $table = $this->table('assigned_posts'),
46
            $constraint = $this->foreignConstraint('assigned_posts_post_id'),
47
            $foreignKey = 'post_id',
48
            $foreignTable = $this->db->posts, 
49
            $foreignColumn = 'ID'
50
        );
51
    }
52
53
    /**
54
     * @return void
55
     */
56
    public function addAssignedTermsForeignConstraints()
57
    {
58
        $this->addForeignConstraint(
59
            $table = $this->table('assigned_terms'),
60
            $constraint = $this->foreignConstraint('assigned_terms_rating_id'),
61
            $foreignKey = 'rating_id',
62
            $foreignTable = $this->table('ratings'), 
63
            $foreignColumn = 'ID'
64
        );
65
        $this->addForeignConstraint(
66
            $table = $this->table('assigned_terms'),
67
            $constraint = $this->foreignConstraint('assigned_terms_term_id'),
68
            $foreignKey = 'term_id',
69
            $foreignTable = $this->db->terms, 
70
            $foreignColumn = 'term_id'
71
        );
72
    }
73
74
    /**
75
     * @return void
76
     */
77
    public function addAssignedUsersForeignConstraints()
78
    {
79
        $this->addForeignConstraint(
80
            $table = $this->table('assigned_users'),
81
            $constraint = $this->foreignConstraint('assigned_users_rating_id'),
82
            $foreignKey = 'rating_id',
83
            $foreignTable = $this->table('ratings'), 
84
            $foreignColumn = 'ID'
85
        );
86
        $this->addForeignConstraint(
87
            $table = $this->table('assigned_users'),
88
            $constraint = $this->foreignConstraint('assigned_users_user_id'),
89
            $foreignKey = 'user_id',
90
            $foreignTable = $this->db->users, 
91
            $foreignColumn = 'ID'
92
        );
93
    }
94
95
    /**
96
     * @return void
97
     */
98
    public function addReviewsForeignConstraints()
99
    {
100
        $this->addForeignConstraint(
101
            $table = $this->table('ratings'),
102
            $constraint = $this->foreignConstraint('assigned_posts_review_id'),
103
            $foreignKey = 'review_id',
104
            $foreignTable = $this->db->posts, 
105
            $foreignColumn = 'ID'
106
        );
107
    }
108
109
    /**
110
     * This method expects the fully formed foreign constraint key
111
     * @param string $table
112
     * @param string $constraint
113
     * @param string $foreignKey
114
     * @param string $foreignTable
115
     * @param string $foreignColumn
116
     * @return int|bool
117
     * @see $this->foreignConstraint()
118
     */
119
    public function addForeignConstraint($table, $constraint, $foreignKey, $foreignTable, $foreignColumn)
120
    {
121
        if ($this->foreignConstraintExists($constraint, $foreignTable)) {
122
            return false;
123
        }
124
        return glsr(Database::class)->dbQuery(glsr(Query::class)->sql("
125
            ALTER TABLE {$table}
126
            ADD CONSTRAINT {$constraint}
127
            FOREIGN KEY ({$foreignKey})
128
            REFERENCES {$foreignTable} ({$foreignColumn})
129
            ON DELETE CASCADE
130
        "));
131
    }
132
133
    /**
134
     * @return void
135
     */
136 7
    public function addForeignConstraints()
137
    {
138 7
        if (!defined('GLSR_UNIT_TESTS')) {
139
            $this->addAssignedPostsForeignConstraints();
140
            $this->addAssignedTermsForeignConstraints();
141
            $this->addAssignedUsersForeignConstraints();
142
            $this->addReviewsForeignConstraints();
143
        }
144 7
    }
145
146
    /**
147
     * @return bool
148
     */
149 7
    public function createAssignedPostsTable()
150
    {
151 7
        if ($this->tableExists('assigned_posts')) {
152 7
            glsr_log()->debug(sprintf('Cannot create the %s table because it already exists.', $this->table('assigned_posts')));
153 7
            return false;
154
        }
155
        dbDelta(glsr(Query::class)->sql("
156
            CREATE TABLE {$this->table('assigned_posts')} (
157
                rating_id bigint(20) unsigned NOT NULL,
158
                post_id bigint(20) unsigned NOT NULL,
159
                is_published tinyint(1) NOT NULL DEFAULT '1',
160
                UNIQUE KEY {$this->prefix('assigned_posts_rating_id_post_id_unique')} (rating_id,post_id)
161
            ) ENGINE=InnoDB {$this->db->get_charset_collate()};
162
        "));
163
        glsr(Database::class)->logErrors();
164
        return true;
165
    }
166
167
    /**
168
     * @return bool
169
     */
170 7
    public function createAssignedTermsTable()
171
    {
172 7
        if ($this->tableExists('assigned_terms')) {
173 7
            glsr_log()->debug(sprintf('Cannot create the %s table because it already exists.', $this->table('assigned_terms')));
174 7
            return false;
175
        }
176
        dbDelta(glsr(Query::class)->sql("
177
            CREATE TABLE {$this->table('assigned_terms')} (
178
                rating_id bigint(20) unsigned NOT NULL,
179
                term_id bigint(20) unsigned NOT NULL,
180
                UNIQUE KEY {$this->prefix('assigned_terms_rating_id_term_id_unique')} (rating_id,term_id)
181
            ) ENGINE=InnoDB {$this->db->get_charset_collate()};
182
        "));
183
        glsr(Database::class)->logErrors();
184
        return true;
185
    }
186
187
    /**
188
     * @return bool
189
     */
190 7
    public function createAssignedUsersTable()
191
    {
192 7
        if ($this->tableExists('assigned_users')) {
193 7
            glsr_log()->debug(sprintf('Cannot create the %s table because it already exists.', $this->table('assigned_users')));
194 7
            return false;
195
        }
196
        dbDelta(glsr(Query::class)->sql("
197
            CREATE TABLE {$this->table('assigned_users')} (
198
                rating_id bigint(20) unsigned NOT NULL,
199
                user_id bigint(20) unsigned NOT NULL,
200
                UNIQUE KEY {$this->prefix('assigned_users_rating_id_user_id_unique')} (rating_id,user_id)
201
            ) ENGINE=InnoDB {$this->db->get_charset_collate()};
202
        "));
203
        glsr(Database::class)->logErrors();
204
        return true;
205
    }
206
207
    /**
208
     * WordPress codex says there must be two spaces between PRIMARY KEY and the key definition.
209
     * @return bool
210
     * @see https://codex.wordpress.org/Creating_Tables_with_Plugins
211
     */
212 7
    public function createRatingTable()
213
    {
214 7
        if ($this->tableExists('ratings')) {
215 7
            glsr_log()->debug(sprintf('Cannot create the %s table because it already exists.', $this->table('ratings')));
216 7
            return false;
217
        }
218
        dbDelta(glsr(Query::class)->sql("
219
            CREATE TABLE {$this->table('ratings')} (
220
                ID bigint(20) unsigned NOT NULL AUTO_INCREMENT,
221
                review_id bigint(20) unsigned NOT NULL,
222
                rating int(11) NOT NULL DEFAULT '0',
223
                type varchar(20) DEFAULT 'local',
224
                is_approved tinyint(1) NOT NULL DEFAULT '0',
225
                is_pinned tinyint(1) NOT NULL DEFAULT '0',
226
                name varchar(250) DEFAULT NULL,
227
                email varchar(100) DEFAULT NULL,
228
                avatar varchar(200) DEFAULT NULL,
229
                ip_address varchar(100) DEFAULT NULL,
230
                url varchar(250) DEFAULT NULL,
231
                PRIMARY KEY  (ID),
232
                UNIQUE KEY {$this->prefix('ratings_review_id_unique')} (review_id),
233
                KEY {$this->prefix('ratings_rating_type_is_pinned_index')} (rating,type,is_pinned)
234
            ) ENGINE=InnoDB {$this->db->get_charset_collate()};
235
        "));
236
        glsr(Database::class)->logErrors();
237
        return true;
238
    }
239
240
    /**
241
     * @return void
242
     */
243 7
    public function createTables()
244
    {
245 7
        $this->createAssignedPostsTable();
246 7
        $this->createAssignedTermsTable();
247 7
        $this->createAssignedUsersTable();
248 7
        $this->createRatingTable();
249 7
        add_option(glsr()->prefix.'db_version', '1.0');
250 7
    }
251
252
    /**
253
     * @param string $table
254
     * @param string $constraint
255
     * @return int|bool
256
     */
257
    public function dropForeignConstraint($table, $constraint)
258
    {
259
        $table = $this->table($table);
260
        $constraint = $this->foreignConstraint($constraint);
261
        if (!$this->foreignConstraintExists($constraint)) {
262
            return false;
263
        }
264
        return glsr(Database::class)->dbQuery(glsr(Query::class)->sql("
265
            ALTER TABLE {$table} DROP FOREIGN KEY {$constraint};
266
        "));
267
    }
268
269
    /**
270
     * This method expects the fully formed foreign constraint key
271
     * @param string $constraint
272
     * @param string $foreignTable
273
     * @return bool
274
     * @see $this->foreignConstraint()
275
     */
276
    public function foreignConstraintExists($constraint, $foreignTable = '')
277
    {
278
        if (!empty($foreignTable) && !$this->isInnodb($foreignTable)) {
279
            glsr_log()->debug("Cannot check for a foreign constraint because {$foreignTable} does not use the InnoDB engine.");
280
            return true; // we cannot create foreign contraints on MyISAM tables
281
        }
282
        if (!is_array($this->constraints)) {
283
            $this->constraints = $this->db->get_col("
284
                SELECT CONSTRAINT_NAME
285
                FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
286
                WHERE CONSTRAINT_SCHEMA = '{$this->db->dbname}'
287
            ");
288
        }
289
        return in_array($constraint, $this->constraints);
290
    }
291
292
    /**
293
     * @param string $table
294
     * @return bool
295
     */
296
    public function isInnodb($table)
297
    {
298
        // Performance improvement: Skip checking innodb if you are sure your db is innodb only
299
        if (defined('GLSR_FORCE_INNODB') && GLSR_FORCE_INNODB == true) {
0 ignored issues
show
Bug introduced by
The constant GeminiLabs\SiteReviews\Database\GLSR_FORCE_INNODB was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
300
            return true;
301
        }
302
        $engine = $this->db->get_var("
303
            SELECT ENGINE
304
            FROM INFORMATION_SCHEMA.TABLES
305
            WHERE TABLE_SCHEMA = '{$this->db->dbname}' AND TABLE_NAME = '{$this->table($table)}'
306
        ");
307
        if (empty($engine)) {
308
            glsr_log()->warning(sprintf('InnoDB: The %s database table does not exist.', $this->table($table)));
309
            return false;
310
        }
311
        return 'innodb' === strtolower($engine);
312
    }
313
314
    /**
315
     * @param string $table
316
     * @param string $prefix
317
     * @return string
318
     */
319 15
    public function prefix($table, $prefix = '')
320
    {
321 15
        $table = Str::prefix($table, glsr()->prefix);
322 15
        return Str::prefix($table, $prefix);
323
    }
324
325
    /**
326
     * @param string $constraint
327
     * @return string
328
     */
329
    public function foreignConstraint($constraint)
330
    {
331
        $constraint = Str::prefix($constraint, glsr()->prefix);
332
        $constraint = Str::suffix($constraint, '_foreign');
333
        if (is_multisite() && $this->db->blogid > 1) {
334
            return Str::suffix($constraint, '_'.$this->db->blogid);
335
        }
336
        return $constraint;
337
    }
338
339
    /**
340
     * @param string $table
341
     * @return string
342
     */
343 15
    public function table($table)
344
    {
345 15
        if (in_array($table, $this->db->tables())) {
346
            return $table; // WordPress table is already prefixed
347
        }
348
        // do this next in case another plugin has created a similar table
349 15
        if (Str::endsWith(['ratings', 'assigned_posts', 'assigned_terms', 'assigned_users'], $table)) {
350 15
            $table = Str::removePrefix($table, $this->db->get_blog_prefix());
351 15
            $table = Str::removePrefix($table, glsr()->prefix);
352 15
            return $this->prefix($table, $this->db->get_blog_prefix());
353
        }
354
        if (array_key_exists($table, $this->db->tables())) {
355
            return $this->db->{$table}; // the prefixed WordPress table
356
        }
357
        glsr_log()->error("The {$table} table does not exist.");
358
        return $table; // @todo maybe throw an exception here instead...
359
    }
360
361
    /**
362
     * @param string $table
363
     * @return bool
364
     */
365 7
    public function tableExists($table)
366
    {
367 7
        if (!is_array($this->tables)) {
368 7
            $prefix = $this->db->get_blog_prefix().glsr()->prefix;
369 7
            $this->tables = $this->db->get_col(
370 7
                $this->db->prepare("SHOW TABLES LIKE %s", $this->db->esc_like($prefix).'%')
371
            );
372
        }
373 7
        return in_array($this->table($table), $this->tables);
374
    }
375
376
    /**
377
     * @return string[]
378
     */
379
    public function tableEngines()
380
    {
381
        $results = $this->db->get_results("
382
            SELECT TABLE_NAME, ENGINE
383
            FROM INFORMATION_SCHEMA.TABLES
384
            WHERE TABLE_SCHEMA = '{$this->db->dbname}'
385
            AND TABLE_NAME IN ('{$this->db->options}','{$this->db->posts}','{$this->db->terms}','{$this->db->users}')
386
        ");
387
        $engines = [];
388
        foreach ($results as $result) {
389
            if (!array_key_exists($result->ENGINE, $engines)) {
390
                $engines[$result->ENGINE] = [];
391
            }
392
            $engines[$result->ENGINE][] = Str::removePrefix($result->TABLE_NAME, $this->db->get_blog_prefix());
393
        }
394
        $tableEngines = [];
395
        foreach ($engines as $engine => $tables) {
396
          $tableEngines[] = sprintf('%s (%s)', $engine, implode('|', $tables));
397
        }
398
        return $tableEngines;
399
    }
400
}
401