Test Failed
Push — develop ( 8b2a32...47c531 )
by Paul
08:13
created

Database::posts()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.1852

Importance

Changes 0
Metric Value
eloc 20
c 0
b 0
f 0
dl 0
loc 25
rs 9.6
ccs 4
cts 12
cp 0.3333
cc 2
nc 2
nop 1
crap 3.1852
1
<?php
2
3
namespace GeminiLabs\SiteReviews;
4
5
use GeminiLabs\SiteReviews\Database\Query;
6
use GeminiLabs\SiteReviews\Database\Search\SearchAssignedPosts;
7
use GeminiLabs\SiteReviews\Database\Search\SearchAssignedUsers;
8
use GeminiLabs\SiteReviews\Database\Search\SearchPosts;
9
use GeminiLabs\SiteReviews\Database\Search\SearchUsers;
10
use GeminiLabs\SiteReviews\Database\Tables;
11
use GeminiLabs\SiteReviews\Helpers\Arr;
12
use GeminiLabs\SiteReviews\Helpers\Cast;
13
use GeminiLabs\SiteReviews\Helpers\Str;
14
use GeminiLabs\SiteReviews\Modules\Sanitizer;
15
16
class Database
17
{
18
    use Deprecated;
19
20
    protected \wpdb $db;
21
22 48
    public function __construct()
23
    {
24 48
        global $wpdb;
25 48
        $this->db = $wpdb;
26
    }
27
28
    /**
29
     * Use this before bulk insert (see: $this->finishTransaction()).
30
     */
31 44
    public function beginTransaction(string $table): void
32
    {
33 44
        if (glsr(Tables::class)->isSqlite()) {
34
            $this->dbQuery("BEGIN TRANSACTION;");
35 44
        } elseif (glsr(Tables::class)->isInnodb($table)) {
36
            $this->dbQuery("START TRANSACTION;");
37 44
        } elseif (glsr(Tables::class)->isMyisam($table)) {
38
            $this->dbQuery("SET autocommit = 0;");
39
        }
40
    }
41
42 44
    public function dbGetCol(string $sql): array
43
    {
44 44
        return $this->logErrors($this->db->get_col($sql));
45
    }
46
47
    /**
48
     * Query result in format specified by $output or null on failure.
49
     *
50
     * @return array|object|null
51
     */
52 44
    public function dbGetResults(string $sql, string $output = 'OBJECT')
53
    {
54 44
        $output = Str::restrictTo(['ARRAY_A', 'ARRAY_N', 'OBJECT', 'OBJECT_K'], $output, OBJECT);
55 44
        return $this->logErrors($this->db->get_results($sql, $output));
56
    }
57
58
    /**
59
     * Query result in format specified by $output or null on failure.
60
     *
61
     * @return array|object|void|null
62
     */
63 24
    public function dbGetRow(string $sql, string $output)
64
    {
65 24
        $output = Str::restrictTo(['ARRAY_A', 'ARRAY_N', 'OBJECT'], $output, OBJECT);
66 24
        return $this->logErrors($this->db->get_row($sql, $output));
67
    }
68
69
    /**
70
     * Query result as string, or null on failure.
71
     */
72 22
    public function dbGetVar(string $sql): ?string
73
    {
74 22
        return $this->logErrors($this->db->get_var($sql));
75
    }
76
77
    /**
78
     * True for CREATE, ALTER, TRUNCATE and DROP queries.
79
     * Number of rows affected/selected for all other queries.
80
     * False on error.
81
     *
82
     * @return int|bool
83
     */
84 44
    public function dbQuery(string $sql)
85
    {
86 44
        return $this->logErrors($this->db->query($sql));
87
    }
88
89
    /**
90
     * True for CREATE, ALTER, TRUNCATE and DROP queries.
91
     * Number of rows affected/selected for all other queries.
92
     * False on error.
93
     *
94
     * @return int|bool
95
     */
96 44
    public function dbSafeQuery(string $sql)
97
    {
98 44
        if (glsr(Tables::class)->isSqlite()) {
99
            return $this->dbQuery($sql);
100
        }
101 44
        $this->db->query('SET foreign_key_checks = 0');
102 44
        $result = $this->logErrors($this->db->query($sql));
103 44
        $this->db->query('SET foreign_key_checks = 1');
104 44
        return $result;
105
    }
106
107
    /**
108
     * Number of rows deleted. False on error.
109
     *
110
     * @return int|false
111
     */
112 3
    public function delete(string $table, array $where)
113
    {
114 3
        $result = $this->db->delete(glsr(Tables::class)->table($table), $where);
115 3
        glsr(Query::class)->sql($this->db->last_query); // for logging use only
116 3
        return $this->logErrors($result);
117
    }
118
119
    /**
120
     * Number of rows deleted. False on error.
121
     *
122
     * @param string|string[] $keys
123
     *
124
     * @return int|false
125
     */
126 44
    public function deleteMeta($keys, string $table = 'postmeta')
127
    {
128 44
        $table = glsr(Tables::class)->table($table);
129 44
        $metaKeys = glsr(Query::class)->escValuesForInsert(Arr::convertFromString($keys));
130 44
        $sql = glsr(Query::class)->sql("
131 44
            DELETE FROM {$table} WHERE meta_key IN {$metaKeys}
132 44
        ");
133 44
        return $this->dbQuery($sql);
134
    }
135
136
    /**
137
     * Use this after bulk insert (see: $this->beginTransaction()).
138
     */
139 44
    public function finishTransaction(string $table): void
140
    {
141 44
        if (glsr(Tables::class)->isSqlite()) {
142
            $this->dbQuery("COMMIT;");
143 44
        } elseif (glsr(Tables::class)->isInnodb($table)) {
144
            $this->dbQuery("COMMIT;");
145 44
        } elseif (glsr(Tables::class)->isMyisam($table)) {
146
            $this->dbQuery("SET autocommit = 1;");
147
        }
148
    }
149
150
    /**
151
     * Number of rows inserted. False on error.
152
     *
153
     * @return int|false
154
     */
155 24
    public function insert(string $table, array $data)
156
    {
157 24
        $this->db->insert_id = 0;
158 24
        $table = glsr(Tables::class)->table($table);
159 24
        $fields = glsr(Query::class)->escFieldsForInsert(array_keys($data));
160 24
        $values = glsr(Query::class)->escValuesForInsert($data);
161 24
        if (glsr(Tables::class)->isSqlite()) {
162
            $sql = glsr(Query::class)->sql("INSERT OR IGNORE INTO {$table} {$fields} VALUES {$values}");
163
        } else {
164 24
            $sql = glsr(Query::class)->sql("INSERT IGNORE INTO {$table} {$fields} VALUES {$values}");
165
        }
166 24
        $result = $this->dbQuery($sql);
167 24
        return $result;
168
    }
169
170
    /**
171
     * Number of rows inserted. False on error.
172
     *
173
     * @return int|false
174
     */
175
    public function insertBulk(string $table, array $values, array $fields)
176
    {
177
        $this->db->insert_id = 0;
178
        $data = [];
179
        foreach ($values as $value) {
180
            $value = array_intersect_key($value, array_flip($fields)); // only keep field values
181
            if (count($value) === count($fields)) {
182
                $value = array_merge(array_flip($fields), $value); // make sure the order is correct
183
                $data[] = glsr(Query::class)->escValuesForInsert($value);
184
            }
185
        }
186
        $table = glsr(Tables::class)->table($table);
187
        $fields = glsr(Query::class)->escFieldsForInsert($fields);
188
        $values = implode(',', $data);
189
        if (glsr(Tables::class)->isSqlite()) {
190
            $sql = glsr(Query::class)->sql("INSERT OR IGNORE INTO {$table} {$fields} VALUES {$values}");
191
        } else {
192
            $sql = glsr(Query::class)->sql("INSERT IGNORE INTO {$table} {$fields} VALUES {$values}");
193
        }
194
        return $this->dbQuery($sql);
195
    }
196
197 8
    public function isMigrationNeeded(): bool
198
    {
199 8
        $postTypes = wp_count_posts(glsr()->post_type);
200 8
        $postCount = Arr::get($postTypes, 'publish');
201 8
        if (empty($postCount)) {
202 8
            return false;
203
        }
204
        $sql = glsr(Query::class)->sql("SELECT COUNT(*) FROM table|ratings WHERE is_approved = 1");
205
        return empty($this->dbGetVar($sql));
206
    }
207
208
    /**
209
     * @param mixed $result
210
     *
211
     * @return mixed
212
     */
213 47
    public function logErrors($result = null)
214
    {
215 47
        if ($this->db->last_error) {
216
            glsr_log()->error($this->db->last_error);
217
            glsr_trace();
218
        }
219 47
        return $result;
220
    }
221
222
    /**
223
     * An array of values if $single is false.
224
     * The value of the meta field if $single is true.
225
     * False for an invalid $post_id (non-numeric, zero, or negative value).
226
     * An empty string if a valid but non-existing post ID is passed.
227
     *
228
     * @return mixed
229
     */
230
    public function meta(int $postId, string $key, bool $single = true)
231
    {
232
        $key = Str::prefix($key, '_');
233
        $postId = Cast::toInt($postId);
234
        return get_post_meta($postId, $key, $single);
235
    }
236
237
    /**
238
     * The new meta field ID if a field with the given key didn't exist and was therefore added.
239
     * True on successful update. False on failure or if the value passed to the function
240
     * is the same as the one that is already in the database.
241
     *
242
     * @param mixed $value
243
     *
244
     * @return int|bool
245
     */
246
    public function metaSet(int $postId, string $key, $value)
247
    {
248
        $key = Str::prefix($key, '_');
249
        $postId = Cast::toInt($postId);
250
        return update_metadata('post', $postId, $key, $value); // update_metadata works with revisions
251
    }
252
253
    public function posts(array $args = []): array
254
    {
255
        $postTypes = get_post_types([
256
            '_builtin' => false,
257
            'exclude_from_search' => true,
258
            'public' => true,
259
            'show_in_rest' => true,
260
            'show_ui' => true,
261
        ]);
262
        $postTypes[] = 'post';
263
        $postTypes[] = 'page';
264
        $args = wp_parse_args($args, [
265
            'ignore_sticky_posts' => true,
266
            'numberposts' => 50,
267
            'order' => 'ASC',
268
            'orderby' => 'title',
269
            'post_type' => array_values($postTypes),
270
        ]);
271
        $posts = get_posts($args);
272
        $results = [];
273 8
        foreach ($posts as $post) {
274
            $title = sanitize_text_field($post->post_title);
275 8
            $results[$post->ID] = trim("{$title} ({$post->ID})");
276 8
        }
277 8
        return $results;
278 8
    }
279 8
280 8
    public function searchAssignedPosts(string $searchTerm): SearchAssignedPosts
281 8
    {
282 8
        return glsr(SearchAssignedPosts::class)->search($searchTerm);
283
    }
284
285
    public function searchAssignedUsers(string $searchTerm): SearchAssignedUsers
286
    {
287 8
        return glsr(SearchAssignedUsers::class)->search($searchTerm);
288
    }
289
290
    public function searchPosts(string $searchTerm): SearchPosts
291
    {
292
        return glsr(SearchPosts::class)->search($searchTerm);
293
    }
294
295
    public function searchUsers(string $searchTerm): SearchUsers
296
    {
297
        return glsr(SearchUsers::class)->search($searchTerm);
298
    }
299
300
    public function terms(array $args = []): array
301
    {
302
        $args = wp_parse_args($args, [
303
            'count' => false,
304
            'fields' => 'id=>name',
305
            'hide_empty' => false,
306
            'taxonomy' => glsr()->taxonomy,
307
        ]);
308
        $terms = get_terms($args);
309
        if (is_wp_error($terms)) {
310
            glsr_log()->error($terms->get_error_message());
311
            glsr_trace();
312
            return [];
313
        }
314
        return $terms;
315
    }
316
317 46
    /**
318
     * Number of rows updated. False on error.
319 46
     *
320 46
     * @return int|false
321
     */
322
    public function update(string $table, array $data, array $where)
323
    {
324 46
        $result = $this->db->update(glsr(Tables::class)->table($table), $data, $where);
325
        glsr(Query::class)->sql($this->db->last_query); // for logging use only
326
        return $this->logErrors($result);
327
    }
328
329
    public function users(array $args = []): array
330
    {
331
        $args = wp_parse_args($args, [
332
            'fields' => ['ID', 'display_name', 'user_nicename'],
333
            'number' => 50, // only get the first 50 users!
334
            'orderby' => 'display_name',
335
        ]);
336
        $results = [];
337
        $users = get_users($args);
338
        foreach ($users as $user) {
339
            $name = glsr(Sanitizer::class)->sanitizeUserName(
340
                $user->display_name,
341
                $user->user_nicename
342
            );
343
            $results[$user->ID] = $name;
344
        }
345
        return $results;
346
    }
347
348
    public function version(): string
349
    {
350
        $dbVersion = Cast::toString(get_option(glsr()->prefix.'db_version'));
351
        if (version_compare($dbVersion, Application::DB_VERSION, '>')) { // version should never be higher than plugin database version
352
            update_option(glsr()->prefix.'db_version', '1.0'); // setting it to a low version will trigger the plugin migration
353
            $dbVersion = '1.0';
354
        }
355
        return $dbVersion;
356
    }
357
}
358