PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.0.11
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.0.11
6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 5.2.2 5.3 All 153 releases
mainwp / class / class-mainwp-db.php

class-mainwp-db.php in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.0.11, at class/class-mainwp-db.php

4,137 lines 163.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MainWP Database Controller
4 *
5 * This file handles all interactions with the DB.
6 *
7 * @package MainWP/Dashboard
8 */
9
10 namespace MainWP\Dashboard;
11
12 // Exit if accessed directly.
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Class MainWP_DB
19 *
20 * @package MainWP\Dashboard
21 *
22 * @uses \MainWP\Dashboard\MainWP_DB_Base
23 */
24 class MainWP_DB extends MainWP_DB_Base { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
25
26 // phpcs:disable WordPress.DB.RestrictedFunctions, WordPress.DB.PreparedSQL.NotPrepared, Generic.Metrics.CyclomaticComplexity -- This is the only way to achieve desired results, pull request solutions appreciated.
27
28 /**
29 * Private static variable to hold the single instance of the class.
30 *
31 * @static
32 *
33 * @var mixed Default null
34 */
35 private static $instance = null;
36
37 /**
38 * Private static variable to hold the single instance.
39 *
40 * @static
41 *
42 * @var mixed Default null
43 */
44 private static $general_options = null;
45
46 /**
47 * Possible options.
48 *
49 * @var array $possible_options
50 */
51 private static $possible_options = array(
52 'plugin_upgrades',
53 'theme_upgrades',
54 'premium_upgrades',
55 'plugins',
56 'themes',
57 'dtsSync',
58 'version',
59 'sync_errors',
60 'ignored_plugins',
61 'wp_upgrades',
62 'site_info',
63 'client',
64 'signature_algo',
65 'verify_method',
66 'pubkey',
67 );
68
69 /**
70 * Create public static instance.
71 *
72 * @static
73 *
74 * @return MainWP_DB
75 */
76 public static function instance() {
77 if ( null === static::$instance ) {
78 static::$instance = new self();
79 }
80
81 static::$instance->test_connection();
82
83 return static::$instance;
84 }
85
86 /**
87 * Get wp_options database table view.
88 *
89 * @compatible function.
90 *
91 * @param array $fields Extra option fields.
92 * @param string $view_query view query.
93 *
94 * @return array wp_options view.
95 */
96 public function get_option_view( $fields = array(), $view_query = 'default' ) {
97
98 if ( ! is_array( $fields ) ) {
99 $fields = array();
100 }
101
102 $view = '(SELECT intwp.id AS wpid ';
103
104 $included_opts = array();
105
106 if ( empty( $fields ) || 'default' === $view_query || 'manage_site' === $view_query ) {
107 $view .= ',(SELECT recent_comments.value FROM ' . $this->table_name( 'wp_options' ) . ' recent_comments WHERE recent_comments.wpid = intwp.id AND recent_comments.name = "recent_comments" LIMIT 1) AS recent_comments,
108 (SELECT recent_posts.value FROM ' . $this->table_name( 'wp_options' ) . ' recent_posts WHERE recent_posts.wpid = intwp.id AND recent_posts.name = "recent_posts" LIMIT 1) AS recent_posts,
109 (SELECT recent_pages.value FROM ' . $this->table_name( 'wp_options' ) . ' recent_pages WHERE recent_pages.wpid = intwp.id AND recent_pages.name = "recent_pages" LIMIT 1) AS recent_pages,
110 (SELECT phpversion.value FROM ' . $this->table_name( 'wp_options' ) . ' phpversion WHERE phpversion.wpid = intwp.id AND phpversion.name = "phpversion" LIMIT 1) AS phpversion,
111 (SELECT added_timestamp.value FROM ' . $this->table_name( 'wp_options' ) . ' added_timestamp WHERE added_timestamp.wpid = intwp.id AND added_timestamp.name = "added_timestamp" LIMIT 1) AS added_timestamp,
112 (SELECT wp_upgrades.value FROM ' . $this->table_name( 'wp_options' ) . ' wp_upgrades WHERE wp_upgrades.wpid = intwp.id AND wp_upgrades.name = "wp_upgrades" LIMIT 1) AS wp_upgrades ';
113 $included_opts = array( 'recent_comments', 'recent_posts', 'recent_pages', 'phpversion', 'added_timestamp', 'wp_upgrades' );
114 }
115
116 if ( ! in_array( 'signature_algo', $fields ) ) {
117 $fields[] = 'signature_algo';
118 }
119
120 if ( ! in_array( 'verify_method', $fields ) ) {
121 $fields[] = 'verify_method';
122 }
123
124 if ( ! in_array( 'cust_site_icon_info', $fields, true ) ) {
125 $fields[] = 'cust_site_icon_info';
126 }
127
128 if ( is_array( $fields ) ) {
129 foreach ( $fields as $field ) {
130 if ( empty( $field ) ) {
131 continue;
132 }
133 if ( in_array( $field, $included_opts ) ) {
134 continue;
135 }
136 $view .= ', ';
137 $view .= '(SELECT ' . $this->escape( $field ) . '.value FROM ' . $this->table_name( 'wp_options' ) . ' ' . $this->escape( $field ) . ' WHERE ' . $this->escape( $field ) . '.wpid = intwp.id AND ' . $this->escape( $field ) . '.name = "' . $this->escape( $field ) . '" LIMIT 1) AS ' . $this->escape( $field );
138 }
139 }
140
141 $view .= ' FROM ' . $this->table_name( 'wp' ) . ' intwp)';
142
143 return $view;
144 }
145
146 /**
147 * Method get_wp_options_join().
148 *
149 * @param array $fields Extra option fields.
150 * @param string $view_query view query.
151 * @param array $params Additional parameters.
152
153 *
154 * NOTE: This method is used to improve the performance of wp_options view, as the old view with subquery for each field will cause performance issue when there are many sites, and this method will generate the SQL with LEFT JOIN which will be much faster than subquery.
155 * The alias of wp table must be 'wp' to make sure the LEFT JOIN works, and the alias of wp_options table will be 'owp' + key of field in fields array, and the join condition is owp.wpid = wp.id AND owp.name = field name, and the select field is owp.value AS field name.
156 *
157 * @since 6.0.8
158 *
159 * @return array wp_options view.
160 */
161 public function get_wp_options_join( $fields = array(), $view_query = 'default', $params = array() ) {
162
163 if ( ! is_array( $fields ) ) {
164 $fields = array();
165 }
166
167 if ( empty( $fields ) || 'default' === $view_query || 'manage_site' === $view_query ) {
168 $fields[] = 'recent_comments';
169 $fields[] = 'recent_posts';
170 $fields[] = 'recent_pages';
171 $fields[] = 'phpversion';
172 $fields[] = 'added_timestamp';
173 $fields[] = 'wp_upgrades';
174 }
175
176 if ( ! in_array( 'signature_algo', $fields ) ) {
177 $fields[] = 'signature_algo';
178 }
179
180 if ( ! in_array( 'verify_method', $fields ) ) {
181 $fields[] = 'verify_method';
182 }
183
184 if ( ! in_array( 'cust_site_icon_info', $fields, true ) ) {
185 $fields[] = 'cust_site_icon_info';
186 }
187
188 $fields = array_values( array_unique( array_filter( $fields ) ) );
189
190 $tbl_wp_options = $this->table_name( 'wp_options' );
191
192 $selects = array();
193 $joins = array();
194
195 foreach ( $fields as $name ) {
196 $alias = 'owp_' . preg_replace( '/[^a-z0-9_]/i', '_', $name );
197
198 // SELECT.
199 $selects[] = "{$alias}.value AS `" . $this->escape( $name ) . '`';
200
201 // JOIN.
202 $joins[] = "LEFT JOIN {$tbl_wp_options} {$alias}
203 ON {$alias}.wpid = wp.id
204 AND {$alias}.name = '" . $this->escape( $name ) . "'";
205 }
206
207 return array(
208 'selects' => implode( ', ', $selects ),
209 'joins' => implode( "\n", $joins ),
210 );
211 }
212
213 /**
214 * Method get_wp_options_view().
215 *
216 * @param array $fields Extra option fields.
217 * @param string $view_query view query.
218 * @param int $siteid Site id.
219 *
220 * @return string SQL subquery for wp_options view.
221 */
222 public function get_wp_options_view( $fields = array(), $view_query = 'default', $siteid = 0 ) {
223
224 if ( ! is_array( $fields ) ) {
225 $fields = array();
226 }
227
228 $where_site = '';
229
230 if ( ! empty( $siteid ) && is_numeric( $siteid ) ) {
231 $where_site = ' AND wpid = ' . intval( $siteid ) . ' ';
232 }
233
234 $view = '(SELECT wpid ';
235
236 $included_opts = array();
237
238 if ( empty( $fields ) || 'default' === $view_query || 'manage_site' === $view_query ) {
239 $view .= ',
240 MAX(CASE WHEN name = "recent_comments" THEN value END) AS recent_comments,
241 MAX(CASE WHEN name = "recent_posts" THEN value END) AS recent_posts,
242 MAX(CASE WHEN name = "recent_pages" THEN value END) AS recent_pages,
243 MAX(CASE WHEN name = "phpversion" THEN value END) AS phpversion,
244 MAX(CASE WHEN name = "added_timestamp" THEN value END) AS added_timestamp,
245 MAX(CASE WHEN name = "wp_upgrades" THEN value END) AS wp_upgrades ';
246 $included_opts = array( 'recent_comments', 'recent_posts', 'recent_pages', 'phpversion', 'added_timestamp', 'wp_upgrades' );
247 }
248
249 if ( ! in_array( 'signature_algo', $fields ) ) {
250 $fields[] = 'signature_algo';
251 }
252
253 if ( ! in_array( 'verify_method', $fields ) ) {
254 $fields[] = 'verify_method';
255 }
256
257 if ( ! in_array( 'cust_site_icon_info', $fields, true ) ) {
258 $fields[] = 'cust_site_icon_info';
259 }
260
261 if ( is_array( $fields ) ) {
262 foreach ( $fields as $field ) {
263 if ( empty( $field ) ) {
264 continue;
265 }
266 if ( in_array( $field, $included_opts ) ) {
267 continue;
268 }
269 $view .= ', ';
270 $view .= 'MAX(CASE WHEN name = "' . $this->escape( $field ) . '" THEN value END) AS ' . $this->escape( $field );
271
272 $included_opts[] = $this->escape( $field );
273 }
274 }
275
276 $view .= ' FROM ' . $this->table_name( 'wp_options' ) .
277 " WHERE 1 {$where_site} AND name IN ('" . implode( "','", $included_opts ) . "')
278 GROUP BY wpid ) ";
279
280 return $view;
281 }
282
283
284
285 /**
286 * Get SQL to get child sites for current user.
287 *
288 * @since 5.2.
289 * @param array $params other params.
290 *
291 * @return object|null Database query results or null on failure.
292 */
293 public function get_sql_websites_for_current_user_by_params( $params = array() ) { // phpcs:ignore -- NOSONAR - complex.
294
295 if ( ! is_array( $params ) ) {
296 $params = array();
297 }
298
299 /**
300 * The hook mainwp_get_sql_websites_by_params
301 *
302 * @since 5.5
303 */
304 $params = apply_filters( 'mainwp_get_sql_websites_by_params', $params );
305
306 $view = isset( $params['view'] ) ? $params['view'] : 'default';
307 $with_clients = isset( $params['with_clients'] ) && $params['with_clients'] ? true : false;
308
309 // legacy support.
310 $selectgroups = isset( $params['with_tags'] ) && $params['with_tags'] ? true : false;
311 $orderBy = isset( $params['orderby'] ) ? $params['orderby'] : 'wp.url';
312 $offset = isset( $params['offset'] ) ? intval( $params['offset'] ) : false;
313 $rowcount = isset( $params['rowcount'] ) && $params['rowcount'] ? true : false;
314 $extraWhere = isset( $params['where'] ) ? $params['where'] : null; // NOTE: without 'AND' at begining and ending of 'where'.
315 $for_manager = isset( $params['for_manager'] ) && $params['for_manager'] ? true : false;
316 $others_fields = isset( $params['others_fields'] ) && is_array( $params['others_fields'] ) ? $params['others_fields'] : array( 'favi_icon' );
317 $is_staging = isset( $params['is_staging'] ) && in_array( $params['is_staging'], array( 'yes', 'no' ) ) ? $params['is_staging'] : 'no';
318 $limit = isset( $params['limit'] ) ? intval( $params['limit'] ) : '';
319
320 $use_comp_subquery = ! empty( $params['use_compatible_subquery'] ) ? true : false;
321
322 $s = isset( $params['s'] ) ? $params['s'] : '';
323 $exclude = isset( $params['exclude'] ) ? wp_parse_id_list( $params['exclude'] ) : array();
324 $include = isset( $params['include'] ) ? wp_parse_id_list( $params['include'] ) : array();
325 $status = isset( $params['status'] ) ? wp_parse_list( $params['status'] ) : array();
326 $page = isset( $params['page'] ) ? intval( $params['page'] ) : false;
327 $per_page = isset( $params['per_page'] ) ? intval( $params['per_page'] ) : false;
328
329 // This parameter is used to enable caching in certain cases.
330 $_included_cache_ids = isset( $params['_included_cache_ids'] ) ? wp_parse_id_list( $params['_included_cache_ids'] ) : array();
331
332 $select_wpfields = isset( $params['select_wp_fields'] ) ? wp_parse_list( $params['select_wp_fields'] ) : '';
333 $select_syncfields = isset( $params['select_sync_fields'] ) ? wp_parse_list( $params['select_sync_fields'] ) : '';
334
335 $where = '';
336
337 if ( ! empty( $extraWhere ) ) {
338 $where .= ' AND ' . $extraWhere;
339 }
340
341 if ( ! $for_manager ) {
342 $where .= $this->get_sql_where_allow_access_sites( 'wp', $is_staging );
343 }
344
345 $connected_sql = '';
346
347 if ( is_array( $params ) && isset( $params['connected'] ) && 'yes' === $params['connected'] ) {
348 $connected_sql = ' AND wp_sync.sync_errors = "" ';
349 } elseif ( is_array( $params ) && isset( $params['connected'] ) && 'no' === $params['connected'] ) {
350 $connected_sql = ' AND wp_sync.sync_errors <> "" ';
351 }
352
353 if ( ! empty( $s ) ) {
354 $s = trim( $s );
355 // Use esc_like() to escape LIKE wildcards (%, _) then prepare() for SQL safety.
356 $like_pattern = '%' . $this->wpdb->esc_like( $s ) . '%';
357 $where .= $this->wpdb->prepare(
358 ' AND ( wp.id LIKE %s OR wp.name LIKE %s OR wp.url LIKE %s ) ',
359 $like_pattern,
360 $like_pattern,
361 $like_pattern
362 );
363 }
364
365 if ( ! empty( $exclude ) ) {
366 $where .= ' AND wp.id NOT IN (' . implode( ',', $exclude ) . ') ';
367 }
368
369 if ( ! empty( $include ) ) {
370 $where .= ' AND wp.id IN (' . implode( ',', $include ) . ') ';
371 }
372
373 if ( ! empty( $_included_cache_ids ) ) {
374 $where .= ' AND wp.id IN (' . implode( ',', $_included_cache_ids ) . ') ';
375 }
376
377 $specific_wp_fields = '';
378 if ( ! empty( $select_wpfields ) ) {
379 foreach ( $select_wpfields as $_field ) {
380 $specific_wp_fields .= 'wp.' . $this->escape( $_field ) . ',';
381 }
382 $specific_wp_fields = rtrim( $specific_wp_fields, ',' );
383 }
384
385 $specific_sync_fields = '';
386 if ( ! empty( $select_syncfields ) ) {
387 foreach ( $select_syncfields as $_field ) {
388 $specific_sync_fields .= 'wp_sync.' . $this->escape( $_field ) . ',';
389 }
390 $specific_sync_fields = rtrim( $specific_sync_fields, ',' );
391 }
392
393 // any, connected, disconnected, suspended, available_update.
394 if ( ! empty( $status ) && is_array( $status ) && ! in_array( 'any', $status ) ) {
395 $status_conds = array();
396 if ( in_array( 'available_update', $status ) ) {
397 $available_sql = " ( wp.plugin_upgrades <> '' && wp.plugin_upgrades <> '[]' ) OR ( wp.theme_upgrades <> '' && wp.theme_upgrades <> '[]' ) OR ( wp.translation_upgrades <> '' && wp.translation_upgrades <> '[]' ) OR ( wp.premium_upgrades <> '' && wp.premium_upgrades <> '[]' ) ";
398 $table_name = esc_sql( $this->table_name( 'wp_options' ) );
399 $results = $this->wpdb->get_results( "SELECT wpid FROM {$table_name} WHERE name = 'wp_upgrades' AND value <> '' AND value <> '[]'" );
400 if ( $results ) {
401 $wp_ids = array();
402 foreach ( $results as $item ) {
403 if ( ! empty( $item->wpid ) ) {
404 $wp_ids[] = $item->wpid;
405 }
406 }
407 $wp_ids = ! empty( $wp_ids ) ? array_unique( $wp_ids ) : array();
408 if ( ! empty( $wp_ids ) ) {
409 $available_sql .= ' OR wp.id IN ( ' . implode( ',', $wp_ids ) . ' )';
410 }
411 }
412 $status_conds[] = ' ( ' . $available_sql . ') ';
413 }
414
415 if ( in_array( 'connected', $status ) ) {
416 $status_conds[] = ' ( wp_sync.sync_errors = "" ) ';
417 }
418 if ( in_array( 'disconnected', $status ) ) {
419 $status_conds[] = " wp_sync.sync_errors <> '' ";
420 }
421
422 if ( in_array( 'suspended', $status ) ) {
423 $status_conds[] = ' wp.suspended = 1 ';
424 }
425
426 if ( ! empty( $status_conds ) ) {
427 $where .= ' AND ( ' . implode( ' OR ', $status_conds ) . ' ) ';
428 }
429 if ( in_array( 'unsuspended', $status ) && ! in_array( 'suspended', $status ) ) { // to sure not conflict the suspended status.
430 $where .= ' AND wp.suspended = 0 ';
431 }
432 }
433
434 if ( ! empty( $page ) && ! empty( $per_page ) ) {
435 $limit = ( $page - 1 ) * $per_page . ',' . $per_page;
436 }
437
438 if ( 'wp.url' === $orderBy ) {
439 $orderBy = "replace(replace(replace(replace(replace(wp.url, 'https://www.',''), 'http://www.',''), 'https://', ''), 'http://', ''), 'www.', '')";
440 }
441
442 $select_clients = '';
443 $join_clients = '';
444
445 if ( $with_clients ) {
446 $select_clients = ', wpclient.name as client_name ';
447 $clients_table = esc_sql( $this->table_name( 'wp_clients' ) );
448 $join_clients = " LEFT JOIN {$clients_table} wpclient ON wp.client_id = wpclient.client_id ";
449 }
450
451 $base_fields = array(
452 'wp.id',
453 'wp.url',
454 'wp.name',
455 'wp.client_id',
456 'wp.verify_certificate',
457 'wp.http_user',
458 'wp.http_pass',
459 'wp.ssl_version',
460 'wp.adminname',
461 'wp.privkey',
462 'wp.pubkey',
463 'wp.wpe',
464 'wp.is_staging',
465 'wp.force_use_ipv4',
466 'wp.siteurl',
467 'wp.suspended',
468 'wp.mainwpdir',
469 'wp.is_ignoreCoreUpdates',
470 'wp.is_ignorePluginUpdates',
471 'wp.is_ignoreThemeUpdates',
472 'wp_sync.sync_errors',
473 'wp.backup_before_upgrade',
474 'wp.userid',
475 'wp.plugins',
476 'wp.themes',
477 'wp.offline_check_result', // 1 - online, -1 offline.
478 );
479
480 $select = ' wp.*,wp_sync.* ';
481 if ( 'base_view' === $view ) {
482 $select = implode( ',', $base_fields );
483 } elseif ( 'updates_view' === $view ) {
484 $updates_fields = array(
485 'wp.plugin_upgrades',
486 'wp.theme_upgrades',
487 'wp.translation_upgrades',
488 'wp.premium_upgrades',
489 'wp.ignored_themes',
490 'wp.ignored_plugins',
491 );
492 $select = implode( ',', array_merge( $updates_fields, $base_fields ) );
493 }
494
495 $view_selects = '';
496 $view_joins = '';
497
498 if ( $use_comp_subquery ) {
499 $view_selects = ',wp_optionview.* ';
500 $view_joins = ' JOIN ' . $this->get_option_view_by( $view, $others_fields ) . ' wp_optionview ON wp.id = wp_optionview.wpid ';
501 } else {
502 $opts_view = $this->get_option_view_by_join( $view, $others_fields );
503 if ( is_array( $opts_view ) && ! empty( $opts_view['selects'] ) ) {
504 $view_selects = ',' . $opts_view['selects'];
505 $view_joins = $opts_view['joins'];
506 }
507 }
508
509 // wpgroups to fix issue for mysql 8.0, as groups will generate error syntax.
510 if ( $selectgroups ) {
511 $qry = 'SELECT ' . $select . $view_selects . ', GROUP_CONCAT(gr.name ORDER BY gr.name SEPARATOR ",") as wpgroups, GROUP_CONCAT(gr.id ORDER BY gr.name SEPARATOR ",") as wpgroupids, GROUP_CONCAT(gr.color ORDER BY gr.name SEPARATOR ",") as wpgroups_colors ' .
512 $select_clients . '
513
514 FROM ' . $this->table_name( 'wp' ) . ' wp
515 LEFT JOIN ' . $this->table_name( 'wp_group' ) . ' wpgr ON wp.id = wpgr.wpid
516 LEFT JOIN ' . $this->table_name( 'group' ) . ' gr ON wpgr.groupid = gr.id
517 ' . $join_clients . '
518 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
519 ' . $view_joins . '
520 WHERE 1 ' . $where . $connected_sql . '
521 GROUP BY wp.id, wp_sync.sync_id
522 ORDER BY ' . $orderBy;
523 } elseif ( ! empty( $specific_wp_fields ) ) { // Optimize select sites data.
524 $select = $specific_wp_fields;
525 $join_sync = '';
526 $group_by = 'wp.id';
527
528 if ( ! empty( $specific_sync_fields ) ) {
529 $select .= ',' . $specific_sync_fields;
530 $join_sync = ' JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid';
531 $group_by .= ', wp_sync.sync_id';
532 }
533 $qry = 'SELECT ' . $select . $view_selects . '
534 FROM ' . $this->table_name( 'wp' ) . ' wp
535 ' . $join_sync . ' ' . $view_joins . '
536 WHERE 1 ' . $where . $connected_sql . '
537 GROUP BY ' . $group_by . '
538 ORDER BY ' . $orderBy;
539 } else {
540 $qry = 'SELECT ' . $select . $view_selects . $select_clients . '
541 FROM ' . $this->table_name( 'wp' ) . ' wp
542 ' . $join_clients . '
543 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
544 ' . $view_joins . '
545 WHERE 1 ' . $where . $connected_sql . '
546 GROUP BY wp.id, wp_sync.sync_id
547 ORDER BY ' . $orderBy;
548 }
549
550 if ( ( false !== $offset ) && ( false !== $rowcount ) ) {
551 $qry .= ' LIMIT ' . $offset . ', ' . $rowcount;
552 } elseif ( false !== $rowcount ) {
553 $qry .= ' LIMIT ' . $rowcount;
554 } elseif ( ! empty( $limit ) ) {
555 $qry .= ' LIMIT ' . $limit;
556 } else {
557 // load all sites so check to support limit sites loading.
558 $limit_sites = ! empty( $params['limit_sites'] ) ? intval( $params['limit_sites'] ) : 0;
559 if ( ! empty( $limit_sites ) ) {
560 $current_page = (int) get_option( 'mainwp_manage_updates_limit_current_page', 0 );
561 $current_page = $current_page > 0 ? $current_page - 1 : 0;
562 $start = $current_page * $limit_sites;
563 $qry .= ' LIMIT ' . intval( $start ) . ', ' . intval( $limit_sites );
564 }
565 }
566
567 if ( ! empty( $_included_cache_ids ) ) {
568 MainWP_Logger::instance()->log_events( 'cache-metrics', sprintf( '[sql websites by params=%s]', $qry ) );
569 }
570 MainWP_Logger::instance()->log_events( 'db-queries', sprintf( '[sql websites by params=%s]', $qry ) );
571 return $qry;
572 }
573
574 /**
575 * Improve get wp_options database table view.
576 *
577 * @param array $view Option view.
578 * @param array $other_fields Extra option fields.
579 *
580 * @return array wp_options view.
581 */
582 public function get_option_view_by_join( $view = '', $other_fields = array() ) {
583
584 $default = array(
585 'recent_comments',
586 'recent_posts',
587 'recent_pages',
588 'phpversion',
589 'added_timestamp',
590 'wp_upgrades',
591 );
592
593 $fields = array();
594
595 if ( 'updates_view' === $view ) {
596 $fields = array(
597 'wp_upgrades',
598 'ignored_wp_upgrades',
599 'ignored_trans_updates',
600 );
601 } elseif ( in_array( $view, array( 'simple_view', 'base_view', 'monitor_view', 'ping_view', 'uptime_notification' ) ) ) {
602 $fields = array();
603 if ( 'monitor_view' === $view ) {
604 $fields[] = 'health_site_status';
605 }
606 } elseif ( 'custom_view' !== $view ) {
607 $fields = $default;
608 }
609
610 if ( is_array( $other_fields ) && ! empty( $other_fields ) ) {
611 $fields = array_unique( array_merge( $fields, $other_fields ) );
612 }
613
614 if ( 'custom_view' !== $view ) {
615 if ( ! in_array( 'signature_algo', $fields ) ) {
616 $fields[] = 'signature_algo';
617 }
618
619 if ( ! in_array( 'verify_method', $fields ) ) {
620 $fields[] = 'verify_method';
621 }
622 }
623
624 $fields = array_values( array_filter( $fields ) );
625
626 $tbl_wp_options = $this->table_name( 'wp_options' );
627
628 $selects = array();
629 $joins = array();
630
631 foreach ( $fields as $name ) {
632 $alias = 'owp_' . preg_replace( '/[^a-z0-9_]/i', '_', $name );
633
634 // SELECT.
635 $selects[] = "{$alias}.value AS `" . $this->escape( $name ) . '`';
636
637 // JOIN.
638 $joins[] = "LEFT JOIN {$tbl_wp_options} {$alias}
639 ON {$alias}.wpid = wp.id
640 AND {$alias}.name = '" . $this->escape( $name ) . "'";
641 }
642
643 return array(
644 'selects' => implode( ', ', $selects ),
645 'joins' => implode( "\n", $joins ),
646 );
647 }
648
649 /**
650 * Get wp_options database table view.
651 *
652 * Use new get_option_view_by_join() method to improve the performance of wp_options view, and this method is used to generate the SQL with LEFT JOIN which will be much faster than subquery, but it will return the same result as get_option_view() method, and the alias of wp table must be 'wp' to make sure the LEFT JOIN works, and the alias of wp_options table will be 'owp' + key of field in fields array, and the join condition is owp.wpid = wp.id AND owp.name = field name, and the select field is owp.value AS field name.
653 *
654 * @param array $view Option view.
655 * @param array $other_fields Extra option fields.
656 *
657 * @return array wp_options view.
658 */
659 public function get_option_view_by( $view = '', $other_fields = array() ) {
660
661 $default = array(
662 'recent_comments',
663 'recent_posts',
664 'recent_pages',
665 'phpversion',
666 'added_timestamp',
667 'wp_upgrades',
668 );
669
670 $fields = array();
671
672 if ( 'updates_view' === $view ) {
673 $fields = array(
674 'wp_upgrades',
675 'ignored_wp_upgrades',
676 'ignored_trans_updates',
677 );
678 } elseif ( in_array( $view, array( 'simple_view', 'base_view', 'monitor_view', 'ping_view', 'uptime_notification' ) ) ) {
679 $fields = array();
680 if ( 'monitor_view' === $view ) {
681 $fields[] = 'health_site_status';
682 }
683 } elseif ( 'custom_view' !== $view ) {
684 $fields = $default;
685 }
686
687 if ( is_array( $other_fields ) && ! empty( $other_fields ) ) {
688 $fields = array_unique( array_merge( $fields, $other_fields ) );
689 }
690
691 $view_query = '(SELECT wpid ';
692
693 if ( 'custom_view' !== $view ) {
694 if ( ! in_array( 'signature_algo', $fields ) ) {
695 $fields[] = 'signature_algo';
696 }
697
698 if ( ! in_array( 'verify_method', $fields ) ) {
699 $fields[] = 'verify_method';
700 }
701 }
702
703 foreach ( $fields as $field ) {
704
705 if ( empty( $field ) ) {
706 continue;
707 }
708
709 $view_query .= ', ';
710 $view_query .= 'MAX(CASE WHEN name = "' . $this->escape( $field ) . '" THEN value END) AS ' . $this->escape( $field );
711 }
712
713 $view_query .= ' FROM ' . $this->table_name( 'wp_options' ) .
714 " WHERE name IN ('" . implode( "','", $fields ) . "')
715 GROUP BY wpid ) ";
716
717 return $view_query;
718 }
719
720 /**
721 * Method get_select_groups_belong().
722 *
723 * @return string sql.
724 */
725 public function get_select_groups_belong() {
726 return ', ( SELECT GROUP_CONCAT(grbl.name ORDER BY grbl.name SEPARATOR ",")
727 FROM ' . $this->table_name( 'wp_group' ) . ' wpgrbl
728 JOIN ' . $this->table_name( 'group' ) . ' grbl ON grbl.id = wpgrbl.groupid WHERE wpgrbl.wpid = wp.id ) as wpgroups_belong,
729 ( SELECT GROUP_CONCAT(grbl.id ORDER BY grbl.name SEPARATOR ",") FROM ' . $this->table_name( 'wp_group' ) . ' wpgrbl
730 JOIN ' . $this->table_name( 'group' ) . ' grbl ON grbl.id = wpgrbl.groupid WHERE wpgrbl.wpid = wp.id ) as wpgroupids_belong,
731 ( SELECT GROUP_CONCAT(grbl.color ORDER BY grbl.name SEPARATOR ",") FROM ' . $this->table_name( 'wp_group' ) . ' wpgrbl
732 JOIN ' . $this->table_name( 'group' ) . ' grbl ON grbl.id = wpgrbl.groupid WHERE wpgrbl.wpid = wp.id ) as wpgroupcolors_belong ';
733 }
734
735 /**
736 * Get connected child sites.
737 *
738 * @param array $sites_ids Websites ids - option field.
739 *
740 * @return array $connected_sites Array of connected sites.
741 */
742 public function get_connected_websites( $sites_ids = false ) {
743 $where = $this->get_sql_where_allow_access_sites( 'wp' );
744 $wp_table = esc_sql( $this->table_name( 'wp' ) );
745 $wp_sync_table = esc_sql( $this->table_name( 'wp_sync' ) );
746
747 $sql = "SELECT wp.*,wp_sync.*
748 FROM {$wp_table} wp
749 JOIN {$wp_sync_table} wp_sync
750 ON wp.id = wp_sync.wpid
751 WHERE (wp_sync.sync_errors IS NOT NULL) AND (wp_sync.sync_errors = \"\") " .
752 $where;
753
754 $websites = $this->wpdb->get_results( $sql ); // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is fully escaped: table names via esc_sql(), WHERE fragment from validated get_sql_where_allow_access_sites()
755 $connected_sites = array();
756 if ( $websites ) {
757 foreach ( $websites as $website ) {
758
759 if ( ! empty( $sites_ids ) && ! in_array( $website->id, $sites_ids ) ) {
760 continue;
761 }
762
763 $connected_sites[] = array(
764 'id' => $website->id,
765 'name' => $website->name,
766 'url' => $website->url,
767 );
768 }
769 }
770 return $connected_sites;
771 }
772
773 /**
774 * Get disconnected child sites.
775 *
776 * @param array $sites_ids Websites ids - option field.
777 *
778 * @return array $disc_sites Array of disonnected sites.
779 */
780 public function get_disconnected_websites( $sites_ids = false ) {
781 $where = $this->get_sql_where_allow_access_sites( 'wp' );
782 $wp_table = esc_sql( $this->table_name( 'wp' ) );
783 $wp_sync_table = esc_sql( $this->table_name( 'wp_sync' ) );
784
785 $sql = "SELECT wp.*,wp_sync.*
786 FROM {$wp_table} wp
787 JOIN {$wp_sync_table} wp_sync
788 ON wp.id = wp_sync.wpid
789 WHERE (wp_sync.sync_errors IS NOT NULL) AND (wp_sync.sync_errors <> \"\") " .
790 $where;
791
792 $websites = $this->wpdb->get_results( $sql ); // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is fully escaped: table names via esc_sql(), WHERE fragment from validated get_sql_where_allow_access_sites()
793 $disc_sites = array();
794 if ( $websites ) {
795 foreach ( $websites as $website ) {
796
797 if ( ! empty( $sites_ids ) && ! in_array( $website->id, $sites_ids ) ) {
798 continue;
799 }
800
801 $disc_sites[] = array(
802 'id' => $website->id,
803 'name' => $website->name,
804 'url' => $website->url,
805 );
806 }
807 }
808 return $disc_sites;
809 }
810
811 /**
812 * Get child site count.
813 *
814 * @param null $userId Current user ID.
815 * @param bool $all_access Check if user has access to all sites.
816 *
817 * @return int Child site count.
818 *
819 * @uses \MainWP\Dashboard\MainWP_System::is_multi_user()
820 *
821 * @see get_websites_count_for_current_user() For Abilities API with status/tags/client filters.
822 * This method is intentionally simple for UI display purposes (total sites count).
823 * The two methods serve different use cases and should not be consolidated.
824 */
825 public function get_websites_count( $userId = null, $all_access = false ) {
826 static $total_sites;
827 if ( null !== $total_sites ) { // NOSONAR -- static value.
828 return $total_sites;
829 }
830 if ( ( null === $userId ) && MainWP_System::instance()->is_multi_user() ) {
831
832 /**
833 * Current user global.
834 *
835 * @global string
836 */
837 global $current_user;
838
839 $userId = $current_user->ID;
840 }
841 $where = ( null === $userId ? '' : ' wp.userid = ' . intval( $userId ) );
842 if ( ! $all_access ) {
843 $where .= $this->get_sql_where_allow_access_sites( 'wp' );
844 }
845 $table_name = esc_sql( $this->table_name( 'wp' ) );
846 $qry = "SELECT COUNT(wp.id) FROM {$table_name} wp WHERE 1 {$where}";
847
848 $total = $this->wpdb->get_var( $qry ); // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is fully escaped: table names via esc_sql(), WHERE fragment from validated get_sql_where_allow_access_sites()
849 $total_sites = $total;// NOSONAR -- static value.
850 return $total;
851 }
852
853
854 /**
855 * Get child sites stats count.
856 *
857 * @param array $params Params.
858 */
859 public function get_websites_stats_count( $params = array() ) {
860 if ( ! is_array( $params ) ) {
861 $params = array();
862 }
863
864 if ( isset( $params['all_access'] ) ) {
865 $all_access = ! empty( $params['all_access'] ) ? true : false;
866 } else {
867 $all_access = true;
868 }
869
870 $where = '';
871 if ( ! $all_access ) {
872 $where .= $this->get_sql_where_allow_access_sites( 'wp' );
873 }
874
875 $select_stats = ' ( SELECT COUNT(wp.id) as count_all ';
876 if ( ! empty( $params['count_disconnected'] ) ) {
877 $select_stats .= ',( SELECT COUNT(wp_disconnected.id) FROM ' . $this->table_name( 'wp' ) . ' wp_disconnected LEFT JOIN ' . $this->table_name( 'wp_sync' ) . ' as wp_sync ';
878 $select_stats .= ' ON wp_disconnected.id = wp_sync.wpid WHERE wp_sync.sync_errors <> "" ) as count_disconnected ';
879 }
880 if ( ! empty( $params['count_suspended'] ) ) {
881 $select_stats .= ',( SELECT COUNT(wp_suspended.id) FROM ' . $this->table_name( 'wp' ) . ' wp_suspended WHERE wp_suspended.suspended = 1 ) as count_suspended ';
882 }
883 $qry = 'SELECT * FROM ' . $select_stats;
884 $qry .= ' FROM ' . $this->table_name( 'wp' ) . ' wp ' . $where . ' ) as wp_stats ';
885
886 return $this->wpdb->get_row( $qry, ARRAY_A ); //phpcs:ignore -- ok.
887 }
888
889 /**
890 * Get Child site wp_options database table.
891 *
892 * @param array $website Child Site array.
893 * @param mixed $option Child Site wp_options table name.
894 * @param mixed $default_value default value.
895 * @param mixed $json_format Is json format value.
896 *
897 * @return string|null Database query result (as string), or null on failure.
898 */
899 public function get_website_option( $website, $option, $default_value = null, $json_format = false ) { //phpcs:ignore -- NOSONAR - complex.
900
901 if ( is_array( $website ) ) {
902 if ( isset( $website[ $option ] ) ) {
903 $value = $website[ $option ];
904 if ( true === $json_format ) {
905 $value = ! empty( $value ) ? json_decode( $value, true ) : array();
906 return is_array( $value ) ? $value : array();
907 } else {
908 return $value;
909 }
910 }
911 $site_id = $website['id'];
912 } elseif ( is_object( $website ) ) {
913 if ( property_exists( $website, $option ) ) {
914 $value = $website->{$option};
915 if ( true === $json_format ) {
916 $value = ! empty( $value ) ? json_decode( $value, true ) : array();
917 return is_array( $value ) ? $value : array();
918 } else {
919 return $value;
920 }
921 }
922 $site_id = $website->id;
923 } elseif ( is_numeric( $website ) ) { // to support $site_id = 0, for global options.
924 $site_id = $website;
925 } else {
926 return false;
927 }
928
929 $table_name = esc_sql( $this->table_name( 'wp_options' ) );
930 $value = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT value FROM {$table_name} WHERE wpid = %d AND name = %s", $site_id, $option ) );
931
932 if ( null === $value && null !== $default_value ) {
933 return $default_value;
934 }
935
936 if ( true === $json_format ) {
937 $value = ! empty( $value ) ? json_decode( $value, true ) : array();
938 return is_array( $value ) ? $value : array();
939 } else {
940 return $value;
941 }
942 }
943
944 /**
945 * Get Child site wp_options json value.
946 *
947 * @since 5.1.1
948 *
949 * @param array $website Child Site array.
950 * @param mixed $option Child Site wp_options table name.
951 * @param mixed $default_value default value.
952 *
953 * @return string|null Database query result (as string), or null on failure.
954 */
955 public function get_json_website_option( $website, $option, $default_value = null ) {
956 return $this->get_website_option( $website, $option, $default_value, true );
957 }
958
959 /**
960 * Get child site options.
961 *
962 * @param array $website Child site.
963 * @param mixed $options Child site options name.
964 *
965 * @return string|null Database query result (as string), or null on failure.
966 */
967 public function get_website_options_array( &$website, $options ) { // phpcs:ignore -- NOSONAR - complex.
968
969 if ( ! is_array( $options ) || empty( $options ) ) {
970 return array();
971 }
972
973 if ( is_array( $website ) ) {
974 $site_id = $website['id'];
975 } elseif ( is_object( $website ) ) {
976 $site_id = $website->id;
977 } elseif ( is_numeric( $website ) ) { // to support $site_id = 0 for global options.
978 $site_id = $website;
979 } else {
980 return array();
981 }
982
983 $arr_options = array();
984 $get_options = array();
985
986 foreach ( $options as $option ) {
987 if ( is_array( $website ) ) {
988 if ( isset( $website[ $option ] ) ) {
989 $arr_options[ $option ] = $website[ $option ];
990 } else {
991 $get_options[] = $option;
992 }
993 } elseif ( is_object( $website ) ) {
994 if ( property_exists( $website, $option ) ) {
995 $arr_options[ $option ] = $website->{$option};
996 } else {
997 $get_options[] = $option;
998 }
999 } else {
1000 $get_options[] = $option;
1001 }
1002 }
1003
1004 if ( empty( $get_options ) ) {
1005 return $arr_options; // all options.
1006 }
1007
1008 $table_name = esc_sql( $this->table_name( 'wp_options' ) );
1009 $placeholders = implode( ',', array_fill( 0, count( $get_options ), '%s' ) );
1010 $options_db = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT name, value FROM {$table_name} WHERE wpid = %d AND name IN ({$placeholders})", array_merge( array( $site_id ), $get_options ) ) );
1011
1012 $fill_options = array(
1013 'primary_lasttime_backup',
1014 );
1015
1016 foreach ( (array) $options_db as $o ) {
1017 $arr_options[ $o->name ] = $o->value;
1018 if ( in_array( $o->name, $fill_options ) ) {
1019 if ( is_array( $website ) ) {
1020 if ( ! isset( $website[ $o->name ] ) ) {
1021 $website[ $o->name ] = $o->value;
1022 }
1023 } elseif ( is_object( $website ) ) {
1024 if ( ! property_exists( $website, $o->name ) ) {
1025 $website->{$o->name} = $o->value;
1026 }
1027 }
1028 }
1029 }
1030 return $arr_options;
1031 }
1032
1033 /**
1034 * Update child site options.
1035 *
1036 * @param object $website Child site object.
1037 * @param mixed $option Option to update.
1038 * @param mixed $value Value to update with.
1039 */
1040 public function update_website_option( $website, $option, $value ) {
1041
1042 if ( is_numeric( $website ) ) {
1043 $site_id = intval( $website );
1044 } else {
1045 $site_id = $website->id;
1046 }
1047
1048 $table_name = esc_sql( $this->table_name( 'wp_options' ) );
1049 $rslt = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT name FROM {$table_name} WHERE wpid = %d AND name = %s", $site_id, $option ) );
1050 if ( empty( $rslt ) ) {
1051 $this->wpdb->insert(
1052 $this->table_name( 'wp_options' ),
1053 array(
1054 'wpid' => $site_id,
1055 'name' => $option,
1056 'value' => $value,
1057 )
1058 );
1059 } else {
1060 $this->wpdb->update(
1061 $this->table_name( 'wp_options' ),
1062 array( 'value' => $value ),
1063 array(
1064 'wpid' => $site_id,
1065 'name' => $option,
1066 )
1067 );
1068 }
1069 }
1070
1071
1072 /**
1073 * Remove child site options.
1074 *
1075 * @param object $website Child site object.
1076 * @param mixed $options Option to update.
1077 */
1078 public function remove_website_option( $website, $options ) {
1079
1080 if ( empty( $options ) ) {
1081 return;
1082 }
1083
1084 if ( is_numeric( $website ) ) {
1085 $site_id = intval( $website );
1086 } else {
1087 $site_id = $website->id;
1088 }
1089
1090 if ( ! is_array( $options ) ) {
1091 $options = (array) $options;
1092 }
1093
1094 $table_name = esc_sql( $this->table_name( 'wp_options' ) );
1095 foreach ( $options as $opt ) {
1096 $this->wpdb->query( $this->wpdb->prepare( "DELETE FROM {$table_name} WHERE wpid=%d AND name=%s", $site_id, $opt ) );
1097 }
1098 }
1099
1100
1101 /**
1102 * Get general Child site option.
1103 *
1104 * @param mixed $option Child Site option name.
1105 *
1106 * @return string|null Database query result (as string), or null on failure.
1107 */
1108 private function get_general_website_option( $option ) {
1109
1110 if ( null !== static::$general_options ) {
1111 if ( isset( static::$general_options[ $option ] ) ) {
1112 return static::$general_options[ $option ];
1113 }
1114 } else {
1115 static::$general_options[] = array();
1116 }
1117
1118 $table_name = esc_sql( $this->table_name( 'wp_options' ) );
1119 $val = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT value FROM {$table_name} WHERE wpid = %d AND name = %s", 0, $option ) );
1120
1121 static::$general_options[ $option ] = $val;
1122 return $val;
1123 }
1124
1125 /**
1126 * Get child site options.
1127 *
1128 * @param mixed $options Child site options name.
1129 *
1130 * @return string|null Database query result (as string), or null on failure.
1131 */
1132 public function get_general_options_array( $options ) {
1133
1134 if ( ! is_array( $options ) || empty( $options ) ) {
1135 return array();
1136 }
1137
1138 $return_options = array();
1139 if ( null !== static::$general_options ) {
1140 foreach ( static::$general_options as $opt => $val ) {
1141 if ( in_array( $opt, $options ) ) {
1142 $return_options[ $opt ] = $val;
1143 }
1144 }
1145 } else {
1146 static::$general_options[] = array();
1147 }
1148
1149 $diff_options = array();
1150 foreach ( $options as $opt ) {
1151 if ( ! isset( $return_options[ $opt ] ) ) {
1152 $diff_options[] = $opt;
1153 }
1154 }
1155
1156 if ( empty( $diff_options ) ) {
1157 return $return_options;
1158 }
1159
1160 $table_name = esc_sql( $this->table_name( 'wp_options' ) );
1161 $placeholders = implode( ',', array_fill( 0, count( $diff_options ), '%s' ) );
1162 $options_db = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT name, value FROM {$table_name} WHERE wpid = %d AND name IN ({$placeholders})", array_merge( array( 0 ), $diff_options ) ) );
1163
1164 foreach ( (array) $options_db as $o ) {
1165 $return_options[ $o->name ] = $o->value;
1166 static::$general_options[ $o->name ] = $o->value;
1167 }
1168 return $return_options;
1169 }
1170
1171 /**
1172 * Update general site options.
1173 *
1174 * @param mixed $option Option to update.
1175 * @param mixed $value Value to update with.
1176 * @param string $type_value Type values: single|array.
1177 */
1178 public function update_general_option( $option, $value, $type_value = 'single' ) {
1179
1180 if ( 'array' === $type_value ) {
1181 if ( empty( $value ) ) {
1182 $value = array();
1183 } elseif ( ! is_array( $value ) ) {
1184 return false;
1185 }
1186 $value = wp_json_encode( $value );
1187 }
1188
1189 if ( null === static::$general_options ) {
1190 static::$general_options[] = array();
1191 }
1192 static::$general_options[ $option ] = $value;
1193
1194 $table_name = esc_sql( $this->table_name( 'wp_options' ) );
1195 $rslt = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT name FROM {$table_name} WHERE wpid = %d AND name = %s", 0, $option ) );
1196
1197 if ( empty( $rslt ) ) {
1198 $this->wpdb->insert(
1199 $this->table_name( 'wp_options' ),
1200 array(
1201 'wpid' => 0,
1202 'name' => $option,
1203 'value' => $value,
1204 )
1205 );
1206 } else {
1207 $this->wpdb->update(
1208 $this->table_name( 'wp_options' ),
1209 array( 'value' => $value ),
1210 array(
1211 'wpid' => 0,
1212 'name' => $option,
1213 )
1214 );
1215 }
1216 return true;
1217 }
1218
1219 /**
1220 * Get general Child site option.
1221 *
1222 * @param mixed $opt Child Site option name.
1223 * @param string $type_value Type values: single|array.
1224 *
1225 * @return string|null Database query result (as string), or null on failure.
1226 */
1227 public function get_general_option( $opt, $type_value = 'single' ) {
1228 if ( 'single' === $type_value ) {
1229 return $this->get_general_website_option( $opt );
1230 } elseif ( 'array' === $type_value ) {
1231 $json_value = $this->get_general_website_option( $opt );
1232 if ( empty( $json_value ) ) {
1233 return array();
1234 }
1235 return json_decode( $json_value, true );
1236 }
1237 return false;
1238 }
1239
1240 /**
1241 * Get child sites by user ID.
1242 *
1243 * @param int $userid User ID.
1244 * @param bool $selectgroups Selected groups.
1245 * @param null $search_site Site search field value.
1246 * @param string $orderBy Order list by. Default: URL.
1247 *
1248 * @return array|object|null Database query results or null on failer.
1249 */
1250 public function get_websites_by_user_id( $userid, $selectgroups = false, $search_site = null, $orderBy = 'wp.url' ) {
1251 return $this->get_results_result( $this->get_sql_websites_by_user_id( $userid, $selectgroups, $search_site, $orderBy ) );
1252 }
1253
1254 /**
1255 * Get child sites.
1256 *
1257 * @return string SQL string.
1258 */
1259 public function get_sql_websites() {
1260 $where = $this->get_sql_where_allow_access_sites( 'wp' );
1261
1262 $view_selects = '';
1263 $view_joins = '';
1264
1265 $opts_view = $this->get_wp_options_join();
1266
1267 if ( is_array( $opts_view ) && ! empty( $opts_view['selects'] ) ) {
1268 $view_selects = ',' . $opts_view['selects'];
1269 $view_joins = $opts_view['joins'];
1270 }
1271
1272 return 'SELECT wp.*,wp_sync.*' . $view_selects . '
1273 FROM ' . $this->table_name( 'wp' ) . ' wp
1274 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
1275 ' . $view_joins . '
1276 WHERE 1 ' . $where . ' ORDER BY wp.id';
1277 }
1278
1279 /**
1280 * Get child sites by user id via SQL.
1281 *
1282 * @param int $userid Given user ID.
1283 * @param bool $selectgroups Selected groups. Default: false.
1284 * @param null $search_site Site search field value. Default: null.
1285 * @param string $orderBy Order list by. Default: URL.
1286 * @param bool $offset Query offset. Default: false.
1287 * @param bool $rowcount Row count. Default: falese.
1288 *
1289 * @return object|null Return database query or null on failure.
1290 *
1291 * @uses \MainWP\Dashboard\MainWP_Utility::ctype_digit()
1292 */
1293 public function get_sql_websites_by_user_id( $userid, $selectgroups = false, $search_site = null, $orderBy = 'wp.url', $offset = false, $rowcount = false ) {
1294 if ( MainWP_Utility::ctype_digit( $userid ) ) {
1295 $where = '';
1296 if ( null !== $search_site ) {
1297 $search_site = trim( $search_site );
1298 $where = ' AND (wp.name LIKE "%' . $search_site . '%" OR wp.url LIKE "%' . $search_site . '%") ';
1299 }
1300
1301 $where .= $this->get_sql_where_allow_access_sites( 'wp' );
1302
1303 $view_selects = '';
1304 $view_joins = '';
1305
1306 $opts_view = $this->get_wp_options_join();
1307
1308 if ( is_array( $opts_view ) && ! empty( $opts_view['selects'] ) ) {
1309 $view_selects = ',' . $opts_view['selects'];
1310 $view_joins = $opts_view['joins'];
1311 }
1312
1313 if ( $selectgroups ) {
1314 $qry = 'SELECT wp.*,wp_sync.*' . $view_selects . ', GROUP_CONCAT(gr.name ORDER BY gr.name SEPARATOR ",") as wpgroups, GROUP_CONCAT(gr.id ORDER BY gr.name SEPARATOR ",") as wpgroupids, GROUP_CONCAT(gr.color ORDER BY gr.name SEPARATOR ",") as wpgroups_colors
1315 FROM ' . $this->table_name( 'wp' ) . ' wp
1316 LEFT JOIN ' . $this->table_name( 'wp_group' ) . ' wpgr ON wp.id = wpgr.wpid
1317 LEFT JOIN ' . $this->table_name( 'group' ) . ' gr ON wpgr.groupid = gr.id
1318 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
1319 ' . $view_joins . '
1320 WHERE wp.userid = ' . $userid . "
1321 $where
1322 GROUP BY wp.id, wp_sync.sync_id
1323 ORDER BY " . $orderBy;
1324 } else {
1325 $qry = 'SELECT wp.*,wp_sync.*' . $view_selects . '
1326 FROM ' . $this->table_name( 'wp' ) . ' wp
1327 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
1328 ' . $view_joins . '
1329 WHERE wp.userid = ' . $userid . "
1330 $where
1331 ORDER BY " . $orderBy;
1332 }
1333
1334 if ( ( false !== $offset ) && ( false !== $rowcount ) ) {
1335 $qry .= ' LIMIT ' . $offset . ', ' . $rowcount;
1336 } elseif ( false !== $rowcount ) {
1337 $qry .= ' LIMIT ' . $rowcount;
1338 }
1339
1340 return $qry;
1341 }
1342
1343 return null;
1344 }
1345
1346 /**
1347 * Get SQL to get child sites for current user.
1348 *
1349 * @param bool $selectgroups Selected groups. Default: false.
1350 * @param null $search_site Site search field value. Default: null.
1351 * @param string $orderBy Order list by. Default: URL.
1352 * @param bool $offset Query offset. Default: false.
1353 * @param bool $rowcount Row count. Default: false.
1354 * @param null $extraWhere Extra WHERE. Default: null.
1355 * @param bool $for_manager For role manager. Default: false.
1356 * @param mixed $extra_view Extra view. Default favi_icon.
1357 * @param string $is_staging yes|no Is child site a staging site.
1358 * @param array $params other params.
1359 *
1360 * @return object|null Database query results or null on failure.
1361 *
1362 * @uses \MainWP\Dashboard\MainWP_System::is_multi_user()
1363 */
1364 public function get_sql_websites_for_current_user( // phpcs:ignore -- NOSONAR - complex.
1365 $selectgroups = false,
1366 $search_site = null,
1367 $orderBy = 'wp.url',
1368 $offset = false,
1369 $rowcount = false,
1370 $extraWhere = null,
1371 $for_manager = false,
1372 $extra_view = array( 'favi_icon' ),
1373 $is_staging = 'no',
1374 $params = array()
1375 ) {
1376
1377 /**
1378 * The hook mainwp_get_sql_websites
1379 *
1380 * @since 5.5
1381 */
1382 $params = apply_filters( 'mainwp_get_sql_websites', $params, $selectgroups, $search_site, $orderBy, $offset, $rowcount, $extraWhere, $for_manager, $extra_view, $is_staging );
1383
1384 $where = '';
1385 if ( MainWP_System::instance()->is_multi_user() ) {
1386
1387 /**
1388 * Current user global.
1389 *
1390 * @global string
1391 */
1392 global $current_user;
1393
1394 $where .= ' AND wp.userid = ' . $current_user->ID . ' ';
1395 }
1396
1397 if ( null !== $search_site ) {
1398 $search_site = trim( $search_site );
1399 // Use esc_like() to escape LIKE wildcards (%, _) then prepare() for SQL safety.
1400 $like_pattern = '%' . $this->wpdb->esc_like( $search_site ) . '%';
1401 $where .= $this->wpdb->prepare(
1402 ' AND (wp.name LIKE %s OR wp.url LIKE %s) ',
1403 $like_pattern,
1404 $like_pattern
1405 );
1406 }
1407
1408 if ( ! empty( $extraWhere ) ) {
1409 $where .= ' AND ' . $extraWhere . ' ';
1410 }
1411
1412 if ( ! $for_manager ) {
1413 $where .= $this->get_sql_where_allow_access_sites( 'wp', $is_staging );
1414 }
1415
1416 $connected_sql = '';
1417
1418 if ( is_array( $params ) && isset( $params['connected'] ) && 'yes' === $params['connected'] ) {
1419 $connected_sql = ' AND wp_sync.sync_errors = "" ';
1420 } elseif ( is_array( $params ) && isset( $params['connected'] ) && 'no' === $params['connected'] ) {
1421 $connected_sql = ' AND wp_sync.sync_errors <> "" ';
1422 }
1423
1424 $use_comp_subquery = false;
1425
1426 $limit = '';
1427 if ( $params && is_array( $params ) ) {
1428 $s = isset( $params['s'] ) ? $params['s'] : '';
1429 $exclude = isset( $params['exclude'] ) ? wp_parse_id_list( $params['exclude'] ) : array();
1430 $include = isset( $params['include'] ) ? wp_parse_id_list( $params['include'] ) : array();
1431 $status = isset( $params['status'] ) ? wp_parse_list( $params['status'] ) : array();
1432 $page = isset( $params['page'] ) ? intval( $params['page'] ) : false;
1433 $per_page = isset( $params['per_page'] ) ? intval( $params['per_page'] ) : false;
1434
1435 // This parameter is used to enable caching in certain cases.
1436 $_included_cache_ids = isset( $params['_included_cache_ids'] ) ? wp_parse_id_list( $params['_included_cache_ids'] ) : array();
1437
1438 if ( ! empty( $s ) ) {
1439 $s = trim( $s );
1440 // Note: This SQL is executed via m_query() which bypasses wpdb, so we can't
1441 // use wpdb->prepare() (its placeholders won't be resolved). Instead, escape
1442 // LIKE wildcards and the value manually. First escape LIKE special chars,
1443 // then SQL escape the result.
1444 $like_value = '%' . $this->escape( $this->wpdb->esc_like( $s ) ) . '%';
1445 $where .= " AND ( wp.id LIKE '{$like_value}' OR wp.name LIKE '{$like_value}' OR wp.url LIKE '{$like_value}' ) ";
1446 }
1447
1448 if ( ! empty( $exclude ) ) {
1449 $where .= ' AND wp.id NOT IN (' . implode( ',', $exclude ) . ') ';
1450 }
1451
1452 if ( ! empty( $include ) ) {
1453 $where .= ' AND wp.id IN (' . implode( ',', $include ) . ') ';
1454 }
1455
1456 if ( ! empty( $_included_cache_ids ) ) {
1457 $where .= ' AND wp.id IN (' . implode( ',', $_included_cache_ids ) . ') ';
1458 }
1459
1460 // any, connected, disconnected, suspended, available_update.
1461 if ( ! empty( $status ) && is_array( $status ) && ! in_array( 'any', $status ) ) {
1462 $status_conds = array();
1463 if ( in_array( 'available_update', $status ) ) {
1464 $available_sql = " ( wp.plugin_upgrades <> '' && wp.plugin_upgrades <> '[]' ) OR ( wp.theme_upgrades <> '' && wp.theme_upgrades <> '[]' ) OR ( wp.translation_upgrades <> '' && wp.translation_upgrades <> '[]' ) OR ( wp.premium_upgrades <> '' && wp.premium_upgrades <> '[]' ) ";
1465 $options_table = esc_sql( $this->table_name( 'wp_options' ) );
1466 $results = $this->wpdb->get_results( "SELECT wpid FROM {$options_table} WHERE name = 'wp_upgrades' AND value <> '' AND value <> '[]'" );
1467 if ( $results ) {
1468 $wp_ids = array();
1469 foreach ( $results as $item ) {
1470 if ( ! empty( $item->wpid ) ) {
1471 $wp_ids[] = $item->wpid;
1472 }
1473 }
1474 $wp_ids = ! empty( $wp_ids ) ? array_unique( $wp_ids ) : array();
1475 if ( ! empty( $wp_ids ) ) {
1476 $available_sql .= ' OR wp.id IN ( ' . implode( ',', $wp_ids ) . ' )';
1477 }
1478 }
1479 $status_conds[] = ' ( ' . $available_sql . ') ';
1480 }
1481
1482 if ( in_array( 'connected', $status ) ) {
1483 $status_conds[] = ' ( wp_sync.sync_errors = "" ) ';
1484 }
1485 if ( in_array( 'disconnected', $status ) ) {
1486 $status_conds[] = " wp_sync.sync_errors <> '' ";
1487 }
1488
1489 if ( in_array( 'suspended', $status ) ) {
1490 $status_conds[] = ' wp.suspended = 1 ';
1491 }
1492
1493 if ( ! empty( $status_conds ) ) {
1494 $where .= ' AND ( ' . implode( ' OR ', $status_conds ) . ' ) ';
1495 }
1496
1497 if ( in_array( 'unsuspended', $status ) && ! in_array( 'suspended', $status ) ) { // to sure not conflict the suspended status.
1498 $where .= ' AND wp.suspended = 0 ';
1499 }
1500 }
1501
1502 if ( ! empty( $page ) && ! empty( $per_page ) ) {
1503 $limit = ( $page - 1 ) * $per_page . ',' . $per_page;
1504 }
1505 $use_comp_subquery = ! empty( $params['use_compatible_subquery'] ) ? true : false;
1506 }
1507
1508 if ( 'wp.url' === $orderBy ) {
1509 $orderBy = "replace(replace(replace(replace(replace(wp.url, 'https://www.',''), 'http://www.',''), 'https://', ''), 'http://', ''), 'www.', '')";
1510 }
1511
1512 $view_selects = '';
1513 $view_joins = '';
1514
1515 if ( $use_comp_subquery ) {
1516 $view_selects = ',wp_optionview.* ';
1517 $view_joins = ' JOIN ' . $this->get_wp_options_view( $extra_view ) . ' wp_optionview ON wp.id = wp_optionview.wpid ';
1518 } else {
1519 $opts_view = $this->get_wp_options_join( $extra_view );
1520
1521 if ( is_array( $opts_view ) && ! empty( $opts_view['selects'] ) ) {
1522 $view_selects = ',' . $opts_view['selects'];
1523 $view_joins = $opts_view['joins'];
1524 }
1525 }
1526
1527 // wpgroups to fix issue for mysql 8.0, as groups will generate error syntax.
1528 if ( $selectgroups ) {
1529 $qry = 'SELECT wp.*,wp_sync.*' . $view_selects . ', GROUP_CONCAT(gr.name ORDER BY gr.name SEPARATOR ",") as wpgroups, GROUP_CONCAT(gr.id ORDER BY gr.name SEPARATOR ",") as wpgroupids, GROUP_CONCAT(gr.color ORDER BY gr.name SEPARATOR ",") as wpgroups_colors,
1530 wpclient.name as client_name
1531 FROM ' . $this->table_name( 'wp' ) . ' wp
1532 LEFT JOIN ' . $this->table_name( 'wp_group' ) . ' wpgr ON wp.id = wpgr.wpid
1533 LEFT JOIN ' . $this->table_name( 'group' ) . ' gr ON wpgr.groupid = gr.id
1534 LEFT JOIN ' . $this->table_name( 'wp_clients' ) . ' wpclient ON wp.client_id = wpclient.client_id
1535 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
1536 ' . $view_joins . '
1537 WHERE 1 ' . $where . $connected_sql . '
1538 GROUP BY wp.id, wp_sync.sync_id
1539 ORDER BY ' . $orderBy;
1540 } else {
1541 $qry = 'SELECT wp.*,wp_sync.*' . $view_selects . ', wpclient.name as client_name
1542 FROM ' . $this->table_name( 'wp' ) . ' wp
1543 LEFT JOIN ' . $this->table_name( 'wp_clients' ) . ' wpclient ON wp.client_id = wpclient.client_id
1544 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
1545 ' . $view_joins . '
1546 WHERE 1 ' . $where . $connected_sql . '
1547 GROUP BY wp.id, wp_sync.sync_id
1548 ORDER BY ' . $orderBy;
1549 }
1550
1551 if ( ( false !== $offset ) && ( false !== $rowcount ) ) {
1552 $qry .= ' LIMIT ' . $offset . ', ' . $rowcount;
1553 } elseif ( false !== $rowcount ) {
1554 $qry .= ' LIMIT ' . $rowcount;
1555 } elseif ( ! empty( $limit ) ) {
1556 $qry .= ' LIMIT ' . $limit;
1557 } else {
1558 // load all sites so check to support limit sites loading.
1559 $limit_sites = ! empty( $params['limit_sites'] ) ? intval( $params['limit_sites'] ) : 0;
1560 if ( ! empty( $limit_sites ) ) {
1561 $current_page = (int) get_option( 'mainwp_manage_updates_limit_current_page', 0 );
1562 $current_page = $current_page > 0 ? $current_page - 1 : 0;
1563 $start = $current_page * $limit_sites;
1564 $qry .= ' LIMIT ' . intval( $start ) . ', ' . intval( $limit_sites );
1565 }
1566 }
1567
1568 if ( ! empty( $_included_cache_ids ) ) {
1569 MainWP_Logger::instance()->log_events( 'cache-metrics', sprintf( '[sql websites=%s]', $qry ) );
1570 }
1571 MainWP_Logger::instance()->log_events( 'db-queries', sprintf( '[sql websites=%s]', $qry ) );
1572
1573 return $qry;
1574 }
1575
1576
1577 /**
1578 * Get SQL to get wp child sites for current user.
1579 *
1580 * @since 4.3
1581 *
1582 * @param array $params params .
1583 *
1584 * @return object|null Database query results or null on failure.
1585 */
1586 public function get_sql_wp_for_current_user( $params = array() ) { // phpcs:ignore -- NOSONAR - complex.
1587 if ( ! is_array( $params ) ) {
1588 $params = array();
1589 }
1590
1591 $selectgroups = ! empty( $params['select_groups'] ) ? true : false;
1592 $search_site = isset( $params['search_site'] ) && ! empty( $params['search_site'] ) ? $params['search_site'] : null;
1593 $orderBy = isset( $params['order_by'] ) && ! empty( $params['order_by'] ) ? $params['order_by'] : 'wp.url';
1594 $offset = isset( $params['offset'] ) ? $params['offset'] : false;
1595 $rowcount = isset( $params['row_count'] ) ? $params['row_count'] : false;
1596 $for_manager = isset( $params['for_manager'] ) ? $params['for_manager'] : false;
1597 $extraWhere = isset( $params['extra_where'] ) && ! empty( $params['extra_where'] ) ? $params['extra_where'] : null;
1598 $extra_view = isset( $params['extra_view'] ) && is_array( $params['extra_view'] ) && ! empty( $params['extra_view'] ) ? $params['extra_view'] : array( 'favi_icon' );
1599 $extra_join = isset( $params['extra_join'] ) ? $params['extra_join'] : '';
1600 $use_comp_subquery = ! empty( $params['use_compatible_subquery'] ) ? true : false;
1601
1602 $extra_select_wp_fields = isset( $params['extra_select_wp_fields'] ) && is_array( $params['extra_select_wp_fields'] ) && ! empty( $params['extra_select_wp_fields'] ) ? $params['extra_select_wp_fields'] : array();
1603 $extra_select_sql_fields = isset( $params['extra_select_sql_fields'] ) && ! empty( $params['extra_select_sql_fields'] ) ? $params['extra_select_sql_fields'] : '';
1604
1605 $is_staging = isset( $params['is_staging'] ) && 'yes' === $params['is_staging'] ? 'yes' : 'no';
1606 $count_only = isset( $params['count_only'] ) && $params['count_only'] ? true : false;
1607
1608 $where = '';
1609
1610 if ( null !== $search_site ) {
1611 $search_site = trim( $search_site );
1612 // Use esc_like() to escape LIKE wildcards (%, _) then prepare() for SQL safety.
1613 $like_pattern = '%' . $this->wpdb->esc_like( $search_site ) . '%';
1614 $where .= $this->wpdb->prepare(
1615 ' AND (wp.name LIKE %s OR wp.url LIKE %s) ',
1616 $like_pattern,
1617 $like_pattern
1618 );
1619 }
1620
1621 if ( null !== $extraWhere ) {
1622 $where .= ' AND ' . $extraWhere;
1623 }
1624
1625 if ( ! $for_manager ) {
1626 $where .= $this->get_sql_where_allow_access_sites( 'wp', $is_staging );
1627 }
1628
1629 if ( 'wp.url' === $orderBy ) {
1630 $orderBy = "replace(replace(replace(replace(replace(wp.url, 'https://www.',''), 'http://www.',''), 'https://', ''), 'http://', ''), 'www.', '')";
1631 }
1632
1633 $select_wp_fields = $this->get_sql_select_wp_valid_fields( $extra_select_wp_fields );
1634
1635 if ( ! empty( $extra_select_sql_fields ) ) {
1636 $extra_select_sql_fields = ',' . $extra_select_sql_fields;
1637 }
1638
1639 $view_selects = '';
1640 $view_joins = '';
1641
1642 if ( $use_comp_subquery ) {
1643 $view_selects = ',wp_optionview.* ';
1644 $view_joins = ' JOIN ' . $this->get_wp_options_view( $extra_view ) . ' wp_optionview ON wp.id = wp_optionview.wpid ';
1645 } else {
1646 $opts_view = $this->get_wp_options_join( $extra_view );
1647 if ( is_array( $opts_view ) && ! empty( $opts_view['selects'] ) ) {
1648 $view_selects = ',' . $opts_view['selects'];
1649 $view_joins = $opts_view['joins'];
1650 }
1651 }
1652
1653 // wpgroups to fix issue for mysql 8.0, as groups will generate error syntax.
1654 if ( $selectgroups ) {
1655 if ( $count_only ) {
1656 $select = ' COUNT(DISTINCT(wp.id)) ';
1657 } else {
1658 $select = $select_wp_fields . '
1659 ' . $extra_select_sql_fields . '
1660 ,wp_sync.sync_errors' . $view_selects . ', GROUP_CONCAT(gr.name ORDER BY gr.name SEPARATOR ",") as wpgroups, GROUP_CONCAT(gr.id ORDER BY gr.name SEPARATOR ",") as wpgroupids, GROUP_CONCAT(gr.color ORDER BY gr.name SEPARATOR ",") as wpgroups_colors, wpclient.name as client_name ';
1661 }
1662 $qry = 'SELECT ' . $select . '
1663 FROM ' . $this->table_name( 'wp' ) . ' wp
1664 LEFT JOIN ' . $this->table_name( 'wp_group' ) . ' wpgr ON wp.id = wpgr.wpid
1665 LEFT JOIN ' . $this->table_name( 'group' ) . ' gr ON wpgr.groupid = gr.id
1666 LEFT JOIN ' . $this->table_name( 'wp_clients' ) . ' wpclient ON wp.client_id = wpclient.client_id
1667 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
1668 ' . $view_joins . '
1669 ' . $extra_join . '
1670 WHERE 1 ' . $where;
1671 if ( ! $count_only ) {
1672 $qry .= ' GROUP BY wp.id, wp_sync.sync_id';
1673 }
1674 $qry .= ' ORDER BY ' . $orderBy;
1675 } else {
1676 if ( $count_only ) {
1677 $select = ' COUNT(DISTINCT(wp.id)) ';
1678 } else {
1679 $select = $select_wp_fields . '
1680 ' . $extra_select_sql_fields . '
1681 ,wp_sync.sync_errors' . $view_selects . ', wpclient.name as client_name ';
1682 }
1683 $qry = 'SELECT ' . $select . '
1684 FROM ' . $this->table_name( 'wp' ) . ' wp
1685 LEFT JOIN ' . $this->table_name( 'wp_clients' ) . ' wpclient ON wp.client_id = wpclient.client_id
1686 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
1687 ' . $view_joins . '
1688 ' . $extra_join . '
1689 WHERE 1 ' . $where;
1690 if ( ! $count_only ) {
1691 $qry .= ' GROUP BY wp.id, wp_sync.sync_id';
1692 }
1693 $qry .= ' ORDER BY ' . $orderBy;
1694 }
1695
1696 if ( ! $count_only ) {
1697 if ( ( false !== $offset ) && ( false !== $rowcount ) ) {
1698 $qry .= ' LIMIT ' . intval( $offset ) . ', ' . intval( $rowcount );
1699 } elseif ( false !== $rowcount ) {
1700 $qry .= ' LIMIT ' . intval( $rowcount );
1701 }
1702 }
1703 return $qry;
1704 }
1705 /**
1706 * Get SQL select websites fields.
1707 *
1708 * @since 4.3
1709 *
1710 * @param array $other_fields extra select wp fields .
1711 *
1712 * @return string sql string.
1713 */
1714 public function get_sql_select_wp_valid_fields( $other_fields = array() ) {
1715
1716 $allow_other_fields = array(
1717 'offline_checks_last',
1718 'offline_check_result', // 1 - online, -1 offline.
1719 'http_response_code',
1720 'disable_health_check',
1721 'health_threshold',
1722 'note',
1723 'statsUpdate',
1724 'directories',
1725 'plugin_upgrades',
1726 'theme_upgrades',
1727 'translation_upgrades',
1728 'premium_upgrades',
1729 'securityIssues',
1730 'themes',
1731 'ignored_themes',
1732 'plugins',
1733 'ignored_plugins',
1734 'users',
1735 'categories',
1736 'pluginDir',
1737 'automatic_update',
1738 'backup_before_upgrade',
1739 'mainwpdir',
1740 'is_ignoreCoreUpdates',
1741 'is_ignorePluginUpdates',
1742 'is_ignoreThemeUpdates',
1743 'verify_certificate',
1744 'force_use_ipv4',
1745 'ssl_version',
1746 'http_user',
1747 'http_pass',
1748 'wpe',
1749 'is_staging',
1750 'client_id',
1751 );
1752
1753 $default_fields = array( 'id', 'url', 'name', 'adminname', 'verify_certificate', 'ssl_version', 'http_user', 'http_pass', 'suspended' );
1754
1755 $select = ' ';
1756
1757 foreach ( $default_fields as $field ) {
1758 $select .= 'wp.' . $this->escape( $field ) . ',';
1759 }
1760 foreach ( $other_fields as $field ) {
1761 if ( ! in_array( $field, $allow_other_fields ) ) {
1762 continue;
1763 }
1764 if ( in_array( $field, $default_fields ) ) {
1765 continue;
1766 }
1767 $select .= 'wp.' . $this->escape( $field ) . ',';
1768 }
1769 $select = rtrim( $select, ',' );
1770 return $select;
1771 }
1772
1773 /**
1774 * Get child sites for current user.
1775 *
1776 * @param array $params to get sites. Default: array().
1777 *
1778 * @return array Results or null on failure.
1779 *
1780 * @uses \MainWP\Dashboard\MainWP_Utility::map_site()
1781 */
1782 public function get_websites_for_current_user( $params = array() ) { // phpcs:ignore -- NOSONAR - complex.
1783 if ( ! is_array( $params ) ) {
1784 $params = array();
1785 }
1786
1787 $selectgroups = isset( $params['selectgroups'] ) ? $params['selectgroups'] : false;
1788 $search_site = isset( $params['search_site'] ) ? $params['search_site'] : null;
1789 $orderBy = isset( $params['order_by'] ) ? $params['order_by'] : 'wp.url';
1790 $offset = isset( $params['offset'] ) ? $params['offset'] : false;
1791 $rowcount = isset( $params['rowcount'] ) ? $params['rowcount'] : false;
1792 $extraWhere = isset( $params['where'] ) ? $params['where'] : null;
1793 $extra_view = isset( $params['extra_view'] ) && is_array( $params['extra_view'] ) ? $params['extra_view'] : array( 'favi_icon' );
1794 $is_staging = isset( $params['is_staging'] ) ? $params['is_staging'] : 'no';
1795 $full_data = isset( $params['full_data'] ) && $params['full_data'] && ( 'no' !== $params['full_data'] ) ? true : false;
1796 $select_data = isset( $params['select_data'] ) && is_array( $params['select_data'] ) ? $params['select_data'] : false;
1797 $format = isset( $params['format'] ) ? $params['format'] : '';
1798 $clients = isset( $params['client'] ) ? $params['client'] : '';
1799 $fields = isset( $params['fields'] ) && is_array( $params['fields'] ) ? $params['fields'] : array();
1800
1801 $for_manager = isset( $params['no_perm_check'] ) && true === $params['no_perm_check'];
1802
1803 $urlsWhere = '';
1804
1805 if ( isset( $params['urls'] ) && ! empty( $params['urls'] ) ) {
1806 $urls = explode( ';', $params['urls'] );
1807 foreach ( $urls as $url ) {
1808 $url = str_replace( array( 'https://www.', 'http://www.', 'https://', 'http://', 'www.' ), array( '', '', '', '', '' ), $url );
1809 if ( '/' !== substr( $url, - 1 ) ) {
1810 $url .= '/';
1811 }
1812 $urlsWhere .= '"' . $this->escape( $url ) . '", ';
1813 }
1814 $urlsWhere = rtrim( $urlsWhere, ', ' );
1815 }
1816
1817 if ( ! empty( $urlsWhere ) ) {
1818 $urlsWhere = " ( replace(replace(replace(replace(replace(wp.url, 'https://www.',''), 'http://www.',''), 'https://', ''), 'http://', ''), 'www.', '') IN ( " . $urlsWhere . ') ) ';
1819
1820 if ( empty( $extraWhere ) ) {
1821 $extraWhere = $urlsWhere;
1822 } else {
1823 $extraWhere = $extraWhere . ' AND ' . $urlsWhere;
1824 }
1825 }
1826
1827 $clientWhere = '';
1828 if ( ! empty( $clients ) ) {
1829 $clients = explode( ';', $clients );
1830 foreach ( $clients as $client ) {
1831 if ( is_numeric( $client ) ) {
1832 $clientWhere .= intval( $client ) . ', ';
1833 }
1834 }
1835 $clientWhere = rtrim( $clientWhere, ', ' );
1836 }
1837
1838 if ( ! empty( $clientWhere ) ) {
1839 $clientWhere = ' ( wp.client_id IN ( ' . $clientWhere . ') ) ';
1840 if ( empty( $extraWhere ) ) {
1841 $extraWhere = $clientWhere;
1842 } else {
1843 $extraWhere = $extraWhere . ' AND ' . $clientWhere;
1844 }
1845 }
1846
1847 $args = array(
1848 's' => isset( $params['s'] ) ? $params['s'] : '',
1849 'exclude' => isset( $params['exclude'] ) && ! empty( $params['exclude'] ) ? wp_parse_id_list( $params['exclude'] ) : array(),
1850 'include' => isset( $params['include'] ) && ! empty( $params['include'] ) ? wp_parse_id_list( $params['include'] ) : array(),
1851 'status' => isset( $params['status'] ) && ! empty( $params['status'] ) ? wp_parse_list( $params['status'] ) : '',
1852 'page' => isset( $params['paged'] ) ? intval( $params['paged'] ) : false,
1853 'per_page' => isset( $params['items_per_page'] ) ? intval( $params['items_per_page'] ) : false,
1854 );
1855
1856 $data = array( 'id', 'url', 'name', 'client_id' );
1857
1858 if ( $full_data ) {
1859 $data = array(
1860 'id',
1861 'url',
1862 'name',
1863 'offline_checks_last',
1864 'offline_check_result', // 1 - online, -1 offline.
1865 'http_response_code',
1866 'disable_health_check',
1867 'health_threshold',
1868 'note',
1869 'dbsize',
1870 'plugin_upgrades',
1871 'theme_upgrades',
1872 'translation_upgrades',
1873 'securityIssues',
1874 'themes',
1875 'plugins',
1876 'automatic_update',
1877 'sync_errors',
1878 'dtsAutomaticSync',
1879 'dtsAutomaticSyncStart',
1880 'dtsSync',
1881 'dtsSyncStart',
1882 'last_post_gmt',
1883 'health_value',
1884 'phpversion',
1885 'wp_upgrades',
1886 'security_stats',
1887 'client_id',
1888 'adminname',
1889 'privkey',
1890 'http_user',
1891 'http_pass',
1892 'ssl_version',
1893 'signature_algo',
1894 'verify_method',
1895 'verify_certificate',
1896 'suspended',
1897 );
1898
1899 if ( ! in_array( 'security_stats', $extra_view ) ) {
1900 $extra_view[] = 'security_stats';
1901 }
1902 }
1903
1904 if ( ! empty( $select_data ) && is_array( $select_data ) ) {
1905 $data = $select_data;
1906 }
1907
1908 if ( $selectgroups ) {
1909 $data[] = 'wpgroups';
1910 $data[] = 'wpgroupids';
1911 }
1912
1913 if ( ! empty( $fields ) ) {
1914 $data = array_unique( array_merge( $fields, $data ) ); // to prevent difference fields name.
1915 }
1916
1917 $dbwebsites = array();
1918
1919 $sql = $this->get_sql_websites_for_current_user( $selectgroups, $search_site, $orderBy, $offset, $rowcount, $extraWhere, $for_manager, $extra_view, $is_staging, $args );
1920 $websites = $this->query( $sql );
1921
1922 while ( $websites && ( $website = static::fetch_object( $websites ) ) ) {
1923
1924 $obj_data = MainWP_Utility::map_site( $website, $data );
1925
1926 if ( $full_data ) {
1927 $sum_upgrades = 0;
1928 if ( '' !== $obj_data->plugin_upgrades ) {
1929 $plugin_upgrades = json_decode( $obj_data->plugin_upgrades, true );
1930 if ( is_array( $plugin_upgrades ) ) {
1931 $sum_upgrades += count( $plugin_upgrades );
1932 }
1933 }
1934
1935 if ( '' !== $obj_data->theme_upgrades ) {
1936 $theme_upgrades = json_decode( $obj_data->theme_upgrades, true );
1937 if ( is_array( $theme_upgrades ) ) {
1938 $sum_upgrades += count( $theme_upgrades );
1939 }
1940 }
1941
1942 if ( '' !== $obj_data->wp_upgrades ) {
1943 $wp_upgrades = json_decode( $obj_data->wp_upgrades, true );
1944 if ( is_array( $wp_upgrades ) ) {
1945 $sum_upgrades += count( $wp_upgrades );
1946 }
1947 }
1948 $obj_data->sum_of_upgrades = $sum_upgrades;
1949 }
1950
1951 if ( 'array' === $format ) {
1952 $dbwebsites[] = $obj_data;
1953 } else {
1954 $dbwebsites[ $website->id ] = $obj_data;
1955 }
1956 }
1957 static::free_result( $websites );
1958 return $dbwebsites;
1959 }
1960
1961 /**
1962 * Get count of child sites for the current user with filters.
1963 *
1964 * This method is optimized for the Abilities API to return site counts
1965 * with filtering support for status, tags (groups), and client_id.
1966 *
1967 * IMPORTANT: This method is intended ONLY for Abilities API consumers.
1968 * For legacy UI code paths (e.g., admin dashboard widgets, site count displays),
1969 * use get_websites_count() instead. The two methods serve different purposes:
1970 * - get_websites_count(): Simple total count for UI display (cached, no filters)
1971 * - get_websites_count_for_current_user(): Filtered count for API pagination
1972 *
1973 * @since 5.3
1974 *
1975 * @param array $params Filter parameters:
1976 * - status (string): 'connected', 'disconnected', 'suspended'
1977 * - tags (array): Array of tag/group IDs to filter by
1978 * - client_id (int): Client ID to filter by.
1979 *
1980 * @return int Count of sites matching the filters.
1981 */
1982 public function get_websites_count_for_current_user( $params = array() ) { //phpcs:ignore -- NOSONAR - complex.
1983
1984 if ( ! is_array( $params ) ) {
1985 $params = array();
1986 }
1987
1988 $status = isset( $params['status'] ) ? $params['status'] : '';
1989 $tags = isset( $params['tags'] ) && is_array( $params['tags'] ) ? $params['tags'] : array();
1990 $client_id = isset( $params['client_id'] ) ? intval( $params['client_id'] ) : 0;
1991 $s = isset( $params['s'] ) ? $params['s'] : '';
1992
1993 // Validate status value (defense-in-depth for direct callers outside Abilities API).
1994 $valid_statuses = array( 'connected', 'disconnected', 'suspended', '' );
1995 if ( ! in_array( $status, $valid_statuses, true ) ) {
1996 $status = '';
1997 }
1998
1999 $where = '';
2000 $sql_params = array();
2001
2002 // Multi-user support: filter by current user.
2003 if ( MainWP_System::instance()->is_multi_user() ) {
2004 global $current_user;
2005 $where .= ' AND wp.userid = %d ';
2006 $sql_params[] = (int) $current_user->ID;
2007 }
2008
2009 // Access control for sites.
2010 $where .= $this->get_sql_where_allow_access_sites( 'wp', 'no' );
2011
2012 // Status filtering: connected, disconnected, suspended.
2013 if ( ! empty( $status ) ) {
2014 switch ( $status ) {
2015 case 'connected':
2016 $where .= ' AND wp_sync.sync_errors = "" AND wp.suspended = 0 ';
2017 break;
2018 case 'disconnected':
2019 $where .= ' AND wp_sync.sync_errors <> "" ';
2020 break;
2021 case 'suspended':
2022 $where .= ' AND wp.suspended = 1 ';
2023 break;
2024 default:
2025 // No additional filtering.
2026 break;
2027 }
2028 }
2029
2030 // Client ID filtering.
2031 if ( ! empty( $client_id ) ) {
2032 $where .= ' AND wp.client_id = %d ';
2033 $sql_params[] = (int) $client_id;
2034 }
2035
2036 // Search filtering.
2037 if ( ! empty( $s ) ) {
2038 $s = trim( $s );
2039 $like_pattern = '%' . $this->wpdb->esc_like( $s ) . '%';
2040 $where .= $this->wpdb->prepare(
2041 ' AND ( wp.id LIKE %s OR wp.name LIKE %s OR wp.url LIKE %s ) ',
2042 $like_pattern,
2043 $like_pattern,
2044 $like_pattern
2045 );
2046 }
2047
2048 // Tags (groups) filtering.
2049 $join_group = '';
2050 if ( ! empty( $tags ) ) {
2051 $tags = array_map( 'intval', $tags );
2052 $tags = array_filter(
2053 $tags,
2054 function ( $id ) {
2055 return $id > 0;
2056 }
2057 );
2058 if ( ! empty( $tags ) ) {
2059 $join_group = ' JOIN ' . $this->table_name( 'wp_group' ) . ' wpgroup ON wp.id = wpgroup.wpid ';
2060 $placeholders = implode( ', ', array_fill( 0, count( $tags ), '%d' ) );
2061 $where .= " AND wpgroup.groupid IN ( $placeholders ) ";
2062 $sql_params = array_merge( $sql_params, $tags );
2063 }
2064 }
2065
2066 $qry = 'SELECT COUNT(DISTINCT wp.id) FROM ' . $this->table_name( 'wp' ) . ' wp ' .
2067 'JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid ' .
2068 $join_group .
2069 'WHERE 1 ' . $where;
2070
2071 // Only call prepare() when we have placeholders.
2072 $result = $sql_params
2073 ? $this->wpdb->get_var( $this->wpdb->prepare( $qry, $sql_params ) ) // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- We have already prepared the query with the parameters.
2074 : $this->wpdb->get_var( $qry ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- We have already prepared the query with the parameters.
2075
2076 return (int) $result;
2077 }
2078
2079 /**
2080 * Get the child sites the current user has searched for.
2081 *
2082 * @param array $params Query parameters.
2083 *
2084 * @return boolean|null $qry Database query results or null on failure.
2085 *
2086 * @uses \MainWP\Dashboard\MainWP_System::is_multi_user()
2087 */
2088 public function get_sql_search_websites_for_current_user( $params ) { // phpcs:ignore -- NOSONAR - complex.
2089
2090 if ( ! is_array( $params ) ) {
2091 $params = array();
2092 }
2093
2094 $view = isset( $params['view'] ) ? $params['view'] : 'default'; // must be default.
2095 $selectgroups = isset( $params['selectgroups'] ) && $params['selectgroups'] ? true : false;
2096 $search_site = isset( $params['search'] ) ? trim( $params['search'] ) : null;
2097 $orderBy = isset( $params['orderby'] ) ? $params['orderby'] : 'wp.url';
2098 $offset = isset( $params['offset'] ) ? intval( $params['offset'] ) : false;
2099 $rowcount = isset( $params['rowcount'] ) ? intval( $params['rowcount'] ) : false;
2100 $extraWhere = isset( $params['extra_where'] ) ? $params['extra_where'] : null; // without AND prefix.
2101 $for_manager = isset( $params['for_manager'] ) && $params['for_manager'] ? true : false;
2102 $extra_view = isset( $params['extra_view'] ) ? $params['extra_view'] : array( 'favi_icon' );
2103 $is_staging = isset( $params['is_staging'] ) && 'yes' === $params['is_staging'] ? 'yes' : 'no';
2104 $is_count = isset( $params['count_only'] ) && $params['count_only'] ? true : false;
2105 $group_ids = isset( $params['group_id'] ) && ! empty( $params['group_id'] ) ? $params['group_id'] : array();
2106 $client_ids = isset( $params['client_id'] ) && ! empty( $params['client_id'] ) ? $params['client_id'] : array();
2107 $group_logic = ( isset( $params['group_logic'] ) && 'and' === $params['group_logic'] ) ? 'and' : 'or';
2108 $is_not = isset( $params['isnot'] ) && ! empty( $params['isnot'] ) ? true : false;
2109 $selected_sites = isset( $params['selected_sites'] ) ? $params['selected_sites'] : array();
2110
2111 // This parameter is used to enable caching in certain cases.
2112 $_included_cache_ids = isset( $params['_included_cache_ids'] ) ? wp_parse_id_list( $params['_included_cache_ids'] ) : array();
2113 $where_cache_ids = '';
2114
2115 if ( ! is_array( $group_ids ) ) {
2116 $group_ids = array();
2117 }
2118
2119 // valid group ids.
2120 $group_ids = array_filter(
2121 $group_ids,
2122 function ( $e ) {
2123 if ( 'nogroups' === $e ) {
2124 return true;
2125 }
2126 return ( is_numeric( $e ) && 0 < $e ) ? true : false;
2127 }
2128 );
2129
2130 if ( ! is_array( $client_ids ) ) {
2131 $client_ids = array();
2132 }
2133
2134 // valid group ids.
2135 $client_ids = array_filter(
2136 $client_ids,
2137 function ( $e ) {
2138 if ( 'noclients' === $e ) {
2139 return true;
2140 }
2141 return is_numeric( $e ) && ! empty( $e ) ? true : false; // to valid client ids.
2142 }
2143 );
2144
2145 if ( $selectgroups ) {
2146 $staging_group = get_option( 'mainwp_stagingsites_group_id' );
2147 if ( $staging_group && in_array( $staging_group, $group_ids ) ) {
2148 if ( empty( $group_ids ) ) {
2149 $is_staging = 'yes';
2150 } else {
2151 $is_staging = 'nocheckstaging';
2152 }
2153 }
2154 }
2155
2156 if ( ! is_array( $selected_sites ) ) {
2157 $selected_sites = array();
2158 }
2159 $selected_sites = MainWP_Utility::array_numeric_filter( $selected_sites );
2160
2161 $where = '';
2162 if ( MainWP_System::instance()->is_multi_user() ) {
2163
2164 /**
2165 * Current user global.
2166 *
2167 * @global string
2168 */
2169 global $current_user;
2170
2171 $where .= ' AND wp.userid = ' . $current_user->ID . ' ';
2172 }
2173
2174 if ( ! empty( $_included_cache_ids ) ) {
2175 $where_cache_ids .= ' AND wp.id IN (' . implode( ',', $_included_cache_ids ) . ') ';
2176 } else {
2177 if ( ! empty( $selected_sites ) ) {
2178 $where .= ' AND wp.id IN (' . implode( ',', $selected_sites ) . ') ';
2179 }
2180
2181 if ( ! empty( $extraWhere ) ) {
2182 $where .= ' AND ' . $extraWhere;
2183 }
2184 }
2185
2186 // Search filtering.
2187 if ( null !== $search_site && '' !== $search_site ) {
2188 // Escape LIKE wildcards first (%, _, \).
2189 // Note: Cannot use WordPress escaping functions (esc_like, esc_sql, prepare) because
2190 // WordPress 6.2+ creates placeholder tokens that are only substituted during query execution.
2191 // Since this method returns a SQL string for later execution, tokens would never be substituted.
2192 // We use direct mysqli_real_escape_string() which is the same underlying function WordPress uses.
2193 $search_escaped = str_replace( array( '\\', '%', '_' ), array( '\\\\', '\\%', '\\_' ), $search_site );
2194 $like_pattern = '%' . $search_escaped . '%';
2195
2196 // SQL escape using direct mysqli function to bypass WordPress placeholder tokens.
2197 if ( $this->wpdb->dbh instanceof \mysqli ) {
2198 $like_pattern_escaped = mysqli_real_escape_string( $this->wpdb->dbh, $like_pattern );
2199 } else {
2200 // Fallback: reject if no mysqli connection available.
2201 $like_pattern_escaped = '';
2202 }
2203
2204 if ( '' !== $like_pattern_escaped ) {
2205 $where .= " AND (wp.name LIKE '" . $like_pattern_escaped . "' OR wp.url LIKE '" . $like_pattern_escaped . "') ";
2206 }
2207 }
2208
2209 $staging_enabled = is_plugin_active( 'mainwp-staging-extension/mainwp-staging-extension.php' ) || is_plugin_active( 'mainwp-timecapsule-extension/mainwp-timecapsule-extension.php' );
2210 if ( ! $staging_enabled ) {
2211 $is_staging = 'no';
2212 }
2213
2214 if ( ! $for_manager ) {
2215 $where .= $this->get_sql_where_allow_access_sites( 'wp', $is_staging );
2216 }
2217
2218 if ( $is_count ) {
2219 $orderBy = '';
2220 } elseif ( 'wp.url' === $orderBy ) {
2221 $orderBy = "replace(replace(replace(replace(replace(wp.url, 'https://www.',''), 'http://www.',''), 'https://', ''), 'http://', ''), 'www.', '')";
2222 }
2223
2224 if ( ! empty( $orderBy ) ) {
2225 $orderBy = ' ORDER BY ' . $orderBy;
2226 }
2227
2228 $join_group = '';
2229 $where_group = '';
2230 $having_group = '';
2231
2232 if ( in_array( 'nogroups', $group_ids ) ) {
2233 $join_group = ' LEFT JOIN ' . $this->table_name( 'wp_group' ) . ' wpgroup ON wp.id = wpgroup.wpid ';
2234 $group_ids = array_filter(
2235 $group_ids,
2236 function ( $e ) {
2237 return 'nogroups' !== $e;
2238 }
2239 );
2240 if ( ! empty( $group_ids ) ) {
2241 $groups = implode( ',', $group_ids );
2242 $groups_count = count( $group_ids );
2243 if ( $is_not ) {
2244 $where_group = ' AND wpgroup.groupid IS NOT NULL ';
2245 if ( 'and' === $group_logic ) {
2246 $sub_select_match_all = ' SELECT wpand.id FROM ' . $this->table_name( 'wp' ) . ' wpand JOIN ' . $this->table_name( 'wp_group' ) . ' wpgroup_and ON wpand.id = wpgroup_and.wpid WHERE wpgroup_and.groupid IN (' . $groups . ') GROUP BY wpand.id HAVING COUNT(DISTINCT wpgroup_and.groupid) = ' . $groups_count . ' ';
2247 $where_group .= ' AND wp.id NOT IN ( ' . $sub_select_match_all . ' ) ';
2248 } else {
2249 $sub_select_is_not = ' SELECT wp_or.id FROM ' . $this->table_name( 'wp' ) . ' wp_or JOIN ' . $this->table_name( 'wp_group' ) . ' wpgroup_or ON wp_or.id = wpgroup_or.wpid WHERE wpgroup_or.groupid IN (' . $groups . ') ';
2250 $where_group .= ' AND wp.id NOT IN ( ' . $sub_select_is_not . ' ) ';
2251 }
2252 } elseif ( 'and' === $group_logic ) {
2253 $where_group = ' AND 1 = 0 ';
2254 } else {
2255 $where_group = ' AND ( wpgroup.groupid IS NULL OR wpgroup.groupid IN (' . $groups . ') ) ';
2256 }
2257 } elseif ( $is_not ) {
2258 $where_group = ' AND wpgroup.groupid IS NOT NULL ';
2259 } else {
2260 $where_group = ' AND wpgroup.groupid IS NULL ';
2261 }
2262 } elseif ( $group_ids ) {
2263 $groups = implode( ',', $group_ids );
2264 $groups_count = count( $group_ids );
2265 if ( $is_not ) {
2266 $join_group = ' LEFT JOIN ' . $this->table_name( 'wp_group' ) . ' wpgroup ON wp.id = wpgroup.wpid ';
2267 $where_group = '';
2268 if ( 'and' === $group_logic ) {
2269 $sub_select_match_all = ' SELECT wpand.id FROM ' . $this->table_name( 'wp' ) . ' wpand JOIN ' . $this->table_name( 'wp_group' ) . ' wpgroup_and ON wpand.id = wpgroup_and.wpid WHERE wpgroup_and.groupid IN (' . $groups . ') GROUP BY wpand.id HAVING COUNT(DISTINCT wpgroup_and.groupid) = ' . $groups_count . ' ';
2270 $where_group .= ' AND wp.id NOT IN ( ' . $sub_select_match_all . ' ) ';
2271 } else {
2272 $sub_select_is_not = ' SELECT wp_or.id FROM ' . $this->table_name( 'wp' ) . ' wp_or JOIN ' . $this->table_name( 'wp_group' ) . ' wpgroup_or ON wp_or.id = wpgroup_or.wpid WHERE wpgroup_or.groupid IN (' . $groups . ') ';
2273 $where_group .= ' AND wp.id NOT IN ( ' . $sub_select_is_not . ' ) ';
2274 }
2275 } else {
2276 $join_group = ' JOIN ' . $this->table_name( 'wp_group' ) . ' wpgroup ON wp.id = wpgroup.wpid ';
2277 $where_group = ' AND wpgroup.groupid IN (' . $groups . ') ';
2278 if ( 'and' === $group_logic ) {
2279 $having_group = 'COUNT(DISTINCT wpgroup.groupid) = ' . $groups_count;
2280 }
2281 }
2282 }
2283
2284 $select_groups_belong = '';
2285
2286 if ( ! $is_count && $group_ids ) {
2287 $select_groups_belong = $this->get_select_groups_belong();
2288 }
2289
2290 $join_client = '';
2291 $where_client = '';
2292 $group_by = ' GROUP BY wp.id, wp_sync.sync_id';
2293 if ( ! empty( $having_group ) ) {
2294 $group_by .= ' HAVING ' . $having_group;
2295 }
2296 $group_by = ' GROUP BY wp.id, wp_sync.sync_id';
2297 if ( ! empty( $having_group ) ) {
2298 $group_by .= ' HAVING ' . $having_group;
2299 }
2300 if ( in_array( 'noclients', $client_ids ) ) {
2301 $join_client = ' LEFT JOIN ' . $this->table_name( 'wp_clients' ) . ' wpclient ON wp.client_id = wpclient.client_id ';
2302 $client_ids = array_filter(
2303 $client_ids,
2304 function ( $e ) {
2305 return 'noclients' !== $e;
2306 }
2307 );
2308 if ( ! empty( $client_ids ) ) {
2309 $clients = implode( ',', $client_ids );
2310 if ( $is_not ) {
2311 $where_client = ' AND wpclient.client_id IS NOT NULL AND wp.client_id NOT IN (' . $clients . ') ';
2312 } else {
2313 $where_client = ' AND wpclient.client_id IN (' . $clients . ') ';
2314 }
2315 } elseif ( $is_not ) {
2316 $where_client = ' AND wpclient.client_id IS NOT NULL ';
2317 } else {
2318 $where_client = ' AND wpclient.client_id IS NULL ';
2319 }
2320 } elseif ( $client_ids && ! empty( $client_ids ) ) {
2321 $clients = implode( ',', $client_ids );
2322 if ( $is_not ) {
2323 $join_client = ' LEFT JOIN ' . $this->table_name( 'wp_clients' ) . ' wpclient ON wp.client_id = wpclient.client_id ';
2324 $where_client = ' AND ( wpclient.client_id NOT IN (' . $clients . ') OR wpclient.client_id IS NULL ) ';
2325 } else {
2326 $join_client = ' JOIN ' . $this->table_name( 'wp_clients' ) . ' wpclient ON wp.client_id = wpclient.client_id ';
2327 $where_client = ' AND wpclient.client_id IN (' . $clients . ') ';
2328 }
2329 }
2330
2331 if ( '' === $join_client ) {
2332 $join_client = ' LEFT JOIN ' . $this->table_name( 'wp_clients' ) . ' wpclient ON wp.client_id = wpclient.client_id ';
2333 }
2334
2335 $light_fields = array(
2336 'wp.id',
2337 'wp.url',
2338 'wp.name',
2339 'wp.client_id',
2340 'wp.verify_certificate',
2341 'wp.http_user',
2342 'wp.http_pass',
2343 'wp.ssl_version',
2344 'wp.adminname',
2345 'wp.privkey',
2346 'wp.pubkey',
2347 'wp.wpe',
2348 'wp.is_staging',
2349 'wp.force_use_ipv4',
2350 'wp.siteurl',
2351 'wp.suspended',
2352 'wp.mainwpdir',
2353 'wp.is_ignoreCoreUpdates',
2354 'wp.is_ignorePluginUpdates',
2355 'wp.is_ignoreThemeUpdates',
2356 'wp.backup_before_upgrade',
2357 'wp.userid',
2358 'wp_sync.sync_errors',
2359 );
2360
2361 $legacy_status_fields = array(
2362 'wp.offline_check_result', // 1 - online, -1 offline.
2363 'wp.http_response_code',
2364 'wp.offline_checks_last',
2365 );
2366
2367 $light_fields = array_merge( $light_fields, $legacy_status_fields );
2368
2369 $join_monitors = '';
2370
2371 $select_fields = array(
2372 'wp.*',
2373 'wp_sync.*',
2374 );
2375
2376 if ( 'light_view' === $view ) {
2377 $select_fields = $light_fields;
2378 } elseif ( 'monitor_view' === $view ) {
2379 $select_fields = $light_fields;
2380 $select_fields[] = 'mo.*';
2381 $join_monitors = 'LEFT JOIN (
2382 SELECT m1.*
2383 FROM ' . $this->table_name( 'monitors' ) . ' m1
2384 JOIN (
2385 SELECT wpid, MAX(monitor_id) AS max_id
2386 FROM ' . $this->table_name( 'monitors' ) . '
2387 WHERE issub = 0
2388 GROUP BY wpid
2389 ) mm ON mm.wpid = m1.wpid AND m1.monitor_id = mm.max_id
2390 ) mo ON mo.wpid = wp.id ';
2391 } elseif ( 'manage_site' === $view ) {
2392 $select_fields[] = 'mo.monitor_id';
2393 $join_monitors = ' LEFT JOIN (
2394 SELECT wpid, MAX(monitor_id) AS monitor_id
2395 FROM ' . $this->table_name( 'monitors' ) . '
2396 WHERE issub = 0
2397 GROUP BY wpid
2398 ) AS mo
2399 ON mo.wpid = wp.id ';
2400
2401 }
2402
2403 $select = implode( ',', $select_fields );
2404
2405 $view_selects = '';
2406 $view_joins = '';
2407
2408 $opts_view = $this->get_wp_options_join( $extra_view, $view );
2409
2410 if ( is_array( $opts_view ) && ! empty( $opts_view['selects'] ) ) {
2411 $view_selects = ',' . $opts_view['selects'];
2412 $view_joins = $opts_view['joins'];
2413 }
2414
2415 // wpgroups to fix issue for mysql 8.0, as groups will generate error syntax.
2416 if ( $selectgroups ) {
2417
2418 if ( empty( $join_group ) ) {
2419 $join_group = ' LEFT JOIN ' . $this->table_name( 'wp_group' ) . ' wpgroup ON wp.id = wpgroup.wpid ';
2420 }
2421
2422 $qry = 'SELECT ' . $select . $view_selects . ', GROUP_CONCAT(DISTINCT gr.name ORDER BY gr.name SEPARATOR ",") as wpgroups, GROUP_CONCAT(DISTINCT gr.id ORDER BY gr.name SEPARATOR ",") as wpgroupids, GROUP_CONCAT(DISTINCT gr.color ORDER BY gr.name SEPARATOR ",") as wpgroups_colors, wpclient.name as client_name ' .
2423 $select_groups_belong . ' FROM ' . $this->table_name( 'wp' ) . ' wp ' .
2424 $join_client . ' ' .
2425 $join_group .
2426 $join_monitors . '
2427 LEFT JOIN ' . $this->table_name( 'group' ) . ' gr ON wpgroup.groupid = gr.id
2428
2429 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
2430 ' . $view_joins . '
2431 WHERE 1 ' . $where_cache_ids . $where . $where_group . $where_client . $group_by .
2432 $orderBy;
2433 } else {
2434 $qry = 'SELECT ' . $select . $view_selects . ', wpclient.name as client_name ' .
2435 $select_groups_belong . ' FROM ' . $this->table_name( 'wp' ) . ' wp ' .
2436 $join_group . ' ' .
2437 $join_client .
2438 $join_monitors . '
2439 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
2440 ' . $view_joins . '
2441 WHERE 1 ' . $where_cache_ids . $where . $where_group . $where_client . $group_by .
2442 $orderBy;
2443 }
2444
2445 if ( ( false !== $offset ) && ( false !== $rowcount ) ) {
2446 // When cache IDs are provided, they already represent the page-specific subset,
2447 // so the effective offset within that subset is always 0.
2448 $effective_offset = ! empty( $_included_cache_ids ) ? 0 : $offset;
2449 $qry .= ' LIMIT ' . $effective_offset . ', ' . $rowcount;
2450 } elseif ( false !== $rowcount ) {
2451 $qry .= ' LIMIT ' . $rowcount;
2452 }
2453
2454 if ( ! empty( $_included_cache_ids ) ) {
2455 MainWP_Logger::instance()->log_events( 'cache-metrics', sprintf( '[sql search websites=%s]', $qry ) );
2456 }
2457 MainWP_Logger::instance()->log_events( 'db-queries', sprintf( '[sql search websites=%s]', $qry ) );
2458
2459 return $qry;
2460 }
2461
2462 /**
2463 * Get child sites where allowed access via SQL.
2464 *
2465 * @param string $site_table_alias Child site table alias.
2466 * @param string $is_staging yes|no Is child site a staging site.
2467 *
2468 * @return boolean|null $_where Database query results or null on failure.
2469 */
2470 public function get_sql_where_allow_access_sites( $site_table_alias = '', $is_staging = 'no' ) { // phpcs:ignore -- NOSONAR - complex.
2471
2472 if ( empty( $site_table_alias ) ) {
2473 $site_table_alias = $this->table_name( 'wp' );
2474 }
2475
2476 // check to filter the staging sites.
2477 $where_staging = ' AND ' . $site_table_alias . '.is_staging = 0 ';
2478 if ( 'no' === $is_staging ) {
2479 $where_staging = ' AND ' . $site_table_alias . '.is_staging = 0 ';
2480 } elseif ( 'yes' === $is_staging ) {
2481 $where_staging = ' AND ' . $site_table_alias . '.is_staging = 1 ';
2482 } elseif ( 'nocheckstaging' === $is_staging ) {
2483 $where_staging = '';
2484 }
2485 // end staging filter.
2486
2487 $_where = $where_staging;
2488 // To fix bug run from cron job.
2489 if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
2490 return $_where;
2491 }
2492
2493 // To fix bug run from wp cli.
2494 if ( defined( 'WP_CLI' ) && WP_CLI ) {
2495 return $_where;
2496 }
2497
2498 // Run from Rest Api.
2499 if ( defined( 'MAINWP_REST_API_DOING' ) && MAINWP_REST_API_DOING ) {
2500 return $_where;
2501 }
2502
2503 /**
2504 * Filter: mainwp_currentuserallowedaccesssites
2505 *
2506 * Filters allowed sites for the current user.
2507 *
2508 * @since Unknown
2509 */
2510 $allowed_sites = apply_filters( 'mainwp_currentuserallowedaccesssites', 'all' );
2511
2512 if ( 'all' === $allowed_sites ) {
2513 return $_where;
2514 }
2515
2516 if ( is_array( $allowed_sites ) && ! empty( $allowed_sites ) ) {
2517 // valid group ids.
2518 $allowed_sites = array_filter(
2519 $allowed_sites,
2520 function ( $e ) {
2521 return is_numeric( $e ) ? true : false;
2522 }
2523 );
2524 $_where .= ' AND ' . $site_table_alias . '.id IN (' . implode( ',', $allowed_sites ) . ') ';
2525 } else {
2526 $_where .= ' AND 0 ';
2527 }
2528
2529 return $_where;
2530 }
2531
2532 /**
2533 * Get groupd where allowed access via SQL.
2534 *
2535 * @param string $group_table_alias Child site table alias.
2536 * @param string $with_staging yes|no Is child site a staging site.
2537 *
2538 * @return boolean|null $_where Database query results or null on failer.
2539 */
2540 public function get_sql_where_allow_groups( $group_table_alias = '', $with_staging = 'no' ) { // phpcs:ignore -- NOSONAR - complex.
2541
2542 if ( empty( $group_table_alias ) ) {
2543 $group_table_alias = $this->table_name( 'group' );
2544 }
2545
2546 // check to filter the staging group.
2547 $where_staging_group = '';
2548 $staging_group = get_option( 'mainwp_stagingsites_group_id' );
2549 if ( $staging_group ) {
2550 $where_staging_group = ' AND ' . $group_table_alias . '.id <> ' . $staging_group . ' ';
2551 if ( 'yes' === $with_staging ) {
2552 $where_staging_group = '';
2553 }
2554 }
2555
2556 // end staging filter.
2557 $_where = $where_staging_group;
2558
2559 // To fix bug run from cron job.
2560 if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
2561 return $_where;
2562 }
2563
2564 // Run from wp cli.
2565 if ( defined( 'WP_CLI' ) && WP_CLI ) {
2566 return $_where;
2567 }
2568
2569 // Run from Rest Api.
2570 if ( defined( 'MAINWP_REST_API_DOING' ) && MAINWP_REST_API_DOING ) {
2571 return $_where;
2572 }
2573
2574 /**
2575 * Filter: mainwp_currentuserallowedaccessgroups
2576 *
2577 * Filters allowed groups for the current user.
2578 *
2579 * @since Unknown
2580 */
2581 $allowed_groups = apply_filters( 'mainwp_currentuserallowedaccessgroups', 'all' );
2582
2583 if ( 'all' === $allowed_groups ) {
2584 return $_where;
2585 }
2586
2587 if ( is_array( $allowed_groups ) && ! empty( $allowed_groups ) ) {
2588
2589 // valid group ids.
2590 $allowed_groups = array_filter(
2591 $allowed_groups,
2592 function ( $e ) {
2593 return is_numeric( $e ) ? true : false;
2594 }
2595 );
2596
2597 return ' AND ' . $group_table_alias . '.id IN (' . implode( ',', $allowed_groups ) . ') ' . $_where;
2598 } else {
2599 return ' AND 0 ';
2600 }
2601 }
2602
2603
2604 /**
2605 * Get child site by id and params.
2606 *
2607 * @param int $id Child site ID.
2608 * @param array $params params.
2609 * @param string $obj OBJECT|ARRAY_A.
2610 *
2611 * @return object|null Database query results or null on failure.
2612 */
2613 public function get_website_by_id_params( $id, $params = array(), $obj = OBJECT ) {
2614 return $this->get_row_result( $this->get_sql_website_by_params( $id, $params ), $obj );
2615 }
2616
2617 /**
2618 * Get sql child site by id and params.
2619 *
2620 * @param int $id Child site ID.
2621 * @param array $params params.
2622 *
2623 * @return object|null Database query results or null on failure.
2624 */
2625 public function get_sql_website_by_params( $id, $params = array() ) {
2626
2627 if ( ! is_array( $params ) ) {
2628 $params = array();
2629 }
2630
2631 $select_groups = ! empty( $params['select_groups'] ) ? true : false;
2632
2633 $view = ! empty( $params['view'] ) ? $params['view'] : 'simple_view';
2634 $view_fields = isset( $params['view_fields'] ) ? $params['view_fields'] : array();
2635
2636 if ( is_string( $view_fields ) ) {
2637 $view_fields = (array) $view_fields;
2638 } elseif ( ! is_array( $view_fields ) ) {
2639 $view_fields = array();
2640 }
2641
2642 if ( MainWP_Utility::ctype_digit( $id ) ) {
2643
2644 $use_comp_subquery = ! empty( $params['use_compatible_subquery'] ) ? true : false;
2645
2646 $view_selects = '';
2647 $view_joins = '';
2648
2649 if ( $use_comp_subquery ) {
2650 $view_selects = ',wp_optionview.* ';
2651 $view_joins = ' JOIN ' . $this->get_option_view_by( $view, $view_fields ) . ' wp_optionview ON wp.id = wp_optionview.wpid ';
2652 } else {
2653 $opts_view = $this->get_option_view_by_join( $view, $view_fields );
2654 if ( is_array( $opts_view ) && ! empty( $opts_view['selects'] ) ) {
2655 $view_selects = ',' . $opts_view['selects'];
2656 $view_joins = $opts_view['joins'];
2657 }
2658 }
2659
2660 $where = $this->get_sql_where_allow_access_sites( 'wp', 'nocheckstaging' );
2661 if ( $select_groups ) {
2662 return 'SELECT wp.*,wp_sync.*' . $view_selects . ', GROUP_CONCAT(gr.name ORDER BY gr.name SEPARATOR ",") as wpgroups, GROUP_CONCAT(gr.id ORDER BY gr.name SEPARATOR ",") as wpgroupids, GROUP_CONCAT(gr.color ORDER BY gr.name SEPARATOR ",") as wpgroups_colors
2663 FROM ' . $this->table_name( 'wp' ) . ' wp
2664 LEFT JOIN ' . $this->table_name( 'wp_group' ) . ' wpgr ON wp.id = wpgr.wpid
2665 LEFT JOIN ' . $this->table_name( 'group' ) . ' gr ON wpgr.groupid = gr.id
2666 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
2667 ' . $view_joins . '
2668 WHERE wp.id = ' . $id . $where . '
2669 GROUP BY wp.id, wp_sync.sync_id';
2670 }
2671
2672 return 'SELECT wp.*,wp_sync.*' . $view_selects . '
2673 FROM ' . $this->table_name( 'wp' ) . ' wp
2674 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
2675 ' . $view_joins . '
2676 WHERE id = ' . $id . $where;
2677 }
2678 return null;
2679 }
2680
2681 /**
2682 * Get child site by id.
2683 *
2684 * @param int $id Child site ID.
2685 * @param array $selectGroups Select groups.
2686 * @param array $extra_view Get extra option fields.
2687 * @param int $obj OBJECT|ARRAY_A.
2688 *
2689 * @return object|null Database query results or null on failure.
2690 */
2691 public function get_website_by_id( $id, $selectGroups = false, $extra_view = array(), $obj = OBJECT ) {
2692 return $this->get_row_result( $this->get_sql_website_by_id( $id, $selectGroups, $extra_view ), $obj );
2693 }
2694
2695 /**
2696 * Get child site by id via SQL.
2697 *
2698 * @param int $id Child site ID.
2699 * @param bool $selectGroups Selected groups.
2700 * @param mixed $extra_view Extra view value.
2701 *
2702 * @return object|null Database query result or null on failure.
2703 *
2704 * @uses \MainWP\Dashboard\MainWP_Utility::ctype_digit()
2705 */
2706 public function get_sql_website_by_id( $id, $selectGroups = false, $extra_view = array() ) {
2707
2708 if ( ! is_array( $extra_view ) || empty( $extra_view ) ) {
2709 $extra_view = array( 'favi_icon', 'site_info' );
2710 }
2711
2712 if ( MainWP_Utility::ctype_digit( $id ) ) {
2713
2714 $view_selects = '';
2715 $view_joins = '';
2716
2717 $opts_view = $this->get_wp_options_join( $extra_view );
2718
2719 if ( is_array( $opts_view ) && ! empty( $opts_view['selects'] ) ) {
2720 $view_selects = ',' . $opts_view['selects'];
2721 $view_joins = $opts_view['joins'];
2722 }
2723
2724 $where = $this->get_sql_where_allow_access_sites( 'wp', 'nocheckstaging' );
2725 if ( $selectGroups ) {
2726 return 'SELECT wp.*,wp_sync.*' . $view_selects . ', GROUP_CONCAT(gr.name ORDER BY gr.name SEPARATOR ",") as wpgroups, GROUP_CONCAT(gr.id ORDER BY gr.name SEPARATOR ",") as wpgroupids, GROUP_CONCAT(gr.color ORDER BY gr.name SEPARATOR ",") as wpgroups_colors
2727 FROM ' . $this->table_name( 'wp' ) . ' wp
2728 LEFT JOIN ' . $this->table_name( 'wp_group' ) . ' wpgr ON wp.id = wpgr.wpid
2729 LEFT JOIN ' . $this->table_name( 'group' ) . ' gr ON wpgr.groupid = gr.id
2730 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
2731 ' . $view_joins . '
2732 WHERE wp.id = ' . $id . $where . '
2733 GROUP BY wp.id, wp_sync.sync_id';
2734 }
2735
2736 return 'SELECT wp.*,wp_sync.*' . $view_selects . '
2737 FROM ' . $this->table_name( 'wp' ) . ' wp
2738 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
2739 ' . $view_joins . '
2740 WHERE id = ' . $id . $where;
2741 }
2742
2743 return null;
2744 }
2745
2746 /**
2747 * Method get_websites_by_ids()
2748 *
2749 * Get child sites by child site IDs.
2750 *
2751 * @param array $ids Child site IDs.
2752 * @param int $userId User ID.
2753 *
2754 * @return object|null Database query result or null on failure.
2755 *
2756 * @uses \MainWP\Dashboard\MainWP_System::is_multi_user()
2757 */
2758 public function get_websites_by_ids( $ids, $userId = null ) {
2759 if ( ( null === $userId ) && MainWP_System::instance()->is_multi_user() ) {
2760
2761 /**
2762 * Current user global.
2763 *
2764 * @global string
2765 */
2766 global $current_user;
2767
2768 $userId = $current_user->ID;
2769 }
2770
2771 // valid group ids.
2772 $ids = array_filter(
2773 $ids,
2774 function ( $e ) {
2775 return ( is_numeric( $e ) && 0 < $e ) ? true : false;
2776 }
2777 );
2778
2779 $where = $this->get_sql_where_allow_access_sites();
2780 $table_name = esc_sql( $this->table_name( 'wp' ) );
2781 $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
2782 $sql = "SELECT * FROM {$table_name} WHERE id IN ({$placeholders})";
2783 $params = $ids;
2784
2785 if ( null !== $userId ) {
2786 $sql .= ' AND userid = %d';
2787 $params[] = intval( $userId );
2788 }
2789
2790 $sql .= ' ' . $where;
2791
2792 return $this->wpdb->get_results( $this->wpdb->prepare( $sql, ...$params ), OBJECT ); // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $where fragment is from validated get_sql_where_access_sites() with numeric IDs
2793 }
2794
2795 /**
2796 * Get child sites by groups IDs.
2797 *
2798 * @param array $ids Groups IDs.
2799 * @param int $userId User ID.
2800 * @param array $fields array fields .
2801 *
2802 * @return object|null Database query result or null on failure.
2803 *
2804 * @uses \MainWP\Dashboard\MainWP_System::is_multi_user()
2805 */
2806 public function get_websites_by_group_ids( $ids, $userId = null, $fields = array() ) {
2807 if ( empty( $ids ) ) {
2808 return array();
2809 }
2810 if ( ( null === $userId ) && MainWP_System::instance()->is_multi_user() ) {
2811
2812 /**
2813 * Current user global.
2814 *
2815 * @global string
2816 */
2817 global $current_user;
2818
2819 $userId = $current_user->ID;
2820 }
2821
2822 // valid group ids.
2823 $group_ids = array_filter(
2824 $ids,
2825 function ( $e ) {
2826 return is_numeric( $e ) ? true : false;
2827 }
2828 );
2829
2830 $select = '*';
2831 if ( ! empty( $fields ) && is_array( $fields ) ) {
2832 $fields = array_filter( array_map( 'trim', $fields ) );
2833 if ( $fields ) {
2834 $select = '';
2835 foreach ( $fields as $field ) {
2836 $select .= $this->escape( $field ) . ',';
2837 }
2838 $select = rtrim( $select, ',' );
2839 }
2840 }
2841 return $this->wpdb->get_results( 'SELECT ' . $select . ' FROM ' . $this->table_name( 'wp' ) . ' wp JOIN ' . $this->table_name( 'wp_group' ) . ' wpgroup ON wp.id = wpgroup.wpid WHERE wpgroup.groupid IN (' . implode( ',', $group_ids ) . ') ' . ( null !== $userId ? ' AND wp.userid = ' . intval( $userId ) : '' ), OBJECT );
2842 }
2843
2844 /**
2845 * Get child sites by group ID.
2846 *
2847 * @param int $id Group ID.
2848 * @param bool $selectgroups Selected groups. Default: false.
2849 * @param string $orderBy Order list by. Default: URL.
2850 * @param bool $offset Query offset. Default: false.
2851 * @param bool $rowcount Row count. Default: falese.
2852 * @param null $where SQL WHERE value.
2853 * @param null $search_site Site search field value. Default: null.
2854 * @param array $others Others params.
2855 *
2856 * @return object|null Database query result or null on failure.
2857 */
2858 public function get_websites_by_group_id( //phpcs:ignore -- NOSONAR -ok.
2859 $id,
2860 $selectgroups = false,
2861 $orderBy = 'wp.url',
2862 $offset = false,
2863 $rowcount = false,
2864 $where = null,
2865 $search_site = null,
2866 $others = array()
2867 ) {
2868 return $this->get_results_result(
2869 $this->get_sql_websites_by_group_id(
2870 $id,
2871 $selectgroups,
2872 $orderBy,
2873 $offset,
2874 $rowcount,
2875 $where,
2876 $search_site,
2877 $others
2878 )
2879 );
2880 }
2881
2882 /**
2883 * Get count of child sites by group ID.
2884 *
2885 * Uses an efficient COUNT query instead of fetching all rows.
2886 *
2887 * @param int $id Group ID.
2888 *
2889 * @return int Number of sites in the group.
2890 *
2891 * @uses \MainWP\Dashboard\MainWP_Utility::ctype_digit()
2892 */
2893 public function get_websites_count_by_group_id( $id ) {
2894 if ( ! MainWP_Utility::ctype_digit( $id ) ) {
2895 return 0;
2896 }
2897
2898 // Determine if this is the staging group.
2899 $is_staging = 'no';
2900 $staging_group = get_option( 'mainwp_stagingsites_group_id' );
2901 if ( $staging_group && (int) $id === (int) $staging_group ) {
2902 $is_staging = 'yes';
2903 }
2904
2905 $where_allowed = $this->get_sql_where_allow_access_sites( 'wp', $is_staging );
2906
2907 // Use prepare() for the groupid parameter.
2908 $qry = $this->wpdb->prepare(
2909 'SELECT COUNT(DISTINCT wp.id) FROM ' . $this->table_name( 'wp' ) . ' wp
2910 JOIN ' . $this->table_name( 'wp_group' ) . ' wpgroup ON wp.id = wpgroup.wpid
2911 WHERE wpgroup.groupid = %d',
2912 $id
2913 ) . $where_allowed;
2914
2915 return (int) $this->wpdb->get_var( $qry ); //phpcs:ignore PluginCheck.Security.DirectDB.Unprepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $where_allowed is from validated get_sql_where_access_sites() with numeric IDs.
2916 }
2917
2918 /**
2919 * Get child sites by group id via SQL.
2920 *
2921 * @param int $id Group ID.
2922 * @param bool $selectgroups Selected groups. Default: false.
2923 * @param string $orderBy Order list by. Default: URL.
2924 * @param bool $offset Query offset. Default: false.
2925 * @param bool $rowcount Row count. Default: falese.
2926 * @param null $where SQL WHERE value.
2927 * @param null $search_site Site search field value. Default: null.
2928 * @param array $others Others params.
2929 *
2930 * @return object|null Return database query or null on failure.
2931 *
2932 * @uses \MainWP\Dashboard\MainWP_Utility::ctype_digit()
2933 */
2934 public function get_sql_websites_by_group_id( // phpcs:ignore -- NOSONAR - complex.
2935 $id,
2936 $selectgroups = false,
2937 $orderBy = 'wp.url',
2938 $offset = false,
2939 $rowcount = false,
2940 $where = null,
2941 $search_site = null,
2942 $others = array()
2943 ) {
2944
2945 $is_staging = 'no';
2946 if ( $selectgroups ) {
2947 $staging_group = get_option( 'mainwp_stagingsites_group_id' );
2948 if ( $staging_group && $id === $staging_group ) {
2949 $is_staging = 'yes';
2950 }
2951 }
2952
2953 $where_search = '';
2954 if ( ! empty( $search_site ) ) {
2955 $search_site = trim( $search_site );
2956 // Use esc_like() to escape LIKE wildcards (%, _) then prepare() for SQL safety.
2957 $like_pattern = '%' . $this->wpdb->esc_like( $search_site ) . '%';
2958 $where_search .= $this->wpdb->prepare(
2959 ' AND (wp.name LIKE %s OR wp.url LIKE %s) ',
2960 $like_pattern,
2961 $like_pattern
2962 );
2963 }
2964
2965 $extra_view = is_array( $others ) && isset( $others['extra_view'] ) && is_array( $others['extra_view'] ) && ! empty( $others['extra_view'] ) ? $others['extra_view'] : array( 'site_info' );
2966
2967 $view_query = null;
2968 if ( is_array( $others ) && ! empty( $others['view_query'] ) ) {
2969 $view_query = $others['view_query'];
2970 }
2971
2972 if ( empty( $view_query ) ) {
2973 $view_query = $selectgroups ? 'default' : 'group'; // To compatible.
2974 }
2975
2976 $view_selects = '';
2977 $view_joins = '';
2978
2979 $opts_view = $this->get_wp_options_join( $extra_view, $view_query );
2980
2981 if ( is_array( $opts_view ) && ! empty( $opts_view['selects'] ) ) {
2982 $view_selects = ',' . $opts_view['selects'];
2983 $view_joins = $opts_view['joins'];
2984 }
2985
2986 if ( MainWP_Utility::ctype_digit( $id ) ) {
2987 $where_allowed = $this->get_sql_where_allow_access_sites( 'wp', $is_staging );
2988 if ( $selectgroups ) {
2989 $qry = 'SELECT wp.*,wp_sync.*' . $view_selects . ', GROUP_CONCAT(gr.name ORDER BY gr.name SEPARATOR ",") as wpgroups, GROUP_CONCAT(gr.id ORDER BY gr.name SEPARATOR ",") as wpgroupids, GROUP_CONCAT(gr.color ORDER BY gr.name SEPARATOR ",") as wpgroups_colors
2990 FROM ' . $this->table_name( 'wp' ) . ' wp
2991 JOIN ' . $this->table_name( 'wp_group' ) . ' wpgroup ON wp.id = wpgroup.wpid
2992 LEFT JOIN ' . $this->table_name( 'wp_group' ) . ' wpgr ON wp.id = wpgr.wpid
2993 LEFT JOIN ' . $this->table_name( 'group' ) . ' gr ON wpgr.groupid = gr.id
2994 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
2995 ' . $view_joins . '
2996 WHERE wpgroup.groupid = ' . $id . ' ' .
2997 ( empty( $where ) ? '' : ' AND ' . $where ) . $where_allowed . $where_search . '
2998 GROUP BY wp.id, wp_sync.sync_id
2999 ORDER BY ' . $orderBy;
3000 } else {
3001 $qry = 'SELECT wp.*' . $view_selects . ', wp_sync.* FROM ' . $this->table_name( 'wp' ) . ' wp
3002 JOIN ' . $this->table_name( 'wp_group' ) . ' wpgroup ON wp.id = wpgroup.wpid
3003 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
3004 ' . $view_joins . '
3005 WHERE wpgroup.groupid = ' . $id . ' ' . $where_allowed . $where_search .
3006 ( empty( $where ) ? '' : ' AND ' . $where ) . ' ORDER BY ' . $orderBy;
3007 }
3008 if ( ( false !== $offset ) && ( false !== $rowcount ) ) {
3009 $qry .= ' LIMIT ' . $offset . ', ' . $rowcount;
3010 } elseif ( false !== $rowcount ) {
3011 $qry .= ' LIMIT ' . $rowcount;
3012 }
3013
3014 return $qry;
3015 }
3016
3017 return null;
3018 }
3019
3020 /**
3021 * Get child sites by group name.
3022 *
3023 * @param int $userid Current user ID.
3024 * @param string $groupname Group name.
3025 *
3026 * @return object|null Database query result or null on failure.
3027 */
3028 public function get_websites_by_group_name( $userid, $groupname ) {
3029 return $this->get_results_result( $this->get_sql_websites_by_group_name( $groupname, $userid ) );
3030 }
3031
3032 /**
3033 * Get child sites by group name.
3034 *
3035 * @param string $groupname Group name.
3036 * @param int $userid Current user ID.
3037 *
3038 * @return object|null Database query result or null on failure.
3039 *
3040 * @uses \MainWP\Dashboard\MainWP_System::is_multi_user()
3041 */
3042 public function get_sql_websites_by_group_name( $groupname, $userid = null ) {
3043 if ( ( null === $userid ) && MainWP_System::instance()->is_multi_user() ) {
3044
3045 /**
3046 * Current user global.
3047 *
3048 * @global string
3049 */
3050 global $current_user;
3051
3052 $userid = $current_user->ID;
3053 }
3054
3055 $view_selects = '';
3056 $view_joins = '';
3057
3058 $opts_view = $this->get_wp_options_join();
3059
3060 if ( is_array( $opts_view ) && ! empty( $opts_view['selects'] ) ) {
3061 $view_selects = ',' . $opts_view['selects'];
3062 $view_joins = $opts_view['joins'];
3063 }
3064
3065 $sql = 'SELECT wp.*,wp_sync.*' . $view_selects . ' FROM ' . $this->table_name( 'wp' ) . ' wp
3066 INNER JOIN ' . $this->table_name( 'wp_group' ) . ' wpgroup ON wp.id = wpgroup.wpid
3067 JOIN ' . $this->table_name( 'group' ) . ' g ON wpgroup.groupid = g.id
3068 JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid
3069 ' . $view_joins . '
3070 WHERE g.name="' . $this->escape( $groupname ) . '"';
3071 if ( null !== $userid ) {
3072 $sql .= ' AND g.userid = "' . intval( $userid ) . '"';
3073 }
3074
3075 return $sql;
3076 }
3077
3078 /**
3079 * Get child site IP address.
3080 *
3081 * @param int $wpid Child site ID.
3082 *
3083 * @return string|null Child site IP address or null on failure.
3084 */
3085 public function get_wp_ip( $wpid ) {
3086 $table_name = esc_sql( $this->table_name( 'request_log' ) );
3087 return $this->wpdb->get_var( $this->wpdb->prepare( "SELECT ip FROM {$table_name} WHERE wpid = %d", $wpid ) );
3088 }
3089
3090 /**
3091 * Add website to the MainWP Dashboard.
3092 *
3093 * @param int $userid Current user ID.
3094 * @param string $name Child site name.
3095 * @param string $url Child site URL.
3096 * @param string $admin Child site administrator username.
3097 * @param string $pubkey OpenSSL public key.
3098 * @param string $privkey OpenSSL private key.
3099 * @param array $params Other params.
3100 *
3101 * @return int|false Child site ID or false on failure.
3102 *
3103 * @uses \MainWP\Dashboard\MainWP_Utility::ctype_digit()
3104 */
3105 public function add_website( // phpcs:ignore -- NOSONAR - complex.
3106 $userid,
3107 $name,
3108 $url,
3109 $admin,
3110 $pubkey,
3111 $privkey,
3112 $params = array()
3113 ) {
3114
3115 if ( ! is_array( $params ) ) {
3116 $params = array();
3117 }
3118
3119 $groupids = isset( $params['groupids'] ) ? $params['groupids'] : array();
3120 $groupnames = isset( $params['groupnames'] ) ? $params['groupnames'] : array();
3121 $verifyCertificate = isset( $params['verifyCertificate'] ) ? (int) $params['verifyCertificate'] : 2;
3122 $uniqueId = isset( $params['uniqueId'] ) ? $params['uniqueId'] : '';
3123 $http_user = isset( $params['http_user'] ) ? $params['http_user'] : null;
3124 $http_pass = isset( $params['http_pass'] ) ? $params['http_pass'] : null;
3125 $sslVersion = isset( $params['sslVersion'] ) ? $params['sslVersion'] : 0;
3126 $wpe = isset( $params['wpe'] ) ? $params['wpe'] : 0;
3127 $isStaging = isset( $params['isStaging'] ) ? $params['isStaging'] : 0;
3128
3129 if ( MainWP_Utility::ctype_digit( $userid ) ) {
3130 if ( '/' !== substr( $url, - 1 ) ) {
3131 $url .= '/';
3132 }
3133
3134 $en_pk_data = MainWP_Encrypt_Data_Lib::instance()->encrypt_privkey( base64_decode( $privkey ) ); // phpcs:ignore -- NOSONAR - base64_encode trust.
3135 $en_privkey = isset( $en_pk_data['en_data'] ) ? $en_pk_data['en_data'] : '';
3136
3137 $values = array(
3138 'userid' => $userid,
3139 'adminname' => $this->escape( $admin ),
3140 'name' => $this->escape( wp_strip_all_tags( $name ) ),
3141 'url' => $this->escape( $url ),
3142 'pubkey' => $this->escape( $pubkey ),
3143 'privkey' => $this->escape( base64_encode( $en_privkey ) ), // phpcs:ignore -- NOSONAR - trust.
3144 'siteurl' => '',
3145 'ga_id' => '',
3146 'gas_id' => 0,
3147 'offline_checks_last' => 0,
3148 'offline_check_result' => 0,
3149 'note' => '',
3150 'statsUpdate' => 0,
3151 'directories' => '',
3152 'plugin_upgrades' => '',
3153 'theme_upgrades' => '',
3154 'translation_upgrades' => '',
3155 'securityIssues' => '',
3156 'premium_upgrades' => '',
3157 'themes' => '',
3158 'ignored_themes' => '',
3159 'plugins' => '',
3160 'ignored_plugins' => '',
3161 'users' => '',
3162 'categories' => '',
3163 'pluginDir' => '',
3164 'automatic_update' => 0,
3165 'backup_before_upgrade' => 2,
3166 'verify_certificate' => intval( $verifyCertificate ),
3167 'ssl_version' => $sslVersion,
3168 'uniqueId' => $uniqueId,
3169 'mainwpdir' => 0,
3170 'http_user' => $http_user,
3171 'http_pass' => $http_pass,
3172 'wpe' => $wpe,
3173 'is_staging' => $isStaging,
3174 );
3175
3176 $syncValues = array(
3177 'dtsSync' => 0,
3178 'dtsSyncStart' => 0,
3179 'dtsAutomaticSync' => 0,
3180 'dtsAutomaticSyncStart' => 0,
3181 'totalsize' => 0,
3182 'extauth' => '',
3183 'sync_errors' => '',
3184 );
3185 if ( $this->wpdb->insert( $this->table_name( 'wp' ), $values ) ) {
3186 $websiteid = $this->wpdb->insert_id;
3187 MainWP_Logger::instance()->log_events( 'db-queries', sprintf( '[Insert site=%s]', $this->get_last_query() ) ); // after: $this->wpdb->insert_id.
3188 MainWP_Encrypt_Data_Lib::instance()->encrypt_save_keys( $websiteid, $en_pk_data );
3189 $syncValues['wpid'] = $websiteid;
3190 $this->wpdb->insert( $this->table_name( 'wp_sync' ), $syncValues );
3191 MainWP_Logger::instance()->log_events( 'db-queries', sprintf( '[Insert sync data=%s]', $this->get_last_query() ) );
3192 $this->wpdb->insert(
3193 $this->table_name( 'wp_settings_backup' ),
3194 array(
3195 'wpid' => $websiteid,
3196 'archiveFormat' => 'global',
3197 )
3198 );
3199
3200 foreach ( $groupnames as $groupname ) {
3201 if ( $this->wpdb->insert(
3202 $this->table_name( 'group' ),
3203 array(
3204 'userid' => $userid,
3205 'name' => $this->escape( htmlspecialchars( $groupname ) ),
3206 )
3207 )
3208 ) {
3209 $groupids[] = $this->wpdb->insert_id;
3210 }
3211 }
3212 // add groupids.
3213 foreach ( $groupids as $groupid ) {
3214 $this->wpdb->insert(
3215 $this->table_name( 'wp_group' ),
3216 array(
3217 'wpid' => $websiteid,
3218 'groupid' => $groupid,
3219 )
3220 );
3221 }
3222 MainWP_Manage_Sites_List_Table::invalidate_manage_sites_cache();
3223 return $websiteid;
3224 }
3225 }
3226
3227 return false;
3228 }
3229
3230 /**
3231 * Remove child site from the MainWP Dashboard.
3232 *
3233 * @param int $websiteid Child site ID.
3234 *
3235 * @return int|boolean Return child site ID that was removed or false on failure.
3236 *
3237 * @uses \MainWP\Dashboard\MainWP_Utility::ctype_digit()
3238 */
3239 public function remove_website( $websiteid ) {
3240 if ( MainWP_Utility::ctype_digit( $websiteid ) ) {
3241 $nr = $this->wpdb->delete( $this->table_name( 'wp' ), array( 'id' => $websiteid ) );
3242 $this->wpdb->delete( $this->table_name( 'wp_group' ), array( 'wpid' => $websiteid ) );
3243 $this->wpdb->delete( $this->table_name( 'wp_sync' ), array( 'wpid' => $websiteid ) );
3244 $this->wpdb->delete( $this->table_name( 'wp_options' ), array( 'wpid' => $websiteid ) );
3245 MainWP_Encrypt_Data_Lib::remove_key_file( $websiteid );
3246 MainWP_DB_Uptime_Monitoring::instance()->delete_monitor( array( 'wpid' => $websiteid ) );
3247 MainWP_Manage_Sites_List_Table::invalidate_manage_sites_cache();
3248 return $nr;
3249 }
3250
3251 return false;
3252 }
3253
3254 /**
3255 * Update child site db values.
3256 *
3257 * @param int $websiteid Child site ID.
3258 * @param array $fields Database fields to update.
3259 *
3260 * @return int|boolean The number of rows updated, or false on error.
3261 */
3262 public function update_website_values( $websiteid, $fields ) {
3263 if ( ! empty( $fields ) ) {
3264 // Lock the data stream to prevent other processes from updating at the same time.
3265 $table_name = esc_sql( $this->table_name( 'wp' ) );
3266 $sql = $this->wpdb->prepare(
3267 "SELECT * FROM {$table_name} WHERE id = %d FOR UPDATE",
3268 $websiteid
3269 );
3270 $this->wpdb->get_row( $sql );
3271
3272 return $this->wpdb->update( $this->table_name( 'wp' ), $fields, array( 'id' => $websiteid ) );
3273 }
3274
3275 return false;
3276 }
3277
3278 /**
3279 * Update child site sync values.
3280 *
3281 * @param int $websiteid Child site ID.
3282 * @param array $fields Database fields to update.
3283 *
3284 * @return int|boolean The number of rows updated, or false on error.
3285 */
3286 public function update_website_sync_values( $websiteid, $fields ) {
3287 if ( ! empty( $fields ) ) {
3288 return $this->wpdb->update( $this->table_name( 'wp_sync' ), $fields, array( 'wpid' => $websiteid ) );
3289 }
3290
3291 return false;
3292 }
3293
3294 /**
3295 * Update child site.
3296 *
3297 * @param int $websiteid Website ID.
3298 * @param string $url Child site URL.
3299 * @param int $userid Current user ID.
3300 * @param string $name Child site name.
3301 * @param string $siteadmin Child site administrator username.
3302 * @param array $groupids Group IDs.
3303 * @param array $groupnames Group Names.
3304 * @param string $pluginDir Plugin directory.
3305 * @param mixed $maximumFileDescriptorsOverride Overwrite the Maximum File Descriptors option.
3306 * @param mixed $maximumFileDescriptorsAuto Auto set the Maximum File Descriptors option.
3307 * @param mixed $maximumFileDescriptors Set the Maximum File Descriptors option.
3308 * @param int $verifyCertificate Whether or not to verify SSL Certificate.
3309 * @param mixed $archiveFormat Backup archive formate.
3310 * @param string $uniqueId Unique security ID.
3311 * @param string $http_user HTTP Basic Authentication username.
3312 * @param string $http_pass HTTP Basic Authentication password.
3313 * @param int $sslVersion SSL Version.
3314 * @param bool $disableHealthChecking Disable Site health threshold.
3315 * @param int $healthThreshold Site health threshold.
3316 * @param string $backup_method Primary backup method.
3317 *
3318 * @return boolean ture on success or false on failure.
3319 *
3320 * @uses \MainWP\Dashboard\MainWP_System_Utility::can_edit_website()
3321 * @uses \MainWP\Dashboard\MainWP_Utility::ctype_digit()
3322 */
3323 public function update_website( // phpcs:ignore -- NOSONAR - complex.
3324 $websiteid,
3325 $url,
3326 $userid,
3327 $name,
3328 $siteadmin,
3329 $groupids,
3330 $groupnames,
3331 $pluginDir,
3332 $maximumFileDescriptorsOverride,
3333 $maximumFileDescriptorsAuto,
3334 $maximumFileDescriptors,
3335 $verifyCertificate = 1,
3336 $archiveFormat = 'global',
3337 $uniqueId = '',
3338 $http_user = null,
3339 $http_pass = null,
3340 $sslVersion = 0,
3341 $disableHealthChecking = 1,
3342 $healthThreshold = 0,
3343 $backup_method = 'global'
3344 ) {
3345
3346 $wpe = 0; // going to update when sync.
3347
3348 if ( MainWP_Utility::ctype_digit( $websiteid ) && MainWP_Utility::ctype_digit( $userid ) ) {
3349 $website = $this->get_website_by_id( $websiteid );
3350 if ( MainWP_System_Utility::can_edit_website( $website ) ) {
3351 // update admin.
3352 $this->wpdb->update(
3353 $this->table_name( 'wp' ),
3354 array(
3355 'url' => $url,
3356 'name' => wp_strip_all_tags( $name ),
3357 'adminname' => $siteadmin,
3358 'pluginDir' => $pluginDir,
3359 'verify_certificate' => intval( $verifyCertificate ),
3360 'ssl_version' => intval( $sslVersion ),
3361 'wpe' => intval( $wpe ),
3362 'uniqueId' => $uniqueId,
3363 'http_user' => $http_user,
3364 'http_pass' => $http_pass,
3365 'disable_health_check' => $disableHealthChecking,
3366 'health_threshold' => $healthThreshold,
3367 'primary_backup_method' => $backup_method,
3368 ),
3369 array( 'id' => $websiteid )
3370 );
3371 $this->wpdb->update(
3372 $this->table_name( 'wp_settings_backup' ),
3373 array( 'archiveFormat' => $archiveFormat ),
3374 array( 'wpid' => $websiteid )
3375 );
3376
3377 if ( get_option( 'mainwp_enableLegacyBackupFeature' ) ) {
3378 $this->wpdb->update(
3379 $this->table_name( 'wp' ),
3380 array(
3381 'maximumFileDescriptorsOverride' => (int) $maximumFileDescriptorsOverride,
3382 'maximumFileDescriptorsAuto' => (int) $maximumFileDescriptorsAuto,
3383 'maximumFileDescriptors' => (int) $maximumFileDescriptors,
3384 ),
3385 array( 'id' => $websiteid )
3386 );
3387 }
3388
3389 // remove groups.
3390 $this->wpdb->delete( $this->table_name( 'wp_group' ), array( 'wpid' => $websiteid ) );
3391 // Remove GA stats.
3392 $showErrors = $this->wpdb->hide_errors();
3393
3394 /**
3395 * Action: mainwp_ga_delete_site
3396 *
3397 * Fires upon site removal process in order to delete Google Analytics data.
3398 *
3399 * @param int $websiteid Child site ID.
3400 *
3401 * @since Unknown
3402 */
3403 do_action( 'mainwp_ga_delete_site', $websiteid );
3404
3405 if ( $showErrors ) {
3406 $this->wpdb->show_errors();
3407 }
3408 // add groups with groupnames.
3409 foreach ( $groupnames as $groupname ) {
3410 if ( $this->wpdb->insert(
3411 $this->table_name( 'group' ),
3412 array(
3413 'userid' => $userid,
3414 'name' => $this->escape( $groupname ),
3415 )
3416 )
3417 ) {
3418 $groupids[] = $this->wpdb->insert_id;
3419 }
3420 }
3421 // add groupids.
3422 foreach ( $groupids as $groupid ) {
3423 $this->wpdb->insert(
3424 $this->table_name( 'wp_group' ),
3425 array(
3426 'wpid' => $websiteid,
3427 'groupid' => $groupid,
3428 )
3429 );
3430 }
3431
3432 return true;
3433 }
3434 }
3435
3436 return false;
3437 }
3438
3439
3440 /**
3441 * Get website update stats via SQL.
3442 *
3443 * @return object|null Database query result of null on failure.
3444 */
3445 public function get_websites_stats_update_sql() {
3446 $where = $this->get_sql_where_allow_access_sites( 'wp' );
3447 return 'SELECT wp.*,wp_sync.sync_errors FROM ' . $this->table_name( 'wp' ) . ' wp JOIN ' . $this->table_name( 'wp_sync' ) . ' wp_sync ON wp.id = wp_sync.wpid WHERE (wp.statsUpdate = 0 OR ' . time() . ' - wp.statsUpdate >= ' . ( 60 * 60 * 24 ) . ')' . $where . ' ORDER BY wp.statsUpdate ASC';
3448 }
3449
3450 /**
3451 * Update child site statistics.
3452 *
3453 * Update whether or not a child site has been updated.
3454 *
3455 * @param mixed $websiteid Child site ID.
3456 * @param mixed $statsUpdated Child site Update status.
3457 *
3458 * @return (int|boolean) Number of rows effected in update or false on failure.
3459 */
3460 public function update_website_stats( $websiteid, $statsUpdated ) {
3461 return $this->wpdb->update(
3462 $this->table_name( 'wp' ),
3463 array( 'statsUpdate' => $statsUpdated ),
3464 array( 'id' => $websiteid )
3465 );
3466 }
3467
3468 /**
3469 * Get child site by url.
3470 *
3471 * @param string $url Child site URL.
3472 *
3473 * @return object|null Database query result or null on failure.
3474 */
3475 public function get_websites_by_url( $url ) {
3476 if ( '/' !== substr( $url, - 1 ) ) {
3477 $url .= '/';
3478 }
3479 $wp_table = esc_sql( $this->table_name( 'wp' ) );
3480 $wp_sync_table = esc_sql( $this->table_name( 'wp_sync' ) );
3481 $results = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$wp_table} wp JOIN {$wp_sync_table} wp_sync ON wp.id = wp_sync.wpid WHERE wp.url = %s", $url ), OBJECT );
3482 if ( $results ) {
3483 return $results;
3484 }
3485
3486 if ( stristr( $url, '/www.' ) ) {
3487 // remove www if it's there!
3488 $url = str_replace( '/www.', '/', $url );
3489 } else {
3490 // add www if it's not there!
3491 $url = str_replace( 'https://', 'https://www.', $url );
3492 $url = str_replace( 'http://', 'http://www.', $url );
3493 }
3494
3495 $results = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$wp_table} wp JOIN {$wp_sync_table} wp_sync ON wp.id = wp_sync.wpid WHERE wp.url = %s", $url ), OBJECT );
3496 if ( $results ) {
3497 return $results;
3498 }
3499
3500 $url = str_replace( array( 'https://www.', 'http://www.', 'https://', 'http://', 'www.' ), array( '', '', '', '', '' ), $url );
3501
3502 return $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$wp_table} wp JOIN {$wp_sync_table} wp_sync ON wp.id = wp_sync.wpid WHERE replace(replace(replace(replace(replace(wp.url, 'https://www.',''), 'http://www.',''), 'https://', ''), 'http://', ''), 'www.', '') = %s", $url ), OBJECT );
3503 }
3504
3505 /**
3506 * Method get_websites_to_notice_health_threshold()
3507 *
3508 * Get websites to notice site health.
3509 *
3510 * @param int $globalThreshold Global site health threshold.
3511 */
3512 public function get_websites_to_notice_health_threshold( $globalThreshold ) {
3513
3514 $where = $this->get_sql_where_allow_access_sites( 'wp' );
3515 $extra_view = array( 'monitoring_notification_emails', 'settings_notification_emails' );
3516
3517 if ( 80 >= $globalThreshold ) { // actual is 80.
3518 // should-be-improved site health.
3519 $where_global_threshold = '( wp.health_threshold = 0 AND wp_sync.health_value < 80 )';
3520 } else {
3521 // good site health.
3522 $where_global_threshold = '( wp.health_threshold = 0 AND wp_sync.health_value >= 80 )';
3523 }
3524
3525 $where_site_threshold = ' ( wp.health_threshold = 80 AND wp_sync.health_value < 80 ) '; // should-be-improved site health.
3526 $where_site_threshold .= ' OR ( wp.health_threshold = 100 AND wp_sync.health_value >= 80 ) '; // good site health.
3527
3528 $wp_table = esc_sql( $this->table_name( 'wp' ) );
3529 $wp_sync_table = esc_sql( $this->table_name( 'wp_sync' ) );
3530
3531 $view_selects = '';
3532 $view_joins = '';
3533
3534 $opts_view = $this->get_wp_options_join( $extra_view );
3535
3536 if ( is_array( $opts_view ) && ! empty( $opts_view['selects'] ) ) {
3537 $view_selects = ',' . $opts_view['selects'];
3538 $view_joins = $opts_view['joins'];
3539 }
3540 return $this->wpdb->get_results( // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $option_view is a validated SQL subquery
3541 "SELECT wp.*,wp_sync.* {$view_selects} FROM {$wp_table} wp
3542 JOIN {$wp_sync_table} wp_sync ON wp.id = wp_sync.wpid
3543 {$view_joins}
3544 WHERE wp.disable_health_check <> 1 AND wp.offline_check_result = 1 AND ( {$where_global_threshold} OR{$where_site_threshold} ) AND wp_sync.health_site_noticed = 0 " .
3545 $where . ' GROUP BY wp.id ',
3546 OBJECT
3547 );
3548 }
3549
3550 /**
3551 * Get websites offline status.
3552 *
3553 * @return array Sites with offline status.
3554 */
3555 public function get_websites_http_check_status() {
3556 $where = $this->get_sql_where_allow_access_sites( 'wp' );
3557 $extra_view = array( 'settings_notification_emails' );
3558 $wp_table = esc_sql( $this->table_name( 'wp' ) );
3559
3560 $view_selects = '';
3561 $view_joins = '';
3562
3563 $opts_view = $this->get_wp_options_join( $extra_view );
3564
3565 if ( is_array( $opts_view ) && ! empty( $opts_view['selects'] ) ) {
3566 $view_selects = ',' . $opts_view['selects'];
3567 $view_joins = $opts_view['joins'];
3568 }
3569
3570 return $this->wpdb->get_results(
3571 "SELECT wp.*{$view_selects} FROM {$wp_table} wp
3572 {$view_joins}" . // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- is a validated SQL subquery.
3573 ' WHERE wp.suspended = 0 AND wp.http_code_noticed = 0 AND wp.offline_check_result = -1 ' .
3574 $where . ' GROUP BY wp.id ',
3575 OBJECT
3576 );
3577 }
3578
3579 /**
3580 * Method set_website_noticed_http_check().
3581 *
3582 * @param array $site_id The site id .
3583 *
3584 * @return void
3585 */
3586 public function set_website_noticed_http_check( $site_id = array() ) {
3587 if ( empty( $site_id ) ) {
3588 return;
3589 }
3590 $this->wpdb->query( $this->wpdb->prepare( 'UPDATE ' . esc_sql( $this->table_name( 'wp' ) ) . ' SET http_code_noticed = 1 WHERE id = %d', $site_id ) ); // phpcs:ignore PluginCheck.Security.DirectDB.UnpreparedSQL -- $site_id is an integer that is validated by ctype_digit() before being passed to this method.
3591 }
3592
3593 /**
3594 * Get DB Sites.
3595 *
3596 * @since 4.6
3597 *
3598 * @param mixed $params params.
3599 *
3600 * @return array $dbwebsites.
3601 */
3602 public function get_db_sites( $params = array() ) { // phpcs:ignore -- NOSONAR - complex.
3603
3604 $dbwebsites = array();
3605
3606 $data_fields = MainWP_System_Utility::get_default_map_site_fields();
3607 $data_fields[] = 'verify_certificate';
3608 $data_fields[] = 'client_id';
3609
3610 $fields = isset( $params['fields'] ) && is_array( $params['fields'] ) ? $params['fields'] : array();
3611 $sites = isset( $params['sites'] ) && is_array( $params['sites'] ) ? $params['sites'] : array();
3612 $groups = isset( $params['groups'] ) && is_array( $params['groups'] ) ? $params['groups'] : array();
3613 $clients = isset( $params['clients'] ) && is_array( $params['clients'] ) ? $params['clients'] : array();
3614 $schema_fields = isset( $params['schema_fields'] ) && is_array( $params['schema_fields'] ) ? $params['schema_fields'] : array(); // since 5.2.
3615 $selectgroups = isset( $params['selectgroups'] ) && ! empty( $params['selectgroups'] ) ? true : false; // since 5.2.
3616
3617 if ( ! empty( $schema_fields ) ) { // since 5.2.
3618 foreach ( $schema_fields as $field_name ) {
3619 if ( ! in_array( $field_name, $data_fields ) ) {
3620 $data_fields[] = $field_name;
3621 }
3622 }
3623 } elseif ( is_array( $fields ) ) {
3624 foreach ( $fields as $field_indx => $field_name ) {
3625
3626 $get_field = $field_name;
3627 if ( is_numeric( $get_field ) || is_bool( $get_field ) ) { // to compatible fix.
3628 $get_field = $field_indx;
3629 }
3630
3631 if ( in_array( $get_field, static::$possible_options ) && ! in_array( $get_field, $data_fields ) ) {
3632 $data_fields[] = $get_field;
3633 }
3634 }
3635 }
3636
3637 if ( ! empty( $sites ) ) {
3638 foreach ( $sites as $v ) {
3639 if ( MainWP_Utility::ctype_digit( $v ) ) {
3640 $website = static::instance()->get_website_by_id( $v, $selectgroups );
3641 if ( empty( $website ) ) {
3642 continue;
3643 }
3644 $dbwebsites[ $website->id ] = MainWP_Utility::map_site( $website, $data_fields );
3645 }
3646 }
3647 }
3648
3649 if ( ! empty( $groups ) ) {
3650 foreach ( $groups as $v ) {
3651 if ( MainWP_Utility::ctype_digit( $v ) ) {
3652 $websites = static::instance()->query( static::instance()->get_sql_websites_by_group_id( $v, $selectgroups ) );
3653 while ( $websites && ( $website = static::fetch_object( $websites ) ) ) {
3654 $dbwebsites[ $website->id ] = MainWP_Utility::map_site( $website, $data_fields );
3655 }
3656 static::free_result( $websites );
3657 }
3658 }
3659 }
3660
3661 $params = array(
3662 'full_data' => true,
3663 'selectgroups' => $selectgroups,
3664 );
3665 $client_sites = MainWP_DB_Client::instance()->get_websites_by_client_ids( $clients, $params );
3666 if ( $client_sites ) {
3667 foreach ( $client_sites as $website ) {
3668 $dbwebsites[ $website->id ] = MainWP_Utility::map_site( $website, $data_fields );
3669 }
3670 }
3671 return $dbwebsites;
3672 }
3673
3674 /**
3675 * Get Sites.
3676 *
3677 * @param int $websiteid The id of the child site you wish to retrieve.
3678 * @param bool $for_manager Check Team Control.
3679 * @param array $others Array of others.
3680 *
3681 * @return array $output Array of content to output.
3682 *
3683 * @uses \MainWP\Dashboard\MainWP_System_Utility::can_edit_website()
3684 * @uses \MainWP\Dashboard\MainWP_Utility::get_nice_url()
3685 */
3686 public function get_sites( $websiteid = null, $for_manager = false, $others = array() ) { // phpcs:ignore -- NOSONAR - not quite complex function.
3687
3688 if ( ! is_array( $others ) ) {
3689 $others = array();
3690 }
3691
3692 $search_site = null;
3693 $orderBy = 'wp.url';
3694 $offset = false;
3695 $rowcount = false;
3696 $extraWhere = null;
3697
3698 if ( isset( $websiteid ) && ( null !== $websiteid ) ) {
3699 $website = static::instance()->get_website_by_id( $websiteid );
3700
3701 if ( ! MainWP_System_Utility::can_edit_website( $website ) ) {
3702 return false;
3703 }
3704
3705 if ( ! \mainwp_current_user_can( 'site', $websiteid ) ) {
3706 return false;
3707 }
3708
3709 return array(
3710 array(
3711 'id' => $websiteid,
3712 'url' => MainWP_Utility::get_nice_url( $website->url, true ),
3713 'name' => $website->name,
3714 'totalsize' => $website->totalsize,
3715 'sync_errors' => $website->sync_errors,
3716 ),
3717 );
3718 } else {
3719 if ( isset( $others['orderby'] ) ) {
3720 if ( 'site' === $others['orderby'] ) {
3721 $orderBy = 'wp.name ' . ( 'asc' === $others['order'] ? 'asc' : 'desc' );
3722 } elseif ( 'url' === $others['orderby'] ) {
3723 $orderBy = 'wp.url ' . ( 'asc' === $others['order'] ? 'asc' : 'desc' );
3724 }
3725 }
3726 if ( isset( $others['search'] ) ) {
3727 $search_site = trim( $others['search'] );
3728 }
3729
3730 if ( is_array( $others ) && isset( $others['plugins_slug'] ) ) {
3731 $slugs = explode( ',', $others['plugins_slug'] );
3732 $extraWhere = '';
3733 foreach ( $slugs as $slug ) {
3734 $slug = wp_json_encode( $slug );
3735 $slug = trim( $slug, '"' );
3736 $slug = str_replace( '\\', '.', $slug );
3737 $extraWhere .= ' wp.plugins REGEXP "' . $slug . '" OR';
3738 }
3739 $extraWhere = trim( rtrim( $extraWhere, 'OR' ) );
3740
3741 if ( '' === $extraWhere ) {
3742 $extraWhere = null;
3743 } else {
3744 $extraWhere = '(' . $extraWhere . ')';
3745 }
3746 }
3747 }
3748
3749 $totalRecords = '';
3750
3751 if ( isset( $others['per_page'] ) && ! empty( $others['per_page'] ) ) {
3752 $sql = static::instance()->get_sql_websites_for_current_user( false, $search_site, $orderBy, false, false, $extraWhere, $for_manager );
3753 $websites_total = static::instance()->query( $sql );
3754 $totalRecords = ( $websites_total ? static::num_rows( $websites_total ) : 0 );
3755
3756 if ( $websites_total ) {
3757 static::free_result( $websites_total );
3758 }
3759
3760 $rowcount = absint( $others['per_page'] );
3761 $pagenum = isset( $others['paged'] ) ? absint( $others['paged'] ) : 0;
3762 if ( $pagenum > $totalRecords ) {
3763 $pagenum = $totalRecords;
3764 }
3765 $pagenum = max( 1, $pagenum );
3766 $offset = ( $pagenum - 1 ) * $rowcount;
3767
3768 }
3769
3770 $sql = static::instance()->get_sql_websites_for_current_user( false, $search_site, $orderBy, $offset, $rowcount, $extraWhere, $for_manager );
3771 $websites = static::instance()->query( $sql );
3772
3773 $output = array();
3774 while ( $websites && ( $website = static::fetch_object( $websites ) ) ) {
3775 $re = array(
3776 'id' => $website->id,
3777 'url' => MainWP_Utility::get_nice_url( $website->url, true ),
3778 'name' => $website->name,
3779 'totalsize' => $website->totalsize,
3780 'sync_errors' => $website->sync_errors,
3781 'client_id' => $website->client_id,
3782 );
3783
3784 if ( 0 < $totalRecords ) {
3785 $re['totalRecords'] = $totalRecords;
3786 $totalRecords = 0;
3787 }
3788
3789 $output[] = $re;
3790 }
3791 static::free_result( $websites );
3792
3793 return $output;
3794 }
3795
3796 /**
3797 * Method get_lookup_items().
3798 *
3799 * Get bulk lookup items to reduce number of db queries.
3800 *
3801 * @param string $item_name lookup item name.
3802 * @param int $item_id lookup item id.
3803 * @param string $obj_name loockup object name.
3804 *
3805 * @return mixed Result
3806 */
3807 public function get_lookup_items( $item_name, $item_id, $obj_name ) {
3808 return $this->wpdb->get_results( $this->wpdb->prepare( 'SELECT * FROM ' . $this->table_name( 'lookup_item_objects' ) . ' WHERE item_name=%s AND item_id = %d AND object_name = %s', $item_name, $item_id, $obj_name ) ); //phpcs:ignore -- ok.
3809 }
3810
3811 /**
3812 * Method insert_lookup_item().
3813 *
3814 * Insert lookup item, need checks existed before to prevent double values.
3815 *
3816 * @param string $item_name item name.
3817 * @param int $item_id item id.
3818 * @param string $obj_name object name.
3819 * @param int $obj_id object id.
3820 *
3821 * @return mixed Result
3822 */
3823 public function insert_lookup_item( $item_name, $item_id, $obj_name, $obj_id ) {
3824 if ( empty( $item_name ) || empty( $item_id ) || empty( $obj_name ) || empty( $obj_id ) ) {
3825 return false;
3826 }
3827 $data = array(
3828 'item_name' => 'cost',
3829 'item_id' => $item_id,
3830 'object_name' => $obj_name,
3831 'object_id' => $obj_id,
3832 );
3833 $this->wpdb->insert( $this->table_name( 'lookup_item_objects' ), $data );
3834 return $this->wpdb->insert_id; // must return lookup id.
3835 }
3836
3837 /**
3838 * Method delete_lookup_items().
3839 *
3840 * Delete bulk lookup items by lookup ids or object names with item id and item name, to reduce number of db queries.
3841 *
3842 * @param string $by Delete by.
3843 * @param array $params params.
3844 *
3845 * @return mixed Result
3846 */
3847 public function delete_lookup_items( $by = 'lookup_id', $params = array() ) { // phpcs:ignore -- NOSONAR - complex.
3848 if ( ! is_array( $params ) ) {
3849 return false;
3850 }
3851
3852 $lookup_ids = isset( $params['lookup_ids'] ) ? $params['lookup_ids'] : null;
3853 $item_id = isset( $params['item_id'] ) ? $params['item_id'] : null;
3854 $object_id = isset( $params['object_id'] ) ? $params['object_id'] : null;
3855 $item_name = isset( $params['item_name'] ) ? $params['item_name'] : null;
3856 $obj_names = isset( $params['object_names'] ) ? $params['object_names'] : null;
3857
3858 if ( 'object_name' === $by ) {
3859 if ( empty( $item_id ) || empty( $item_name ) ) {
3860 return false;
3861 }
3862
3863 $obj_names = $this->escape_array( $obj_names );
3864 if ( ! empty( $obj_names ) ) {
3865 $this->wpdb->query( $this->wpdb->prepare( 'DELETE FROM ' . $this->table_name( 'lookup_item_objects' ) . ' WHERE item_name = %s AND item_id = %d AND object_name IN ("' . implode( '","', $obj_names ) . '") ', $item_name, $item_id ) ); //phpcs:ignore -- ok.
3866 return true;
3867 }
3868 } elseif ( 'object_id' === $by ) {
3869 if ( empty( $object_id ) || empty( $item_name ) || empty( $obj_names ) ) {
3870 return false;
3871 }
3872
3873 $obj_names = $this->escape_array( $obj_names );
3874 if ( ! empty( $obj_names ) ) {
3875 $this->wpdb->query( $this->wpdb->prepare( 'DELETE FROM ' . $this->table_name( 'lookup_item_objects' ) . ' WHERE item_name = %s AND object_id = %d AND object_name IN ("' . implode( '","', $obj_names ) . '") ', $item_name, $object_id ) ); //phpcs:ignore -- ok.
3876 return true;
3877 }
3878 } elseif ( 'lookup_id' === $by ) {
3879 if ( empty( $lookup_ids ) ) {
3880 return false;
3881 }
3882 if ( is_numeric( $lookup_ids ) ) {
3883 $lookup_ids = array( $lookup_ids );
3884 } elseif ( is_array( $lookup_ids ) ) {
3885 $lookup_ids = MainWP_Utility::array_numeric_filter( $lookup_ids );
3886 } else {
3887 return false;
3888 }
3889 $this->wpdb->query( 'DELETE FROM ' . $this->table_name( 'lookup_item_objects' ) . ' WHERE lookup_id IN (' . implode( ',', $lookup_ids ) . ') ' ); //phpcs:ignore -- ok.
3890 return true;
3891 }
3892 return false;
3893 }
3894
3895
3896 /**
3897 * Return the user data for the given consumer_key.
3898 *
3899 * @param string $consumer_key Consumer key.
3900 * @param string $consumer_secret Secret key.
3901 * @param string $scope scope.
3902 * @param string $description description.
3903 * @param int $enabled 1 or 0.
3904 * @param array $others others.
3905 *
3906 * @return array
3907 */
3908 public function insert_rest_api_key( $consumer_key, $consumer_secret, $scope, $description, $enabled, $others = array() ) {
3909 global $current_user;
3910
3911 if ( $current_user ) {
3912 $user_id = $current_user->ID;
3913 }
3914
3915 if ( empty( $user_id ) ) {
3916 return false;
3917 }
3918
3919 if ( ! is_array( $others ) ) {
3920 $others = array();
3921 }
3922
3923 $pass = isset( $others['key_pass'] ) ? $others['key_pass'] : '';
3924 $type = isset( $others['key_type'] ) ? intval( $others['key_type'] ) : 0;
3925
3926 // Created API keys.
3927 $permissions = in_array( $scope, array( 'read', 'write', 'delete', 'read_write' ), true ) ? sanitize_text_field( $scope ) : 'read';
3928 $this->wpdb->insert(
3929 $this->table_name( 'api_keys' ),
3930 array(
3931 'user_id' => $user_id,
3932 'description' => $description,
3933 'permissions' => $permissions,
3934 'consumer_key' => mainwp_api_hash( $consumer_key ),
3935 'consumer_secret' => $consumer_secret,
3936 'truncated_key' => substr( $consumer_key, -7 ),
3937 'enabled' => $enabled,
3938 'key_pass' => $pass,
3939 'key_type' => $type,
3940 ),
3941 array(
3942 '%d',
3943 '%s',
3944 '%s',
3945 '%s',
3946 '%s',
3947 '%s',
3948 '%d',
3949 '%s',
3950 '%d',
3951 ),
3952 );
3953
3954 return array(
3955 'key_id' => $this->wpdb->insert_id,
3956 'user_id' => $user_id,
3957 'consumer_key' => $consumer_key,
3958 'consumer_secret' => $consumer_secret,
3959 'key_permissions' => $permissions,
3960 );
3961 }
3962
3963 /**
3964 * Update rest api key.
3965 *
3966 * @param int $key_id Consumer key.
3967 * @param string $scope scope.
3968 * @param string $description description.
3969 * @param int $enabled Enabled.
3970 *
3971 * @return array
3972 */
3973 public function update_rest_api_key( $key_id, $scope, $description, $enabled = 1 ) {
3974 $permissions = in_array( $scope, array( 'read', 'write', 'delete', 'read_write' ), true ) ? sanitize_text_field( $scope ) : 'read';
3975 return $this->wpdb->update(
3976 $this->table_name( 'api_keys' ),
3977 array(
3978 'description' => $description,
3979 'permissions' => $permissions,
3980 'enabled' => $enabled ? 1 : 0,
3981 ),
3982 array(
3983 'key_id' => $key_id,
3984 )
3985 );
3986 }
3987
3988
3989 /**
3990 * Method is_existed_enabled_rest_key().
3991 *
3992 * @return bool result.
3993 */
3994 public function is_existed_enabled_rest_key() {
3995 $table_name = esc_sql( $this->table_name( 'api_keys' ) );
3996 $enabled = $this->wpdb->get_row( "SELECT * FROM {$table_name} WHERE enabled = 1 LIMIT 1" );
3997 return $enabled ? true : false;
3998 }
3999
4000 /**
4001 * Method get_rest_api_key_by().
4002 *
4003 * @param int $id To get key.
4004 *
4005 * @return array
4006 */
4007 public function get_rest_api_key_by( $id ) {
4008 $table_name = esc_sql( $this->table_name( 'api_keys' ) );
4009 return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$table_name} WHERE key_id = %d", $id ) );
4010 }
4011
4012 /**
4013 * Method remove_rest_api_key().
4014 *
4015 * @param string $id to delete.
4016 *
4017 * @return array
4018 */
4019 public function remove_rest_api_key( $id ) {
4020 $table_name = esc_sql( $this->table_name( 'api_keys' ) );
4021 return $this->wpdb->query( $this->wpdb->prepare( "DELETE FROM {$table_name} WHERE key_id = %s", $id ) );
4022 }
4023
4024 /**
4025 * Method get_rest_api_keys().
4026 *
4027 * @return array
4028 */
4029 public function get_rest_api_keys() {
4030 $table_name = esc_sql( $this->table_name( 'api_keys' ) );
4031 return $this->wpdb->get_results( "SELECT * FROM {$table_name} ORDER BY key_id DESC" );
4032 }
4033
4034
4035 /**
4036 * Update regular process.
4037 *
4038 * @param array $data process data.
4039 * @return mixed
4040 */
4041 public function update_regular_process( $data ) {
4042 if ( isset( $data['process_id'] ) ) {
4043 $process_id = $data['process_id'];
4044 unset( $data['process_id'] );
4045 return $this->wpdb->update( $this->table_name( 'schedule_processes' ), $data, array( 'process_id' => $process_id ) );
4046 } elseif ( is_array( $data ) && isset( $data['type'] ) && isset( $data['process_slug'] ) ) {
4047 return $this->wpdb->insert( $this->table_name( 'schedule_processes' ), $data );
4048 }
4049 return false;
4050 }
4051
4052 /**
4053 * Delete regular process.
4054 *
4055 * @param int $process_id Process id.
4056 * @param int $item_id Item id.
4057 * @param string $pro_type Process type.
4058 * @param string $pro_slug Process slug.
4059 *
4060 * @return mixed
4061 */
4062 public function delete_regular_process( $process_id = false, $item_id = false, $pro_type = false, $pro_slug = false ) {
4063
4064 if ( is_numeric( $process_id ) && ! empty( $process_id ) ) {
4065 return $this->wpdb->delete(
4066 $this->table_name( 'schedule_processes' ),
4067 array(
4068 'process_id' => $process_id,
4069 )
4070 );
4071 } elseif ( ! empty( $pro_type ) || ! empty( $pro_slug ) ) {
4072
4073 $data = array();
4074
4075 if ( ! empty( $pro_type ) ) {
4076 $data['type'] = $pro_type;
4077 }
4078
4079 if ( ! empty( $pro_slug ) ) {
4080 $data['process_slug'] = $pro_slug;
4081 }
4082
4083 if ( ! empty( $item_id ) ) {
4084 $data['item_id'] = $item_id;
4085 }
4086 // Bulk delete.
4087 return $this->wpdb->delete( $this->table_name( 'schedule_processes' ), $data );
4088 }
4089 return false;
4090 }
4091
4092 /**
4093 * Method get_regular_process_by_item_id_type_slug
4094 *
4095 * @param integer $item_id item id.
4096 * @param string $type type.
4097 * @param string $process_slug process slug.
4098 *
4099 * @return mixed result
4100 */
4101 public function get_regular_process_by_item_id_type_slug( $item_id, $type, $process_slug ) {
4102 $table_name = esc_sql( $this->table_name( 'schedule_processes' ) );
4103 return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT pr.* FROM {$table_name} pr WHERE pr.item_id = %d AND pr.type = %s AND pr.process_slug = %s", $item_id, $type, $process_slug ) );
4104 }
4105
4106 /**
4107 * Log SQL queries for debugging via hook.
4108 *
4109 * This method provides a structured way to log SQL queries for development/debugging.
4110 * It does NOT use error_log() directly - instead, it fires the `mainwp_log_system_query`
4111 * action hook, allowing external listeners (logging plugins, debug tools) to handle
4112 * the log output appropriately.
4113 *
4114 * To enable query logging:
4115 * 1. Set `$params['dev_log_query'] = 1` when calling database methods
4116 * 2. Add a listener to the `mainwp_log_system_query` action hook
4117 *
4118 * Example listener:
4119 * ```php
4120 * add_action( 'mainwp_log_system_query', function( $params, $sql, $caller ) {
4121 * error_log( 'MainWP Query: ' . $sql );
4122 * }, 10, 3 );
4123 * ```
4124 *
4125 * @param array $params Query parameters. Set 'dev_log_query' to enable logging.
4126 * @param string $sql The SQL query string.
4127 * @param mixed $caller Instance of caller class (optional).
4128 * @return void
4129 */
4130 public function log_system_query( $params, $sql, $caller = false ) {
4131 $params = apply_filters( 'mainwp_log_system_query_params', $params, $sql, $caller );
4132 if ( is_array( $params ) && ! empty( $params['dev_log_query'] ) && ! empty( $sql ) ) {
4133 do_action( 'mainwp_log_system_query', $params, $sql, $caller );
4134 }
4135 }
4136 }
4137