| @@ -176,11 +176,11 @@ | ||
| 176 | 176 | |
| 177 | 177 | /** |
| 178 | 178 | * Method get_wp_options_join(). |
| 179 | 179 | * |
| 180 | - * @param array $fields Extra option fields. | |
| 181 | - * @param string $view_query view query. | |
| 182 | - * @param array $params Additional parameters. | |
| 180 | + * @param array $fields Extra option fields. | |
| 181 | + * @param string $view_query view query. | |
| 182 | + * @param array<string, mixed> $params Additional parameters. | |
| 183 | 183 | |
| 184 | 184 | * |
| 185 | 185 | * 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. |
| 186 | 186 | * 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. |
| @@ -429,9 +429,9 @@ | ||
| 429 | 429 | $status_conds = array(); |
| 430 | 430 | if ( in_array( 'available_update', $status ) ) { |
| 431 | 431 | $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 <> '[]' ) "; |
| 432 | 432 | $table_name = esc_sql( $this->table_name( 'wp_options' ) ); |
| 433 | - $results = $this->wpdb->get_results( "SELECT wpid FROM {$table_name} WHERE name = 'wp_upgrades' AND value <> '' AND value <> '[]'" ); | |
| 433 | + $results = $this->wpdb->get_results( "SELECT wpid FROM {$table_name} WHERE name = 'wp_upgrades' AND value <> '' AND value <> '[]'" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd; the rest of the query is a literal. | |
| 434 | 434 | if ( $results ) { |
| 435 | 435 | $wp_ids = array(); |
| 436 | 436 | foreach ( $results as $item ) { |
| 437 | 437 | if ( ! empty( $item->wpid ) ) { |
| @@ -643,10 +643,11 @@ | ||
| 643 | 643 | 'ignored_trans_updates', |
| 644 | 644 | ); |
| 645 | 645 | } elseif ( in_array( $view, array( 'simple_view', 'base_view', 'monitor_view', 'ping_view', 'uptime_notification' ) ) ) { |
| 646 | 646 | $fields = array(); |
| 647 | - if ( 'monitor_view' === $view ) { | |
| 647 | + if ( 'monitor_view' === $view || 'ping_view' === $view ) { | |
| 648 | 648 | $fields[] = 'health_site_status'; |
| 649 | + $fields[] = 'bypass_cache'; | |
| 649 | 650 | } |
| 650 | 651 | } elseif ( 'custom_view' !== $view ) { |
| 651 | 652 | $fields = $default; |
| 652 | 653 | } |
| @@ -720,10 +721,11 @@ | ||
| 720 | 721 | 'ignored_trans_updates', |
| 721 | 722 | ); |
| 722 | 723 | } elseif ( in_array( $view, array( 'simple_view', 'base_view', 'monitor_view', 'ping_view', 'uptime_notification' ) ) ) { |
| 723 | 724 | $fields = array(); |
| 724 | - if ( 'monitor_view' === $view ) { | |
| 725 | + if ( 'monitor_view' === $view || 'ping_view' === $view ) { | |
| 725 | 726 | $fields[] = 'health_site_status'; |
| 727 | + $fields[] = 'bypass_cache'; | |
| 726 | 728 | } |
| 727 | 729 | } elseif ( 'custom_view' !== $view ) { |
| 728 | 730 | $fields = $default; |
| 729 | 731 | } |
| @@ -972,9 +974,9 @@ | ||
| 972 | 974 | return false; |
| 973 | 975 | } |
| 974 | 976 | |
| 975 | 977 | $table_name = esc_sql( $this->table_name( 'wp_options' ) ); |
| 976 | - $value = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT value FROM {$table_name} WHERE wpid = %d AND name = %s", $site_id, $option ) ); | |
| 978 | + $value = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT value FROM {$table_name} WHERE wpid = %d AND name = %s", $site_id, $option ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd. | |
| 977 | 979 | |
| 978 | 980 | if ( null === $value && null !== $default_value ) { |
| 979 | 981 | return $default_value; |
| 980 | 982 | } |
| @@ -1052,9 +1054,9 @@ | ||
| 1052 | 1054 | } |
| 1053 | 1055 | |
| 1054 | 1056 | $table_name = esc_sql( $this->table_name( 'wp_options' ) ); |
| 1055 | 1057 | $placeholders = implode( ',', array_fill( 0, count( $get_options ), '%s' ) ); |
| 1056 | - $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 ) ) ); | |
| 1058 | + $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 ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd; $placeholders is a generated list of %s. | |
| 1057 | 1059 | |
| 1058 | 1060 | $fill_options = array( |
| 1059 | 1061 | 'primary_lasttime_backup', |
| 1060 | 1062 | ); |
| @@ -1091,9 +1093,9 @@ | ||
| 1091 | 1093 | $site_id = $website->id; |
| 1092 | 1094 | } |
| 1093 | 1095 | |
| 1094 | 1096 | $table_name = esc_sql( $this->table_name( 'wp_options' ) ); |
| 1095 | - $rslt = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT name FROM {$table_name} WHERE wpid = %d AND name = %s", $site_id, $option ) ); | |
| 1097 | + $rslt = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT name FROM {$table_name} WHERE wpid = %d AND name = %s", $site_id, $option ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd. | |
| 1096 | 1098 | if ( empty( $rslt ) ) { |
| 1097 | 1099 | $this->wpdb->insert( |
| 1098 | 1100 | $this->table_name( 'wp_options' ), |
| 1099 | 1101 | array( |
| @@ -1138,9 +1140,9 @@ | ||
| 1138 | 1140 | } |
| 1139 | 1141 | |
| 1140 | 1142 | $table_name = esc_sql( $this->table_name( 'wp_options' ) ); |
| 1141 | 1143 | foreach ( $options as $opt ) { |
| 1142 | - $this->wpdb->query( $this->wpdb->prepare( "DELETE FROM {$table_name} WHERE wpid=%d AND name=%s", $site_id, $opt ) ); | |
| 1144 | + $this->wpdb->query( $this->wpdb->prepare( "DELETE FROM {$table_name} WHERE wpid=%d AND name=%s", $site_id, $opt ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd. | |
| 1143 | 1145 | } |
| 1144 | 1146 | } |
| 1145 | 1147 | |
| 1146 | 1148 | |
| @@ -1161,9 +1163,9 @@ | ||
| 1161 | 1163 | static::$general_options[] = array(); |
| 1162 | 1164 | } |
| 1163 | 1165 | |
| 1164 | 1166 | $table_name = esc_sql( $this->table_name( 'wp_options' ) ); |
| 1165 | - $val = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT value FROM {$table_name} WHERE wpid = %d AND name = %s", 0, $option ) ); | |
| 1167 | + $val = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT value FROM {$table_name} WHERE wpid = %d AND name = %s", 0, $option ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd. | |
| 1166 | 1168 | |
| 1167 | 1169 | static::$general_options[ $option ] = $val; |
| 1168 | 1170 | return $val; |
| 1169 | 1171 | } |
| @@ -1204,9 +1206,9 @@ | ||
| 1204 | 1206 | } |
| 1205 | 1207 | |
| 1206 | 1208 | $table_name = esc_sql( $this->table_name( 'wp_options' ) ); |
| 1207 | 1209 | $placeholders = implode( ',', array_fill( 0, count( $diff_options ), '%s' ) ); |
| 1208 | - $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 ) ) ); | |
| 1210 | + $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 ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd; $placeholders is a generated list of %s. | |
| 1209 | 1211 | |
| 1210 | 1212 | foreach ( (array) $options_db as $o ) { |
| 1211 | 1213 | $return_options[ $o->name ] = $o->value; |
| 1212 | 1214 | static::$general_options[ $o->name ] = $o->value; |
| @@ -1237,9 +1239,9 @@ | ||
| 1237 | 1239 | } |
| 1238 | 1240 | static::$general_options[ $option ] = $value; |
| 1239 | 1241 | |
| 1240 | 1242 | $table_name = esc_sql( $this->table_name( 'wp_options' ) ); |
| 1241 | - $rslt = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT name FROM {$table_name} WHERE wpid = %d AND name = %s", 0, $option ) ); | |
| 1243 | + $rslt = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT name FROM {$table_name} WHERE wpid = %d AND name = %s", 0, $option ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd. | |
| 1242 | 1244 | |
| 1243 | 1245 | if ( empty( $rslt ) ) { |
| 1244 | 1246 | $this->wpdb->insert( |
| 1245 | 1247 | $this->table_name( 'wp_options' ), |
| @@ -1508,9 +1510,9 @@ | ||
| 1508 | 1510 | $status_conds = array(); |
| 1509 | 1511 | if ( in_array( 'available_update', $status ) ) { |
| 1510 | 1512 | $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 <> '[]' ) "; |
| 1511 | 1513 | $options_table = esc_sql( $this->table_name( 'wp_options' ) ); |
| 1512 | - $results = $this->wpdb->get_results( "SELECT wpid FROM {$options_table} WHERE name = 'wp_upgrades' AND value <> '' AND value <> '[]'" ); | |
| 1514 | + $results = $this->wpdb->get_results( "SELECT wpid FROM {$options_table} WHERE name = 'wp_upgrades' AND value <> '' AND value <> '[]'" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd; the rest of the query is a literal. | |
| 1513 | 1515 | if ( $results ) { |
| 1514 | 1516 | $wp_ids = array(); |
| 1515 | 1517 | foreach ( $results as $item ) { |
| 1516 | 1518 | if ( ! empty( $item->wpid ) ) { |
| @@ -1970,9 +1972,12 @@ | ||
| 1970 | 1972 | $obj_data = MainWP_Utility::map_site( $website, $data ); |
| 1971 | 1973 | |
| 1972 | 1974 | if ( $full_data ) { |
| 1973 | 1975 | $sum_upgrades = 0; |
| 1974 | - if ( '' !== $obj_data->plugin_upgrades ) { | |
| 1976 | + // The option-backed columns are SQL NULL until a first sync writes them, and | |
| 1977 | + // map_site() copies a null property through, so an '' check alone leaves | |
| 1978 | + // json_decode() a null on PHP 8.1+. | |
| 1979 | + if ( ! empty( $obj_data->plugin_upgrades ) ) { | |
| 1975 | 1980 | $plugin_upgrades = json_decode( $obj_data->plugin_upgrades, true ); |
| 1976 | 1981 | if ( is_array( $plugin_upgrades ) ) { |
| 1977 | 1982 | $sum_upgrades += count( $plugin_upgrades ); |
| 1978 | 1983 | } |
| @@ -1977,9 +1982,9 @@ | ||
| 1977 | 1982 | $sum_upgrades += count( $plugin_upgrades ); |
| 1978 | 1983 | } |
| 1979 | 1984 | } |
| 1980 | 1985 | |
| 1981 | - if ( '' !== $obj_data->theme_upgrades ) { | |
| 1986 | + if ( ! empty( $obj_data->theme_upgrades ) ) { | |
| 1982 | 1987 | $theme_upgrades = json_decode( $obj_data->theme_upgrades, true ); |
| 1983 | 1988 | if ( is_array( $theme_upgrades ) ) { |
| 1984 | 1989 | $sum_upgrades += count( $theme_upgrades ); |
| 1985 | 1990 | } |
| @@ -1984,9 +1989,9 @@ | ||
| 1984 | 1989 | $sum_upgrades += count( $theme_upgrades ); |
| 1985 | 1990 | } |
| 1986 | 1991 | } |
| 1987 | 1992 | |
| 1988 | - if ( '' !== $obj_data->wp_upgrades ) { | |
| 1993 | + if ( ! empty( $obj_data->wp_upgrades ) ) { | |
| 1989 | 1994 | $wp_upgrades = json_decode( $obj_data->wp_upgrades, true ); |
| 1990 | 1995 | if ( is_array( $wp_upgrades ) ) { |
| 1991 | 1996 | $sum_upgrades += count( $wp_upgrades ); |
| 1992 | 1997 | } |
| @@ -2980,16 +2985,9 @@ | ||
| 2980 | 2985 | $sites_table = $this->table_name( 'wp' ); |
| 2981 | 2986 | $sync_table = $this->table_name( 'wp_sync' ); |
| 2982 | 2987 | |
| 2983 | 2988 | return $this->wpdb->get_row( |
| 2984 | - " | |
| 2985 | - SELECT | |
| 2986 | - COUNT(wp.id) AS total_sites, | |
| 2987 | - SUM(CASE WHEN s.sync_errors = '' THEN 1 ELSE 0 END) AS connected_sites, | |
| 2988 | - SUM(CASE WHEN s.sync_errors <> '' THEN 1 ELSE 0 END) AS disconnected_sites | |
| 2989 | - FROM {$sites_table} wp | |
| 2990 | - JOIN {$sync_table} s | |
| 2991 | - ON s.wpid = wp.id " . | |
| 2989 | + "SELECT COUNT(wp.id) AS total_sites, SUM(CASE WHEN s.sync_errors = '' THEN 1 ELSE 0 END) AS connected_sites, SUM(CASE WHEN s.sync_errors <> '' THEN 1 ELSE 0 END) AS disconnected_sites FROM {$sites_table} wp JOIN {$sync_table} s ON s.wpid = wp.id " . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table names come from table_name(), which is the DB prefix plus a hardcoded suffix. | |
| 2992 | 2990 | $this->get_sql_where_allow_access_sites( 'wp', $is_staging ), |
| 2993 | 2991 | ARRAY_A |
| 2994 | 2992 | ); |
| 2995 | 2993 | } |
| @@ -3163,9 +3161,9 @@ | ||
| 3163 | 3161 | * @return string|null Child site IP address or null on failure. |
| 3164 | 3162 | */ |
| 3165 | 3163 | public function get_wp_ip( $wpid ) { |
| 3166 | 3164 | $table_name = esc_sql( $this->table_name( 'request_log' ) ); |
| 3167 | - return $this->wpdb->get_var( $this->wpdb->prepare( "SELECT ip FROM {$table_name} WHERE wpid = %d", $wpid ) ); | |
| 3165 | + return $this->wpdb->get_var( $this->wpdb->prepare( "SELECT ip FROM {$table_name} WHERE wpid = %d", $wpid ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd. | |
| 3168 | 3166 | } |
| 3169 | 3167 | |
| 3170 | 3168 | /** |
| 3171 | 3169 | * Add website to the MainWP Dashboard. |
| @@ -3204,8 +3202,12 @@ | ||
| 3204 | 3202 | $http_pass = isset( $params['http_pass'] ) ? $params['http_pass'] : null; |
| 3205 | 3203 | $sslVersion = isset( $params['sslVersion'] ) ? $params['sslVersion'] : 0; |
| 3206 | 3204 | $wpe = isset( $params['wpe'] ) ? $params['wpe'] : 0; |
| 3207 | 3205 | $isStaging = isset( $params['isStaging'] ) ? $params['isStaging'] : 0; |
| 3206 | + // 2 means "use the global mainwp_forceUseIPv4 option" and mainwp_string_to_bool() | |
| 3207 | + // collapses it to 0, so it is kept before the boolean conversion. Null or absent | |
| 3208 | + // stays null and leaves the NOT NULL column at its default. | |
| 3209 | + $force_use_ipv4 = isset( $params['force_use_ipv4'] ) ? ( 2 === (int) $params['force_use_ipv4'] ? 2 : (int) mainwp_string_to_bool( $params['force_use_ipv4'] ) ) : null; | |
| 3208 | 3210 | |
| 3209 | 3211 | // MWP-1548: encrypt http_user / http_pass at rest. Empty / null |
| 3210 | 3212 | // values pass through unchanged (encrypt_credential is a no-op |
| 3211 | 3213 | // for those). A non-empty value that fails to encrypt produces |
| @@ -3264,8 +3266,12 @@ | ||
| 3264 | 3266 | 'wpe' => $wpe, |
| 3265 | 3267 | 'is_staging' => $isStaging, |
| 3266 | 3268 | ); |
| 3267 | 3269 | |
| 3270 | + if ( null !== $force_use_ipv4 ) { | |
| 3271 | + $values['force_use_ipv4'] = $force_use_ipv4; | |
| 3272 | + } | |
| 3273 | + | |
| 3268 | 3274 | $syncValues = array( |
| 3269 | 3275 | 'dtsSync' => 0, |
| 3270 | 3276 | 'dtsSyncStart' => 0, |
| 3271 | 3277 | 'dtsAutomaticSync' => 0, |
| @@ -3310,8 +3316,26 @@ | ||
| 3310 | 3316 | 'groupid' => $groupid, |
| 3311 | 3317 | ) |
| 3312 | 3318 | ); |
| 3313 | 3319 | } |
| 3320 | + | |
| 3321 | + // Add primary monitor, use global settings. | |
| 3322 | + $success = MainWP_DB_Uptime_Monitoring::instance()->update_wp_monitor( | |
| 3323 | + array( | |
| 3324 | + 'wpid' => $websiteid, | |
| 3325 | + 'suburl' => '', | |
| 3326 | + ) | |
| 3327 | + ); | |
| 3328 | + | |
| 3329 | + $opt_name = 'mainwp_primary_monitor_repair_attempts_for_wp_' . $websiteid; | |
| 3330 | + | |
| 3331 | + if ( ! $success ) { | |
| 3332 | + update_option( $opt_name, 1 ); | |
| 3333 | + MainWP_Utility::unhide_mainwp_message( 'failed-attempt-create-primary-monitor-' . $websiteid ); | |
| 3334 | + } elseif ( get_option( $opt_name ) ) { | |
| 3335 | + delete_option( $opt_name ); | |
| 3336 | + } | |
| 3337 | + | |
| 3314 | 3338 | MainWP_Manage_Sites_List_Table::invalidate_manage_sites_cache(); |
| 3315 | 3339 | return $websiteid; |
| 3316 | 3340 | } |
| 3317 | 3341 | } |
| @@ -3334,9 +3358,9 @@ | ||
| 3334 | 3358 | $this->wpdb->delete( $this->table_name( 'wp_group' ), array( 'wpid' => $websiteid ) ); |
| 3335 | 3359 | $this->wpdb->delete( $this->table_name( 'wp_sync' ), array( 'wpid' => $websiteid ) ); |
| 3336 | 3360 | $this->wpdb->delete( $this->table_name( 'wp_options' ), array( 'wpid' => $websiteid ) ); |
| 3337 | 3361 | MainWP_Encrypt_Data_Lib::remove_key_file( $websiteid ); |
| 3338 | - MainWP_DB_Uptime_Monitoring::instance()->delete_monitor( array( 'wpid' => $websiteid ) ); | |
| 3362 | + MainWP_DB_Uptime_Monitoring::instance()->delete_site_monitors( $websiteid ); | |
| 3339 | 3363 | MainWP_Manage_Sites_List_Table::invalidate_manage_sites_cache(); |
| 3340 | 3364 | return $nr; |
| 3341 | 3365 | } |
| 3342 | 3366 | |
| @@ -3374,9 +3398,9 @@ | ||
| 3374 | 3398 | } |
| 3375 | 3399 | // Lock the data stream to prevent other processes from updating at the same time. |
| 3376 | 3400 | $table_name = esc_sql( $this->table_name( 'wp' ) ); |
| 3377 | 3401 | $sql = $this->wpdb->prepare( |
| 3378 | - "SELECT * FROM {$table_name} WHERE id = %d FOR UPDATE", | |
| 3402 | + "SELECT * FROM {$table_name} WHERE id = %d FOR UPDATE", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd. | |
| 3379 | 3403 | $websiteid |
| 3380 | 3404 | ); |
| 3381 | 3405 | $this->wpdb->get_row( $sql ); |
| 3382 | 3406 | |
| @@ -3598,9 +3622,9 @@ | ||
| 3598 | 3622 | $url .= '/'; |
| 3599 | 3623 | } |
| 3600 | 3624 | $wp_table = esc_sql( $this->table_name( 'wp' ) ); |
| 3601 | 3625 | $wp_sync_table = esc_sql( $this->table_name( 'wp_sync' ) ); |
| 3602 | - $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 ); | |
| 3626 | + $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 ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table names are esc_sql'd. | |
| 3603 | 3627 | if ( $results ) { |
| 3604 | 3628 | return $results; |
| 3605 | 3629 | } |
| 3606 | 3630 | |
| @@ -3612,9 +3636,9 @@ | ||
| 3612 | 3636 | $url = str_replace( 'https://', 'https://www.', $url ); |
| 3613 | 3637 | $url = str_replace( 'http://', 'http://www.', $url ); |
| 3614 | 3638 | } |
| 3615 | 3639 | |
| 3616 | - $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 ); | |
| 3640 | + $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 ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table names are esc_sql'd. | |
| 3617 | 3641 | if ( $results ) { |
| 3618 | 3642 | return $results; |
| 3619 | 3643 | } |
| 3620 | 3644 | |
| @@ -3619,12 +3643,68 @@ | ||
| 3619 | 3643 | } |
| 3620 | 3644 | |
| 3621 | 3645 | $url = str_replace( array( 'https://www.', 'http://www.', 'https://', 'http://', 'www.' ), array( '', '', '', '', '' ), $url ); |
| 3622 | 3646 | |
| 3623 | - 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 ); | |
| 3647 | + 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 ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table names are esc_sql'd. | |
| 3624 | 3648 | } |
| 3625 | 3649 | |
| 3626 | 3650 | /** |
| 3651 | + * Get recently-synced child sites whose stored url differs from the | |
| 3652 | + * child-reported siteurl. | |
| 3653 | + * | |
| 3654 | + * The trailing slash must be normalized in SQL: stored urls always end | |
| 3655 | + * with a slash while child-reported siteurl values never do, so a plain | |
| 3656 | + * inequality would match nearly every site. | |
| 3657 | + * | |
| 3658 | + * @since 6.2 | |
| 3659 | + * | |
| 3660 | + * @param int $days Freshness gate: only sites synced within this many days. | |
| 3661 | + * | |
| 3662 | + * @return array|object|null Rows with id, url, siteurl or null on failure. | |
| 3663 | + */ | |
| 3664 | + public function get_websites_with_url_siteurl_mismatch( $days = 30 ) { | |
| 3665 | + $wp_table = esc_sql( $this->table_name( 'wp' ) ); | |
| 3666 | + $wp_sync_table = esc_sql( $this->table_name( 'wp_sync' ) ); | |
| 3667 | + $minimum_sync = time() - intval( $days ) * DAY_IN_SECONDS; | |
| 3668 | + | |
| 3669 | + return $this->wpdb->get_results( $this->wpdb->prepare( "SELECT wp.id, wp.url, wp.siteurl FROM {$wp_table} wp JOIN {$wp_sync_table} wp_sync ON wp.id = wp_sync.wpid WHERE wp.siteurl <> '' AND TRIM(TRAILING '/' FROM wp.url) <> TRIM(TRAILING '/' FROM wp.siteurl) AND wp_sync.dtsSync >= %d", $minimum_sync ), OBJECT ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table names are esc_sql'd. | |
| 3670 | + } | |
| 3671 | + | |
| 3672 | + /** | |
| 3673 | + * Get IDs of child sites that have a non-empty value for a website option. | |
| 3674 | + * | |
| 3675 | + * @since 6.2 | |
| 3676 | + * | |
| 3677 | + * @param string $option_name Website option name. | |
| 3678 | + * @param int $limit Maximum number of IDs to return. | |
| 3679 | + * | |
| 3680 | + * @return array Site IDs. | |
| 3681 | + */ | |
| 3682 | + public function get_website_ids_with_nonempty_option( $option_name, $limit = 3 ) { | |
| 3683 | + $options_table = esc_sql( $this->table_name( 'wp_options' ) ); | |
| 3684 | + | |
| 3685 | + return $this->wpdb->get_col( $this->wpdb->prepare( "SELECT wpid FROM {$options_table} WHERE name = %s AND value <> '' ORDER BY wpid ASC LIMIT %d", $option_name, max( 1, intval( $limit ) ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd. | |
| 3686 | + } | |
| 3687 | + | |
| 3688 | + /** | |
| 3689 | + * Get one child site whose stored url exactly matches the given URL, | |
| 3690 | + * excluding a site ID. Used as the duplicate guard before a URL correction. | |
| 3691 | + * | |
| 3692 | + * @since 6.2 | |
| 3693 | + * | |
| 3694 | + * @param string $url URL to match ( trailing slash enforced ). | |
| 3695 | + * @param int $exclude_id Site ID to exclude. | |
| 3696 | + * | |
| 3697 | + * @return object|null Matching row ( id, url ) or null. | |
| 3698 | + */ | |
| 3699 | + public function get_website_by_exact_url_excluding_id( $url, $exclude_id ) { | |
| 3700 | + $url = rtrim( (string) $url, '/' ); | |
| 3701 | + $wp_table = esc_sql( $this->table_name( 'wp' ) ); | |
| 3702 | + | |
| 3703 | + return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT id, url FROM {$wp_table} WHERE TRIM(TRAILING '/' FROM url) = %s AND id <> %d", $url, intval( $exclude_id ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd. | |
| 3704 | + } | |
| 3705 | + | |
| 3706 | + /** | |
| 3627 | 3707 | * Method get_websites_to_notice_health_threshold() |
| 3628 | 3708 | * |
| 3629 | 3709 | * Get websites to notice site health. |
| 3630 | 3710 | * |
| @@ -3658,12 +3738,9 @@ | ||
| 3658 | 3738 | $view_selects = ',' . $opts_view['selects']; |
| 3659 | 3739 | $view_joins = $opts_view['joins']; |
| 3660 | 3740 | } |
| 3661 | 3741 | return $this->wpdb->get_results( // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $option_view is a validated SQL subquery |
| 3662 | - "SELECT wp.*,wp_sync.* {$view_selects} FROM {$wp_table} wp | |
| 3663 | - JOIN {$wp_sync_table} wp_sync ON wp.id = wp_sync.wpid | |
| 3664 | - {$view_joins} | |
| 3665 | - 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 " . | |
| 3742 | + "SELECT wp.*,wp_sync.* {$view_selects} FROM {$wp_table} wp JOIN {$wp_sync_table} wp_sync ON wp.id = wp_sync.wpid {$view_joins} 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 " . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table names are esc_sql'd, the view fragments come from get_wp_options_join(), which allowlists option names, and both threshold fragments are literals picked above. | |
| 3666 | 3743 | $where . ' GROUP BY wp.id ', |
| 3667 | 3744 | OBJECT |
| 3668 | 3745 | ); |
| 3669 | 3746 | } |
| @@ -3689,10 +3766,9 @@ | ||
| 3689 | 3766 | } |
| 3690 | 3767 | |
| 3691 | 3768 | // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- is a validated SQL subquery. |
| 3692 | 3769 | return $this->wpdb->get_results( |
| 3693 | - "SELECT wp.*{$view_selects} FROM {$wp_table} wp | |
| 3694 | - {$view_joins}" . ' WHERE wp.suspended = 0 AND wp.http_code_noticed = 0 AND wp.offline_check_result = -1 ' . | |
| 3770 | + "SELECT wp.*{$view_selects} FROM {$wp_table} wp {$view_joins}" . ' WHERE wp.suspended = 0 AND wp.http_code_noticed = 0 AND wp.offline_check_result = -1 ' . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd; the view fragments come from get_wp_options_join(), which allowlists option names. | |
| 3695 | 3771 | $where . ' GROUP BY wp.id ', |
| 3696 | 3772 | OBJECT |
| 3697 | 3773 | ); |
| 3698 | 3774 | } |
| @@ -3941,18 +4017,28 @@ | ||
| 3941 | 4017 | * |
| 3942 | 4018 | * @return mixed Result |
| 3943 | 4019 | */ |
| 3944 | 4020 | public function insert_lookup_item( $item_name, $item_id, $obj_name, $obj_id ) { |
| 4021 | + // get_lookup_items() and delete_lookup_items() match item_name as given, so a | |
| 4022 | + // name that sanitizing would alter is refused instead of being stored under a | |
| 4023 | + // value the caller can never read back or delete. | |
| 4024 | + if ( ! is_string( $item_name ) || sanitize_text_field( $item_name ) !== $item_name ) { | |
| 4025 | + return false; | |
| 4026 | + } | |
| 3945 | 4027 | if ( empty( $item_name ) || empty( $item_id ) || empty( $obj_name ) || empty( $obj_id ) ) { |
| 3946 | 4028 | return false; |
| 3947 | 4029 | } |
| 3948 | 4030 | $data = array( |
| 3949 | - 'item_name' => 'cost', | |
| 4031 | + 'item_name' => $item_name, | |
| 3950 | 4032 | 'item_id' => $item_id, |
| 3951 | 4033 | 'object_name' => $obj_name, |
| 3952 | 4034 | 'object_id' => $obj_id, |
| 3953 | 4035 | ); |
| 3954 | - $this->wpdb->insert( $this->table_name( 'lookup_item_objects' ), $data ); | |
| 4036 | + // wpdb refuses a value past the column length before any SQL runs, and | |
| 4037 | + // insert_id would still hold the previous insert's id. | |
| 4038 | + if ( false === $this->wpdb->insert( $this->table_name( 'lookup_item_objects' ), $data ) ) { | |
| 4039 | + return false; | |
| 4040 | + } | |
| 3955 | 4041 | return $this->wpdb->insert_id; // must return lookup id. |
| 3956 | 4042 | } |
| 3957 | 4043 | |
| 3958 | 4044 | /** |
| @@ -4127,9 +4213,9 @@ | ||
| 4127 | 4213 | * @return bool result. |
| 4128 | 4214 | */ |
| 4129 | 4215 | public function is_existed_enabled_rest_key() { |
| 4130 | 4216 | $table_name = esc_sql( $this->table_name( 'api_keys' ) ); |
| 4131 | - $enabled = $this->wpdb->get_row( "SELECT * FROM {$table_name} WHERE enabled = 1 LIMIT 1" ); | |
| 4217 | + $enabled = $this->wpdb->get_row( "SELECT * FROM {$table_name} WHERE enabled = 1 LIMIT 1" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd. | |
| 4132 | 4218 | return $enabled ? true : false; |
| 4133 | 4219 | } |
| 4134 | 4220 | |
| 4135 | 4221 | /** |
| @@ -4140,9 +4226,9 @@ | ||
| 4140 | 4226 | * @return array |
| 4141 | 4227 | */ |
| 4142 | 4228 | public function get_rest_api_key_by( $id ) { |
| 4143 | 4229 | $table_name = esc_sql( $this->table_name( 'api_keys' ) ); |
| 4144 | - return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$table_name} WHERE key_id = %d", $id ) ); | |
| 4230 | + return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$table_name} WHERE key_id = %d", $id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd. | |
| 4145 | 4231 | } |
| 4146 | 4232 | |
| 4147 | 4233 | /** |
| 4148 | 4234 | * Method remove_rest_api_key(). |
| @@ -4152,9 +4238,9 @@ | ||
| 4152 | 4238 | * @return array |
| 4153 | 4239 | */ |
| 4154 | 4240 | public function remove_rest_api_key( $id ) { |
| 4155 | 4241 | $table_name = esc_sql( $this->table_name( 'api_keys' ) ); |
| 4156 | - return $this->wpdb->query( $this->wpdb->prepare( "DELETE FROM {$table_name} WHERE key_id = %s", $id ) ); | |
| 4242 | + return $this->wpdb->query( $this->wpdb->prepare( "DELETE FROM {$table_name} WHERE key_id = %s", $id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd. | |
| 4157 | 4243 | } |
| 4158 | 4244 | |
| 4159 | 4245 | /** |
| 4160 | 4246 | * Method get_rest_api_keys(). |
| @@ -4162,9 +4248,9 @@ | ||
| 4162 | 4248 | * @return array |
| 4163 | 4249 | */ |
| 4164 | 4250 | public function get_rest_api_keys() { |
| 4165 | 4251 | $table_name = esc_sql( $this->table_name( 'api_keys' ) ); |
| 4166 | - return $this->wpdb->get_results( "SELECT * FROM {$table_name} ORDER BY key_id DESC" ); | |
| 4252 | + return $this->wpdb->get_results( "SELECT * FROM {$table_name} ORDER BY key_id DESC" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd. | |
| 4167 | 4253 | } |
| 4168 | 4254 | |
| 4169 | 4255 | |
| 4170 | 4256 | /** |
| @@ -4234,9 +4320,9 @@ | ||
| 4234 | 4320 | * @return mixed result |
| 4235 | 4321 | */ |
| 4236 | 4322 | public function get_regular_process_by_item_id_type_slug( $item_id, $type, $process_slug ) { |
| 4237 | 4323 | $table_name = esc_sql( $this->table_name( 'schedule_processes' ) ); |
| 4238 | - 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 ) ); | |
| 4324 | + 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 ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is esc_sql'd. | |
| 4239 | 4325 | } |
| 4240 | 4326 | |
| 4241 | 4327 | /** |
| 4242 | 4328 | * Log SQL queries for debugging via hook. |
| @@ -4266,6 +4352,50 @@ | ||
| 4266 | 4352 | $params = apply_filters( 'mainwp_log_system_query_params', $params, $sql, $caller ); |
| 4267 | 4353 | if ( is_array( $params ) && ! empty( $params['dev_log_query'] ) && ! empty( $sql ) ) { |
| 4268 | 4354 | do_action( 'mainwp_log_system_query', $params, $sql, $caller ); |
| 4269 | 4355 | } |
| 4356 | + } | |
| 4357 | + | |
| 4358 | + /** | |
| 4359 | + * Method get_core_tables(). | |
| 4360 | + * | |
| 4361 | + * Returns the list of core tables used by MainWP for validation data integrity and other purposes. | |
| 4362 | + * | |
| 4363 | + * @return array Core tables info | |
| 4364 | + */ | |
| 4365 | + public function get_core_tables() { | |
| 4366 | + | |
| 4367 | + $core_tables = array( | |
| 4368 | + $this->table_name( 'wp_clients' ), | |
| 4369 | + $this->table_name( 'wp_clients_fields' ), | |
| 4370 | + $this->table_name( 'wp_clients_field_values' ), | |
| 4371 | + $this->table_name( 'wp_clients_contacts' ), | |
| 4372 | + $this->table_name( 'monitors' ), | |
| 4373 | + $this->table_name( 'monitor_heartbeat' ), | |
| 4374 | + $this->table_name( 'monitor_stat_hourly' ), | |
| 4375 | + $this->table_name( 'wp' ), | |
| 4376 | + $this->table_name( 'wp_sync' ), | |
| 4377 | + $this->table_name( 'wp_options' ), | |
| 4378 | + $this->table_name( 'wp_settings_backup' ), | |
| 4379 | + $this->table_name( 'users' ), | |
| 4380 | + $this->table_name( 'wp_status' ), | |
| 4381 | + $this->table_name( 'group' ), | |
| 4382 | + $this->table_name( 'wp_group' ), | |
| 4383 | + $this->table_name( 'lookup_item_objects' ), | |
| 4384 | + $this->table_name( 'wp_backup_progress' ), | |
| 4385 | + $this->table_name( 'wp_backup' ), | |
| 4386 | + $this->table_name( 'api_keys' ), | |
| 4387 | + $this->table_name( 'action_log' ), | |
| 4388 | + $this->table_name( 'request_log' ), | |
| 4389 | + $this->table_name( 'schedule_processes' ), | |
| 4390 | + $this->table_name( 'cost_tracker' ), | |
| 4391 | + ); | |
| 4392 | + | |
| 4393 | + if ( ! defined( 'MAINWP_MODULE_LOG_ENABLED' ) || MAINWP_MODULE_LOG_ENABLED ) { | |
| 4394 | + $core_tables[] = $this->table_name( 'wp_logs' ); | |
| 4395 | + $core_tables[] = $this->table_name( 'wp_logs_meta' ); | |
| 4396 | + $core_tables[] = $this->table_name( 'wp_logs_meta_archive' ); | |
| 4397 | + } | |
| 4398 | + | |
| 4399 | + return apply_filters( 'mainwp_database_core_tables', $core_tables ); | |
| 4270 | 4400 | } |
| 4271 | 4401 | } |