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