PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / trunk
MainWP Dashboard: Self-hosted WordPress Management for Agencies vtrunk
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-common.php

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

1,204 lines 41.0 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_Common
19 *
20 * @package MainWP\Dashboard
21 */
22 class MainWP_DB_Common extends MainWP_DB { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
23
24 // 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.
25
26 /**
27 * Private static variable to hold the single instance of the class.
28 *
29 * @static
30 *
31 * @var mixed Default null
32 */
33 private static $instance = null;
34
35 /**
36 * Method instance()
37 *
38 * Create public static instance.
39 *
40 * @static
41 * @return MainWP_DB_Common
42 */
43 public static function instance() {
44 if ( null === static::$instance ) {
45 static::$instance = new self();
46 }
47 return static::$instance;
48 }
49
50 /**
51 * Get the last sync status.
52 *
53 * @param string $is_staging Wether query on staging sites.
54 *
55 * @return array{
56 * sync_status: string|false,
57 * last_sync: int
58 * }
59 */
60 public function get_last_sync_status( $is_staging = 'no' ) {
61
62 $cutoff = time() - DAY_IN_SECONDS;
63
64 $site_table = $this->table_name( 'wp' );
65 $sync_table = $this->table_name( 'wp_sync' );
66
67 $sql = $this->wpdb->prepare(
68 "
69 SELECT
70 COUNT(*) AS total_sites,
71 SUM(CASE WHEN s.dtsSync >= %d THEN 1 ELSE 0 END) AS synced_sites,
72 MAX(s.dtsSync) AS last_sync
73 FROM {$site_table} w
74 INNER JOIN {$sync_table} s ON s.wpid = w.id
75 WHERE s.sync_errors = ''
76 " . $this->get_sql_where_allow_access_sites( 'w', $is_staging ),
77 $cutoff
78 );
79
80 $stats = $this->wpdb->get_row( $sql );
81
82 $return = array(
83 'sync_status' => false,
84 'last_sync' => 0,
85 );
86
87 if ( empty( $stats ) || 0 === (int) $stats->total_sites ) {
88 $return['sync_status'] = 'all_synced';
89 return $return;
90 }
91
92 $total_sites = (int) $stats->total_sites;
93 $synced_sites = (int) $stats->synced_sites;
94
95 if ( $total_sites === $synced_sites ) {
96 $return['sync_status'] = 'all_synced';
97 } elseif ( 0 === $synced_sites ) {
98 $return['sync_status'] = 'not_synced';
99 }
100
101 $return['last_sync'] = (int) $stats->last_sync;
102
103 return $return;
104 }
105
106 /**
107 * Method get_group_by_name()
108 *
109 * Get group by name.
110 *
111 * @param mixed $name Group name.
112 * @param null $userid user ID.
113 *
114 * @return object|null Database query result for chosen group name or null on failure
115 *
116 * @uses \MainWP\Dashboard\MainWP_System::is_multi_user()
117 */
118 public function get_group_by_name( $name, $userid = null ) {
119 if ( ( null === $userid ) && MainWP_System::instance()->is_multi_user() ) {
120
121 /**
122 * Current user global.
123 *
124 * @global string
125 */
126 global $current_user;
127
128 $userid = $current_user->ID;
129 }
130 $where = ( null !== $userid ) ? ' AND userid=' . intval( $userid ) : '';
131 $where .= $this->get_sql_where_allow_groups();
132
133 $table_group = esc_sql( $this->table_name( 'group' ) );
134 return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM `{$table_group}` WHERE 1 " . esc_sql( $where ) . ' AND name= %s', $this->escape( $name ) ) );
135 }
136
137 /**
138 * Method get_group_by_id()
139 *
140 * Get group by ID.
141 *
142 * @param mixed $id Group ID.
143 *
144 * @return object|null Database query result for chosen Group ID or null on failure.
145 *
146 * @uses \MainWP\Dashboard\MainWP_Utility::ctype_digit()
147 */
148 public function get_group_by_id( $id ) {
149 if ( MainWP_Utility::ctype_digit( $id ) ) {
150 $table_group = esc_sql( $this->table_name( 'group' ) );
151 return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM `{$table_group}` WHERE id= %d", $id ) );
152 }
153
154 return null;
155 }
156
157 /**
158 * Method get_groups_for_manage_sites()
159 *
160 * Get groups for mananged sites.
161 *
162 * @return object|null Database query result for Managed Sites Groups or null on failure.
163 *
164 * @uses \MainWP\Dashboard\MainWP_System::is_multi_user()
165 */
166 public function get_groups_for_manage_sites() {
167 $where = ' 1 ';
168 if ( MainWP_System::instance()->is_multi_user() ) {
169
170 /**
171 * Current user global.
172 *
173 * @global string
174 */
175 global $current_user;
176
177 $where = ' userid = ' . $current_user->ID . ' ';
178 }
179 $with_staging = 'yes';
180 $staging_enabled = is_plugin_active( 'mainwp-staging-extension/mainwp-staging-extension.php' ) || is_plugin_active( 'mainwp-timecapsule-extension/mainwp-timecapsule-extension.php' );
181
182 if ( ! $staging_enabled ) {
183 $with_staging = 'no';
184 }
185
186 $where .= $this->get_sql_where_allow_groups( '', $with_staging );
187
188 $table_group = esc_sql( $this->table_name( 'group' ) );
189 return $this->wpdb->get_results( "SELECT * FROM `{$table_group}` WHERE " . esc_sql( $where ) . ' ORDER BY name', OBJECT_K );
190 }
191
192
193 /**
194 * Method get_sql_where_wpopt_phpversion().
195 *
196 * @param string $coln Column compare.
197 * @param string $operator Operator compare.
198 * @param string $ver_str Version compare.
199 *
200 * @return string Sql version compare.
201 */
202 public function get_sql_where_wpopt_phpversion( $coln, $operator, $ver_str ) {
203 $allowed_columns = array( 'phpversion' );
204 $allowed_operators = array( '>', '>=', '<', '<=', '=', '!=' );
205 if ( ! in_array( $coln, $allowed_columns, true ) || ! in_array( $operator, $allowed_operators, true ) ) {
206 return ' 0 = 1 ';
207 }
208 $alias = 'owp_' . $coln;
209 return $this->wpdb->prepare(
210 " ( {$alias}.value IS NOT NULL AND INET_ATON( SUBSTRING_INDEX( CONCAT(SUBSTRING_INDEX( {$alias}.value, '-', 1), '.0.0.0.0'), '.', 4 ) ) {$operator} INET_ATON(%s) ) ", //phpcs:ignore -- NOSONAR escaped variables.
211 $ver_str
212 );
213 }
214
215 /**
216 * Method get_sql_version_compare().
217 *
218 * @param string $coln Column compare.
219 * @param string $operator Operator compare.
220 * @param string $ver_str Version compare.
221 *
222 * @return string Sql version compare.
223 */
224 public function get_sql_version_compare( $coln, $operator, $ver_str ) {
225 // It's safe since it's not user input, but the AI still suggests escaping it.
226 return ' INET_ATON( SUBSTRING_INDEX( CONCAT( SUBSTRING_INDEX(' . $this->escape( $coln ) . ", '-', 1), '.0.0.0.0' ), '.', 4) ) " .
227 $this->escape( $operator ) . " INET_ATON('" . $this->escape( $ver_str ) . "') ";
228 }
229
230 /**
231 * Method get_groups_for_current_user()
232 *
233 * Get groups for current user.
234 *
235 * @return object|null Database query result for Current User Groups or null on failure.
236 *
237 * @uses \MainWP\Dashboard\MainWP_System::is_multi_user()
238 */
239 public function get_groups_for_current_user() {
240 $where = ' 1 ';
241 if ( MainWP_System::instance()->is_multi_user() ) {
242
243 /**
244 * Current user global.
245 *
246 * @global string
247 */
248 global $current_user;
249
250 $where = ' userid = ' . $current_user->ID . ' ';
251 }
252 $where .= $this->get_sql_where_allow_groups();
253
254 $table_group = esc_sql( $this->table_name( 'group' ) );
255 return $this->wpdb->get_results( "SELECT * FROM `{$table_group}` WHERE " . esc_sql( $where ) . ' ORDER BY name', OBJECT_K );
256 }
257
258 /**
259 * Method get_groups_by_website_id()
260 *
261 * Get groups by website ID.
262 *
263 * @param mixed $websiteid Child Site ID.
264 *
265 * @return object|null Database query result for groups by website ID or null on failure.
266 *
267 * @uses \MainWP\Dashboard\MainWP_Utility::ctype_digit()
268 */
269 public function get_groups_by_website_id( $websiteid ) {
270 if ( MainWP_Utility::ctype_digit( $websiteid ) ) {
271 $table_group = esc_sql( $this->table_name( 'group' ) );
272 $table_wp_group = esc_sql( $this->table_name( 'wp_group' ) );
273 return $this->wpdb->get_results(
274 $this->wpdb->prepare(
275 "SELECT * FROM `{$table_group}` gr JOIN `{$table_wp_group}` wpgr ON gr.id = wpgr.groupid WHERE wpgr.wpid = %d ORDER BY name",
276 $websiteid
277 ),
278 OBJECT_K
279 );
280 }
281
282 return null;
283 }
284
285 /**
286 * Medthod get_groups_and_count()
287 *
288 * Get groups and count.
289 *
290 * @param null $userid Current user ID.
291 * @param bool $for_manager Default: false.
292 *
293 * @return object|null Database query result for groups and count or null on failure.
294 *
295 * @uses \MainWP\Dashboard\MainWP_System::is_multi_user()
296 */
297 public function get_groups_and_count( $userid = null, $for_manager = false ) {
298 if ( ( null === $userid ) && MainWP_System::instance()->is_multi_user() ) {
299
300 /**
301 * Current user global.
302 *
303 * @global string
304 */
305 global $current_user;
306
307 $userid = $current_user->ID;
308 }
309
310 $where = '';
311
312 if ( ! empty( $userid ) ) {
313 $where = ' AND gr.userid = ' . intval( $userid );
314 }
315
316 if ( ! $for_manager ) {
317 $where .= $this->get_sql_where_allow_groups( 'gr' );
318 }
319
320 $table_group = esc_sql( $this->table_name( 'group' ) );
321 $table_wp_group = esc_sql( $this->table_name( 'wp_group' ) );
322 $where = esc_sql( $where );
323 return $this->wpdb->get_results( "SELECT gr.*, COUNT(DISTINCT(wpgr.wpid)) as nrsites FROM `{$table_group}` gr LEFT JOIN `{$table_wp_group}` wpgr ON gr.id = wpgr.groupid WHERE 1 {$where} GROUP BY gr.id ORDER BY gr.name", OBJECT_K );
324 }
325
326
327 /**
328 * Method get_tags()
329 *
330 * Get tags (groups) with optional filtering and pagination.
331 *
332 * @since 5.1.1
333 *
334 * @param array $params Optional parameters for filtering and pagination.
335 * - 's' (string) Search term for tag name or ID.
336 * - 'exclude' (array) Tag IDs to exclude.
337 * - 'include' (array) Tag IDs to include.
338 * - 'page' (int) Page number for pagination.
339 * - 'per_page' (int) Items per page for pagination.
340 * - 'with_sites_ids' (bool) Include associated site IDs.
341 * - 'count' (bool) Return count only instead of results.
342 *
343 * @return object[]|int When $params['count'] is true, returns an integer count of matching tags.
344 * Otherwise, returns an array of tag objects keyed by ID, or empty array on failure.
345 */
346 public function get_tags( $params = array() ) { //phpcs:ignore -- NOSONAR - complex.
347
348 $s = '';
349 $exclude = array();
350 $include = array();
351 $limit = '';
352
353 $where = '';
354 $select = '';
355
356 if ( $params && is_array( $params ) ) {
357 $s = isset( $params['s'] ) ? $params['s'] : '';
358 $exclude = isset( $params['exclude'] ) ? wp_parse_id_list( $params['exclude'] ) : array();
359 $include = isset( $params['include'] ) ? wp_parse_id_list( $params['include'] ) : array();
360 $page = isset( $params['page'] ) ? intval( $params['page'] ) : false;
361 $per_page = isset( $params['per_page'] ) ? intval( $params['per_page'] ) : false;
362 $with_sites_ids = isset( $params['with_sites_ids'] ) && $params['with_sites_ids'] ? true : false;
363 $count_only = ! empty( $params['count'] );
364
365 if ( $with_sites_ids ) {
366 $select .= ', wp_tagview.* ';
367 }
368
369 if ( ! empty( $s ) ) {
370 $s = trim( $s );
371 $like_pattern = '%' . $this->wpdb->esc_like( $s ) . '%';
372 $where .= $this->wpdb->prepare(
373 ' AND ( gr.name LIKE %s OR gr.id LIKE %s ) ',
374 $like_pattern,
375 $like_pattern
376 );
377 }
378
379 if ( ! empty( $exclude ) ) {
380 $where .= ' AND gr.id NOT IN (' . implode( ',', $exclude ) . ') ';
381 }
382
383 if ( ! empty( $include ) ) {
384 $where .= ' AND gr.id IN (' . implode( ',', $include ) . ') ';
385 }
386
387 $gr_table = esc_sql( $this->table_name( 'group' ) );
388
389 // Return count only if requested.
390 if ( $count_only ) {
391 return (int) $this->wpdb->get_var( 'SELECT COUNT(*) FROM ' . $gr_table . ' gr WHERE 1 ' . $where ); // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $where is built with wpdb->prepare() and escaped table name, safe to use directly.
392 }
393
394 if ( ! empty( $page ) && ! empty( $per_page ) ) {
395 $limit = $this->wpdb->prepare( ' LIMIT %d, %d', ( $page - 1 ) * $per_page, $per_page );
396 }
397
398 $join = '';
399
400 if ( $with_sites_ids ) {
401 $join = ' JOIN ' . $this->get_tag_view() . ' wp_tagview ON gr.id = wp_tagview.id ';
402 }
403 }
404 $table_group = esc_sql( $this->table_name( 'group' ) );
405 $table_wp_group = esc_sql( $this->table_name( 'wp_group' ) );
406 $select_sql = $select ? $select : '';
407 $join_sql = $join ? $join : '';
408 $where_sql = $where ? $where : '';
409 $limit_sql = $limit ? $limit : '';
410 $query = "SELECT gr.* {$select_sql}, COUNT(DISTINCT(wpgr.wpid)) as count_sites FROM `{$table_group}` gr LEFT JOIN `{$table_wp_group}` wpgr ON gr.id = wpgr.groupid {$join_sql} WHERE 1 {$where_sql} GROUP BY gr.id ORDER BY gr.name {$limit_sql}";
411 return $this->wpdb->get_results( $query, OBJECT_K ); // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $query is built with proper escaping and parameterization above.
412 }
413
414 /**
415 * Method get_tag_view().
416 *
417 * @return string tag view.
418 */
419 public function get_tag_view() {
420 $view = "( SELECT intgr.id, ( SELECT GROUP_CONCAT(wp.id ORDER BY wp.id SEPARATOR ',') FROM `" . $this->table_name( 'wp' ) . '` wp ';
421 $view .= ' LEFT JOIN `' . $this->table_name( 'wp_group' ) . '` wpgr ON wp.id = wpgr.wpid WHERE wpgr.groupid = intgr.id ) as sites_ids ';
422 $view .= ' FROM `' . $this->table_name( 'group' ) . '` intgr )';
423 return $view;
424 }
425
426
427 /**
428 * Method get_not_empty_groups()
429 *
430 * Get non-empty groups.
431 *
432 * @param mixed $userid Current user ID.
433 * @param bool $enableOfflineSites Include offline sites? Default: true.
434 *
435 * @return object|null Database query result for non-empty groups or null on failure.
436 *
437 * @uses \MainWP\Dashboard\MainWP_System::is_multi_user()
438 */
439 public function get_not_empty_groups( $userid = null, $enableOfflineSites = true ) { //phpcs:ignore --NOSONAR -- complex.
440 if ( ( null === $userid ) && MainWP_System::instance()->is_multi_user() ) {
441
442 /**
443 * Current user global.
444 *
445 * @global string
446 */
447 global $current_user;
448
449 $userid = $current_user->ID;
450 }
451
452 $table_group = esc_sql( $this->table_name( 'group' ) );
453 $table_wp_group = esc_sql( $this->table_name( 'wp_group' ) );
454 $table_wp = esc_sql( $this->table_name( 'wp' ) );
455 $table_wp_sync = esc_sql( $this->table_name( 'wp_sync' ) );
456
457 $sql = "SELECT DISTINCT(g.id), g.name, count(wp.wpid) FROM `{$table_group}` g JOIN `{$table_wp_group}` wp ON g.id = wp.groupid JOIN `{$table_wp}` wpsite ON wp.wpid = wpsite.id JOIN `{$table_wp_sync}` wp_sync ON wp.wpid = wp_sync.wpid WHERE 1 = 1";
458 $params = array();
459
460 $staging_group = get_option( 'mainwp_stagingsites_group_id' );
461 if ( $staging_group ) {
462 $sql .= ' AND g.id <> %d';
463 $params[] = absint( $staging_group );
464 }
465
466 $allowed_groups = apply_filters( 'mainwp_currentuserallowedaccessgroups', 'all' );
467 if ( 'all' !== $allowed_groups ) {
468 if ( is_array( $allowed_groups ) && ! empty( $allowed_groups ) ) {
469 $allowed_groups = array_filter(
470 $allowed_groups,
471 function ( $e ) {
472 return is_numeric( $e ) ? true : false;
473 }
474 );
475
476 if ( ! empty( $allowed_groups ) ) {
477 $placeholders = implode( ',', array_fill( 0, count( $allowed_groups ), '%d' ) );
478 $sql .= ' AND g.id IN (' . $placeholders . ')';
479 $params = array_merge( $params, array_map( 'intval', $allowed_groups ) );
480 }
481 } else {
482 $sql .= ' AND 0';
483 }
484 }
485
486 if ( null !== $userid ) {
487 $sql .= ' AND g.userid = %d';
488 $params[] = intval( $userid );
489 }
490
491 if ( ! $enableOfflineSites ) {
492 $sql .= " AND wp_sync.sync_errors = ''";
493 }
494
495 $sql .= ' GROUP BY g.id HAVING count(wp.wpid) > 0 ORDER BY g.name';
496
497 if ( ! empty( $params ) ) {
498 $sql = $this->wpdb->prepare( $sql, ...$params );
499 }
500
501 return $this->wpdb->get_results( $sql, OBJECT_K ); // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $sql is prepared via $wpdb->prepare() with all dynamic values properly parameterized.
502 }
503
504 /**
505 * Method get_sql_log()
506 *
507 * Get sql log.
508 *
509 * @param int $paged paged.
510 * @param int $order order.
511 * @param array $params params.
512 *
513 * @return string sql query.
514 */
515 public function get_sql_log( $paged = 0, $order = '', $params = array() ) {
516
517 $count_only = ! empty( $params['count'] ) ? true : false;
518 $limit = ! empty( $params['limit'] ) ? intval( $params['limit'] ) : 500;
519
520 $last_hours = ! empty( $params['hour'] ) ? intval( $params['hour'] ) : 0;
521
522 $order = strtoupper( $order );
523
524 $order = 'DESC' === $order || 'ASC' === $order ? $order : 'DESC';
525
526 $start = ! empty( $paged ) ? absint( $paged * $limit ) : 0;
527
528 if ( $count_only ) {
529 return 'SELECT count(*)
530 FROM `' . $this->table_name( 'action_log' ) . '` log
531 WHERE 1 ';
532 }
533
534 if ( ! empty( $last_hours ) ) {
535 return 'SELECT log.*
536 FROM `' . $this->table_name( 'action_log' ) . '` log
537 WHERE ' . $this->wpdb->prepare( ' log_timestamp > %d ', time() - $last_hours * HOUR_IN_SECONDS ) .
538 ' ORDER BY log_timestamp ' . $this->escape( $order );
539 }
540
541 return 'SELECT log.*
542 FROM `' . $this->table_name( 'action_log' ) . '` log
543 WHERE 1 ORDER BY ' .
544 $this->wpdb->prepare( 'log_timestamp ' . $this->escape( $order ) . ' LIMIT %d, %d', $start, $limit );
545 }
546
547 /**
548 * Method insert_action_log()
549 *
550 * Insert action log.
551 *
552 * @param array $data log data.
553 *
554 * @return void
555 */
556 public function insert_action_log( $data ) {
557 $this->wpdb->insert( $this->table_name( 'action_log' ), $data );
558 }
559
560 /**
561 * Method delete_action_log()
562 *
563 * Delete action log.
564 *
565 * @param int $days number days.
566 *
567 * @return void
568 */
569 public function delete_action_log( $days = false ) {
570 $where = '';
571 if ( ! empty( $days ) ) {
572 $where .= ' AND log_timestamp < ' . ( time() - $days * DAY_IN_SECONDS );
573 }
574 $table_action_log = esc_sql( $this->table_name( 'action_log' ) );
575 $where = esc_sql( $where );
576 $this->wpdb->query( "DELETE FROM `{$table_action_log}` WHERE 1 {$where}" );
577 }
578
579 /**
580 * Method insert_or_update_request_log()
581 *
582 * Insert or update request log.
583 *
584 * @param mixed $wpid WordPress ID.
585 * @param mixed $ip IP address.
586 * @param mixed $start Start time.
587 * @param mixed $stop Stop Time.
588 *
589 * @return void
590 */
591 public function insert_or_update_request_log( $wpid, $ip, $start, $stop ) {
592 $updateValues = array();
593 if ( ! empty( $ip ) ) {
594 $updateValues['ip'] = $ip;
595 }
596 if ( ! empty( $start ) ) {
597 $updateValues['micro_timestamp_start'] = $start;
598 }
599 if ( ! empty( $stop ) ) {
600 $updateValues['micro_timestamp_stop'] = $stop;
601 }
602
603 $table_request_log = esc_sql( $this->table_name( 'request_log' ) );
604 $var = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT id FROM `{$table_request_log}` WHERE wpid = %d ", $wpid ) );
605 if ( null !== $var ) {
606 $this->wpdb->update( $this->table_name( 'request_log' ), $updateValues, array( 'wpid' => $wpid ) );
607 } else {
608 $updateValues['wpid'] = $wpid;
609 $this->wpdb->insert( $this->table_name( 'request_log' ), $updateValues );
610 }
611 }
612
613 /**
614 * Method close_open_requests()
615 *
616 * Close open request.
617 *
618 * @return void
619 */
620 public function close_open_requests() {
621 $table_request_log = esc_sql( $this->table_name( 'request_log' ) );
622 $microtime_value = esc_sql( microtime( true ) );
623 $this->wpdb->query( "UPDATE `{$table_request_log}` SET micro_timestamp_stop = micro_timestamp_start WHERE micro_timestamp_stop < micro_timestamp_start and {$microtime_value} - micro_timestamp_start > 7" );
624 }
625
626 /**
627 * Method get_nrof_open_requests()
628 *
629 * Get number of requests.
630 *
631 * @param null $ip IP Address.
632 *
633 * @return (string|null) Database query result for number of requests or null on failure.
634 */
635 public function get_nrof_open_requests( $ip = null ) {
636 $table_request_log = esc_sql( $this->table_name( 'request_log' ) );
637 if ( null === $ip ) {
638 return $this->wpdb->get_var( "select count(id) from `{$table_request_log}` where micro_timestamp_stop < micro_timestamp_start" ); // phpcs:ignore -- NOSONAR - table name escaped.
639 }
640
641 return $this->wpdb->get_var(
642 $this->wpdb->prepare(
643 "SELECT COUNT(id) FROM `{$table_request_log}` WHERE micro_timestamp_stop < micro_timestamp_start AND ip = %s", // phpcs:ignore -- NOSONAR - table name escaped.
644 $ip
645 )
646 );
647 }
648
649 /**
650 * Method get_last_request_timestamp()
651 *
652 * Get timestamp of last request sent.
653 *
654 * @param null $ip Child Site IP address, default: null.
655 *
656 * @return (int|null) Database query result for timestamp of last request sent or null on failure.
657 */
658 public function get_last_request_timestamp( $ip = null ) {
659 $table_request_log = esc_sql( $this->table_name( 'request_log' ) );
660 if ( null === $ip ) {
661 return $this->wpdb->get_var( "select micro_timestamp_start from `{$table_request_log}` order by micro_timestamp_start desc limit 1" );
662 }
663
664 return $this->wpdb->get_var( $this->wpdb->prepare( "SELECT micro_timestamp_start FROM `{$table_request_log}` WHERE ip = %s order by micro_timestamp_start desc limit 1", esc_sql( $ip ) ) );
665 }
666
667 /**
668 * Method update_group_site()
669 *
670 * @param mixed $groupId Group ID.
671 * @param mixed $websiteId Child Site ID.
672 *
673 * @return void
674 */
675 public function update_group_site( $groupId, $websiteId ) {
676 $this->wpdb->insert(
677 $this->table_name( 'wp_group' ),
678 array(
679 'wpid' => $websiteId,
680 'groupid' => $groupId,
681 )
682 );
683 }
684
685 /**
686 * Method clear_group()
687 *
688 * Clear sites in group.
689 *
690 * @param mixed $groupId ID of group.
691 * @param mixed $exclude_wpids Empty or array of wp ids to exclude.
692 */
693 public function clear_group( $groupId, $exclude_wpids = array() ) {
694 $this->wpdb->query( 'DELETE FROM ' . $this->table_name( 'wp_group' ) . ' WHERE groupid=' . $groupId . ( ! empty( $exclude_wpids ) && is_array( $exclude_wpids ) ? ' AND wpid NOT IN (' . implode( ',', array_map( 'intval', $exclude_wpids ) ) . ')' : '' ) );
695 }
696
697
698 /**
699 * Method add_group()
700 *
701 * Add group.
702 *
703 * @param mixed $userid Current User ID.
704 * @param mixed $name Name of group to add.
705 * @param mixed $color Color of group to add.
706 *
707 * @return boolean true
708 *
709 * @uses \MainWP\Dashboard\MainWP_Utility::ctype_digit()
710 * @uses \MainWP\Dashboard\MainWP_Utility::ctype_digit()
711 */
712 public function add_group( $userid, $name, $color = '' ) {
713 if ( MainWP_Utility::ctype_digit( $userid ) && $this->wpdb->insert(
714 $this->table_name( 'group' ),
715 array(
716 'userid' => $userid,
717 'name' => $this->escape( $name ),
718 'color' => $this->escape( $color ),
719 )
720 ) ) {
721
722 $groupId = $this->wpdb->insert_id;
723
724 $group = $this->get_group_by_id( $groupId );
725
726 /**
727 * Fires after a new sites tag has been created.
728 *
729 * @param object $group group created.
730 * @param string group action.
731 */
732 do_action( 'mainwp_site_tag_action', $group, 'created' );
733
734 return $groupId;
735 }
736
737 return false;
738 }
739
740 /**
741 * Method add_tag()
742 *
743 * Add Group.
744 *
745 * @param array $params params data.
746 */
747 public function add_tag( $params = array() ) {
748 /**
749 * Current user global.
750 *
751 * @global string
752 */
753 global $current_user;
754 //phpcs:disable WordPress.Security.NonceVerification.Missing
755 $groupId = isset( $params['id'] ) ? intval( $params['id'] ) : 0;
756 $newName = isset( $params['name'] ) ? sanitize_text_field( wp_unslash( $params['name'] ) ) : '';
757 $newColor = null;
758
759 if ( isset( $params['color'] ) ) {
760 $newColor = sanitize_hex_color( wp_unslash( $params['color'] ) );
761 }
762 //phpcs:enable WordPress.Security.NonceVerification.Missing
763
764 if ( ! empty( $groupId ) ) {
765 $table_group = esc_sql( $this->table_name( 'group' ) );
766 if ( null !== $newColor ) {
767 $this->wpdb->query( $this->wpdb->prepare( "UPDATE `{$table_group}` SET name=%s, color=%s WHERE id=%d", $newName, $newColor, $groupId ) );
768 } else {
769 $this->wpdb->query( $this->wpdb->prepare( "UPDATE `{$table_group}` SET name=%s WHERE id=%d", $newName, $groupId ) );
770 }
771 return $this->get_group_by_id( $groupId );
772 } elseif ( ! empty( $newName ) ) {
773 $groupId = $this->add_group( $current_user->ID, MainWP_Manage_Groups::check_group_name( $newName ), $newColor );
774
775 /**
776 * New Group Added
777 *
778 * Fires after a new sites group has been created.
779 *
780 * @param int $groupId Group ID.
781 */
782 do_action( 'mainwp_added_new_group', $groupId );
783 return $this->get_group_by_id( $groupId );
784 }
785 return false;
786 }
787
788 /**
789 * Method remove_group()
790 *
791 * Remove group.
792 *
793 * @param mixed $groupid Group ID.
794 *
795 * @return int|boolean Group that was deleted or false on failure.
796 *
797 * @uses \MainWP\Dashboard\MainWP_Utility::ctype_digit()
798 */
799 public function remove_group( $groupid ) {
800 if ( MainWP_Utility::ctype_digit( $groupid ) ) {
801 $group = $this->get_group_by_id( $groupid );
802 $table_group = esc_sql( $this->table_name( 'group' ) );
803 $table_wp_group = esc_sql( $this->table_name( 'wp_group' ) );
804 $nr = $this->wpdb->query( $this->wpdb->prepare( "DELETE FROM `{$table_group}` WHERE id=%d", $groupid ) );
805 $this->wpdb->query( $this->wpdb->prepare( "DELETE FROM `{$table_wp_group}` WHERE groupid=%d", $groupid ) );
806 if ( $nr ) {
807 /**
808 * Fires after a tag has been deleted.
809 *
810 * @param object $group group created.
811 * @param string group action.
812 */
813 do_action( 'mainwp_site_tag_action', $group, 'deleted' );
814 }
815 return $nr;
816 }
817
818 return false;
819 }
820
821 /**
822 * Method update_note()
823 *
824 * Update Note.
825 *
826 * @param mixed $websiteid Child Site ID.
827 * @param mixed $note Note data.
828 *
829 * @return void
830 */
831 public function update_note( $websiteid, $note ) {
832 $table_wp = esc_sql( $this->table_name( 'wp' ) );
833 $this->wpdb->query( $this->wpdb->prepare( "UPDATE `{$table_wp}` SET note= %s WHERE id=%d", $this->escape( $note ), $websiteid ) );
834 }
835
836 /**
837 * Method update_group()
838 *
839 * Update group.
840 *
841 * @param mixed $groupid Group ID.
842 * @param mixed $groupname Group Name.
843 * @param string $groupcolor Group Color.
844 *
845 * @return boolean true|false.
846 *
847 * @uses \MainWP\Dashboard\MainWP_Utility::ctype_digit()
848 */
849 public function update_group( $groupid, $groupname, $groupcolor ) {
850 if ( MainWP_Utility::ctype_digit( $groupid ) ) {
851 $table_group = esc_sql( $this->table_name( 'group' ) );
852 $this->wpdb->query( $this->wpdb->prepare( "UPDATE `{$table_group}` SET name=%s, color=%s WHERE id=%d", $this->escape( $groupname ), $this->escape( $groupcolor ), $groupid ) );
853
854 return true;
855 }
856
857 return false;
858 }
859
860 /**
861 * Method get_user_notification_email()
862 *
863 * Get user notification email.
864 *
865 * @param mixed $userid Current user ID.
866 *
867 * @return string $user_email User email address.
868 *
869 * @uses \MainWP\Dashboard\MainWP_System::is_single_user()
870 */
871 public function get_user_notification_email( $userid = 0 ) {
872 $theUserId = $userid;
873 if ( MainWP_System::instance()->is_single_user() ) {
874 $theUserId = 0;
875 }
876 $table_users = esc_sql( $this->table_name( 'users' ) );
877 $user_email = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT user_email FROM `{$table_users}` WHERE userid = %d", $theUserId ) );
878
879 if ( null === $user_email || empty( $user_email ) ) {
880 $user_email = $this->wpdb->get_var( $this->wpdb->prepare( 'SELECT user_email FROM `' . $this->wpdb->prefix . 'users` WHERE id = %d', $userid ) );
881 }
882
883 return $user_email;
884 }
885
886 /**
887 * Method get_user_extension()
888 *
889 * Get user extension.
890 *
891 * @return boolean|int false|get_user_extension_by_user_id()
892 *
893 * @uses \MainWP\Dashboard\MainWP_System::is_single_user()
894 */
895 public function get_user_extension() {
896
897 /**
898 * Current user global.
899 *
900 * @global string
901 */
902 global $current_user;
903
904 if ( empty( $current_user ) ) {
905 if ( MainWP_System::instance()->is_single_user() ) {
906 $userid = 0;
907 } else {
908 return false;
909 }
910 } else {
911 $userid = $current_user->ID;
912 }
913
914 return $this->get_user_extension_by_user_id( $userid );
915 }
916
917 /**
918 * Method get_user_extension_by_user_id()
919 *
920 * Get user extension by user id.
921 *
922 * @param mixed $userid Current user ID.
923 *
924 * @return object $row User extension.
925 *
926 * @uses \MainWP\Dashboard\MainWP_System::is_single_user()
927 */
928 public function get_user_extension_by_user_id( $userid = 0 ) {
929 if ( MainWP_System::instance()->is_single_user() ) {
930 $userid = 0;
931 }
932
933 $table_users = esc_sql( $this->table_name( 'users' ) );
934 $row = $this->wpdb->get_row( "SELECT * FROM `{$table_users}` WHERE userid= " . intval( $userid ), OBJECT );
935 if ( null === $row ) {
936 $this->create_user_extension( $userid );
937 $row = $this->wpdb->get_row( "SELECT * FROM `{$table_users}` WHERE userid= " . intval( $userid ), OBJECT );
938 }
939
940 return $row;
941 }
942
943 /**
944 * Method create_user_extension()
945 *
946 * Create user extension
947 *
948 * @param mixed $userId Current user ID.
949 *
950 * @return void
951 */
952 protected function create_user_extension( $userId ) {
953 $fields = array(
954 'userid' => $userId,
955 'user_email' => '',
956 'ignored_plugins' => '',
957 'trusted_plugins' => '',
958 'trusted_plugins_notes' => '',
959 'ignored_themes' => '',
960 'trusted_themes' => '',
961 'trusted_themes_notes' => '',
962 'pluginDir' => '',
963 'ignored_wp_upgrades' => '',
964 );
965
966 $this->wpdb->insert( $this->table_name( 'users' ), $fields );
967 }
968
969 /**
970 * Method update_user_extension()
971 *
972 * Update user extension.
973 *
974 * @param mixed $userExtension User extention to update.
975 *
976 * @return object $row User extension.
977 *
978 * @uses \MainWP\Dashboard\MainWP_System::is_single_user()
979 */
980 public function update_user_extension( $userExtension ) {
981
982 if ( is_object( $userExtension ) ) {
983 $userid = $userExtension->userid;
984 } elseif ( is_array( $userExtension ) ) {
985 $userid = $userExtension['userid'];
986 } else {
987 $userid = null;
988 }
989
990 if ( null === $userid ) {
991 if ( MainWP_System::instance()->is_single_user() ) {
992 $userid = '0';
993 } else {
994
995 /**
996 * Current user global.
997 *
998 * @global string
999 */
1000 global $current_user;
1001
1002 $userid = $current_user->ID;
1003 }
1004 }
1005 $table_users = esc_sql( $this->table_name( 'users' ) );
1006 $row = $this->wpdb->get_row( "SELECT * FROM `{$table_users}` WHERE userid= " . intval( $userid ), OBJECT );
1007 if ( null === $row ) {
1008 $this->create_user_extension( $userid );
1009 }
1010
1011 $fields = array();
1012 foreach ( $userExtension as $field => $value ) {
1013 if ( $value != $row->$field ) { //phpcs:ignore -- to valid.
1014 $fields[ $field ] = $value;
1015 }
1016 }
1017
1018 if ( ! empty( $fields ) ) {
1019 $this->wpdb->update( $this->table_name( 'users' ), $fields, array( 'userid' => $userid ) );
1020 }
1021
1022 return $this->wpdb->get_row( "SELECT * FROM `{$table_users}` WHERE userid= " . intval( $userid ), OBJECT );
1023 }
1024
1025
1026 /**
1027 * Method rest_api_update_website().
1028 *
1029 * Rest API update website.
1030 *
1031 * @param int $websiteid website ID.
1032 * @param array $data Update fields array.
1033 * 'http_user'.
1034 * 'http_pass'.
1035 * 'name'.
1036 * 'admin'.
1037 * 'sslversion'.
1038 * 'uniqueid'.
1039 * 'verify'.
1040 * 'protocol'.
1041 * 'checkinterval'.
1042 * 'disablehealthchecking'.
1043 * 'healththreshold'.
1044 * 'groupids'.
1045 * 'automatic_update'.
1046 * 'backup_before_upgrade'.
1047 * 'force_use_ipv4'.
1048 * 'ignore_core_updates'.
1049 * 'ignore_plugin_updates'.
1050 * 'ignore_theme_updates'.
1051 * 'monitoring_emails'.
1052 *
1053 * @return mixed array|true|false.
1054 */
1055 public function rest_api_update_website( $websiteid, $data ) { // phpcs:ignore -- NOSONAR - complex function.
1056
1057 $website = MainWP_DB::instance()->get_website_by_id( $websiteid );
1058 if ( empty( $website ) ) {
1059 return false;
1060 }
1061 $success = false;
1062
1063 $map_fields = array(
1064 'http_user' => 'http_user',
1065 'http_pass' => 'http_pass',
1066 'name' => 'name',
1067 'adminname' => 'admin',
1068 'ssl_version' => 'sslversion',
1069 'uniqueId' => 'uniqueid',
1070 );
1071
1072 $update_fields = array();
1073
1074 foreach ( $map_fields as $field => $name ) {
1075 if ( isset( $data[ $name ] ) && empty( ! $data[ $name ] ) ) {
1076 $value = $data[ $name ];
1077 // MWP-1548: encrypt http_user / http_pass at rest before
1078 // they hit the raw SQL UPDATE. Same fail-closed contract
1079 // as the other write paths in MainWP_DB.
1080 if ( 'http_user' === $field || 'http_pass' === $field ) {
1081 $encrypted = MainWP_Credential_Storage::encrypt_credential( $value, $field );
1082 if ( false === $encrypted ) {
1083 return false;
1084 }
1085 $value = $encrypted;
1086 }
1087 $update_fields[ $field ] = $value;
1088 }
1089 }
1090
1091 if ( isset( $data['verify'] ) ) {
1092 $verify = intval( $data['verify'] );
1093 $update_fields['verify_certificate'] = $verify;
1094 }
1095
1096 if ( isset( $data['protocol'] ) && ( 'http' === $data['protocol'] || 'https' === $data['protocol'] ) ) {
1097 $url = $data['protocol'] . '://' . MainWP_Utility::remove_http_prefix( $website->url, true );
1098 $update_fields['url'] = $this->escape( $url );
1099 }
1100
1101 if ( isset( $data['disablehealthchecking'] ) ) {
1102 $update_fields['disable_health_check'] = $data['disablehealthchecking'] ? 1 : 0;
1103 }
1104
1105 if ( isset( $data['healththreshold'] ) ) {
1106 $update_fields['health_threshold'] = intval( $data['healththreshold'] );
1107 }
1108
1109 if ( isset( $data['suspended'] ) ) {
1110 $update_fields['suspended'] = 1 === intval( $data['suspended'] ) ? 1 : 0;
1111 }
1112
1113 if ( ! empty( $update_fields ) ) {
1114 $updated = $this->wpdb->update(
1115 $this->table_name( 'wp' ),
1116 $update_fields,
1117 array( 'id' => $websiteid )
1118 );
1119 if ( false === $updated ) {
1120 return false;
1121 }
1122 $success = true;
1123
1124 if ( isset( $update_fields['url'] ) ) {
1125 // A user-set URL ( REST API / WP-CLI ) that diverges from the
1126 // child-reported address locks auto-correction for the site.
1127 MainWP_Site_Url_Corrector::after_user_set_url( $website, $update_fields['url'] );
1128 }
1129 }
1130
1131 $groupids = array();
1132 if ( isset( $data['groupids'] ) && ! empty( $data['groupids'] ) ) {
1133 $groupids = explode( ',', sanitize_text_field( wp_unslash( $data['groupids'] ) ) );
1134 }
1135
1136 if ( ! empty( $groupids ) ) {
1137 $table_wp_group = esc_sql( $this->table_name( 'wp_group' ) );
1138 $this->wpdb->query( $this->wpdb->prepare( "DELETE FROM `{$table_wp_group}` WHERE wpid=%d", $websiteid ) );
1139
1140 // update groups.
1141 foreach ( $groupids as $groupid ) {
1142 $this->wpdb->insert(
1143 $this->table_name( 'wp_group' ),
1144 array(
1145 'wpid' => $websiteid,
1146 'groupid' => $groupid,
1147 )
1148 );
1149 }
1150 $success = true;
1151 }
1152
1153 $newValues = array();
1154
1155 if ( isset( $data['automatic_update'] ) ) {
1156 $newValues['automatic_update'] = $data['automatic_update'] ? 1 : 0;
1157 }
1158
1159 if ( isset( $data['backup_before_upgrade'] ) ) {
1160 $newValues['backup_before_upgrade'] = $data['backup_before_upgrade'] ? 1 : 0;
1161 }
1162 if ( isset( $data['force_use_ipv4'] ) ) {
1163 $forceuseipv4 = intval( $data['force_use_ipv4'] );
1164 if ( 2 < $forceuseipv4 ) {
1165 $forceuseipv4 = 0;
1166 }
1167 $newValues['force_use_ipv4'] = $forceuseipv4;
1168 }
1169
1170 if ( isset( $data['ignore_core_updates'] ) ) {
1171 $newValues['is_ignoreCoreUpdates'] = $data['ignore_core_updates'] ? 1 : 0;
1172 }
1173
1174 if ( isset( $data['ignore_plugin_updates'] ) ) {
1175 $newValues['is_ignorePluginUpdates'] = $data['ignore_plugin_updates'] ? 1 : 0;
1176 }
1177
1178 if ( isset( $data['ignore_theme_updates'] ) ) {
1179 $newValues['is_ignoreThemeUpdates'] = $data['ignore_theme_updates'] ? 1 : 0;
1180 }
1181
1182 if ( isset( $data['client_id'] ) ) {
1183 $newValues['client_id'] = max( 0, intval( $data['client_id'] ) );
1184 }
1185
1186 if ( ! empty( $newValues ) ) {
1187 MainWP_DB::instance()->update_website_values( $website->id, $newValues );
1188 $success = true;
1189 }
1190
1191 if ( isset( $data['monitoring_emails'] ) ) {
1192 $monitoring_emails = MainWP_Utility::valid_input_emails( $data['monitoring_emails'] );
1193 MainWP_DB::instance()->update_website_option( $website, 'monitoring_notification_emails', ( $monitoring_emails ) );
1194
1195 }
1196
1197 return array(
1198 'message' => 'Site updated successfully.',
1199 'site' => $website->url,
1200 'success' => $success,
1201 );
1202 }
1203 }
1204