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-system-utility.php +1965 -1353 5.0.3 → 6.2.2 View file →
@@ -6,8 +6,13 @@
6 6 */
7 7
8 8 namespace MainWP\Dashboard;
9 9
10 +// Exit if accessed directly.
11 +if ( ! defined( 'ABSPATH' ) ) {
12 + exit;
13 +}
14 +
10 15 // phpcs:disable WordPress.DB.RestrictedFunctions, WordPress.WP.AlternativeFunctions, WordPress.PHP.NoSilencedErrors, Generic.Metrics.CyclomaticComplexity -- Using cURL functions.
11 16
12 17 /**
13 18 * Class MainWP_System_Utility
@@ -13,1562 +18,2169 @@
13 18 * Class MainWP_System_Utility
14 19 *
15 20 * @package MainWP\Dashboard
16 21 */
17 -class MainWP_System_Utility {
22 +class MainWP_System_Utility { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
18 23
19 - /**
20 - * Private static variable to hold the single instance of the class.
21 - *
22 - * @static
23 - *
24 - * @var mixed Default null
25 - */
26 - private static $instance = null;
24 + /**
25 + * Private static variable to hold the single instance of the class.
26 + *
27 + * @static
28 + *
29 + * @var mixed Default null
30 + */
31 + private static $instance = null;
27 32
28 - /**
29 - * Method instance()
30 - *
31 - * Create a public static instance.
32 - *
33 - * @static
34 - * @return MainWP_Post_Handler
35 - */
36 - public static function instance() {
37 - if ( null === self::$instance ) {
38 - self::$instance = new self();
39 - }
40 - return self::$instance;
41 - }
33 + /**
34 + * Request-local contrast results for tag backgrounds.
35 + *
36 + * Keyed by normalized #rrggbb. Invalid colors are not stored.
37 + *
38 + * @static
39 + *
40 + * @var array
41 + */
42 + private static $tag_contrast_cache = array();
42 43
44 + /**
45 + * Method instance()
46 + *
47 + * Create a public static instance.
48 + *
49 + * @static
50 + * @return MainWP_Post_Handler
51 + */
52 + public static function instance() {
53 + if ( null === static::$instance ) {
54 + static::$instance = new self();
55 + }
56 + return static::$instance;
57 + }
43 58
44 - /**
45 - * Method get_class_name()
46 - *
47 - * Get Class Name.
48 - *
49 - * @return object
50 - */
51 - public static function get_class_name() {
52 - return __CLASS__;
53 - }
59 + /**
60 + * Constructor.
61 + *
62 + * Run each time the class is called.
63 + *
64 + * @return void
65 + */
66 + public function __construct() {
67 + add_action( 'admin_init', array( &$this, 'admin_init' ) );
68 + }
54 69
55 - /**
56 - * Method is_admin()
57 - *
58 - * Check if current user is an administrator.
59 - *
60 - * @return boolean True|False.
61 - */
62 - public static function is_admin() {
70 + /**
71 + * Method admin_init().
72 + *
73 + * @return void
74 + */
75 + public function admin_init() {
76 + $current_month = gmdate( 'Y-m' );
77 + if ( get_option( 'mainwp_last_short_term_notice_purge' ) !== $current_month ) { // Run once per month.
78 + MainWP_Utility::purge_short_term_notices();
79 + update_option(
80 + 'mainwp_last_short_term_notice_purge',
81 + $current_month,
82 + false
83 + );
84 + }
85 + }
63 86
64 - /**
65 - * Current user global.
66 - *
67 - * @global string
68 - */
69 - global $current_user;
70 - if ( empty( $current_user->ID ) ) {
71 - return false;
72 - }
87 + /**
88 + * Method get_class_name()
89 + *
90 + * Get Class Name.
91 + *
92 + * @return object
93 + */
94 + public static function get_class_name() {
95 + return __CLASS__;
96 + }
73 97
74 - if ( ( property_exists( $current_user, 'wp_user_level' ) && 10 === (int) $current_user->wp_user_level ) || ( isset( $current_user->user_level ) && 10 === (int) $current_user->user_level ) || self::current_user_has_role( 'administrator' ) ) {
75 - return true;
76 - }
98 + /**
99 + * Method is_admin()
100 + *
101 + * Check if current user is an administrator.
102 + *
103 + * @return boolean True|False.
104 + */
105 + public static function is_admin() {
77 106
78 - return false;
79 - }
107 + /**
108 + * Current user global.
109 + *
110 + * @global string
111 + */
112 + global $current_user;
113 + if ( empty( $current_user->ID ) ) {
114 + return false;
115 + }
80 116
81 - /**
82 - * Method current_user_has_role()
83 - *
84 - * Check if the user has role.
85 - *
86 - * @param array|string $roles role or array of roles to check.
87 - * @param object|null $user user check.
88 - *
89 - * @return bool true|false If the user is administrator (Level 10), return true, if not, return false.
90 - */
91 - public static function current_user_has_role( $roles, $user = null ) {
117 + if ( ( property_exists( $current_user, 'wp_user_level' ) && 10 === (int) $current_user->wp_user_level ) || ( isset( $current_user->user_level ) && 10 === (int) $current_user->user_level ) || static::current_user_has_role( 'administrator' ) ) {
118 + return true;
119 + }
92 120
93 - if ( null === $user ) {
94 - $user = wp_get_current_user();
95 - }
121 + return false;
122 + }
96 123
97 - if ( empty( $user ) || empty( $user->ID ) ) {
98 - return false;
99 - }
124 + /**
125 + * Method current_user_has_role()
126 + *
127 + * Check if the user has role.
128 + *
129 + * @param array|string $roles role or array of roles to check.
130 + * @param object|null $user user check.
131 + *
132 + * @return bool true|false If the user is administrator (Level 10), return true, if not, return false.
133 + */
134 + public static function current_user_has_role( $roles, $user = null ) {
100 135
101 - if ( is_string( $roles ) ) {
102 - $allowed_roles = array( $roles );
103 - } elseif ( is_array( $roles ) ) {
104 - $allowed_roles = $roles;
105 - } else {
106 - return false;
107 - }
136 + if ( null === $user ) {
137 + $user = wp_get_current_user();
138 + }
108 139
109 - if ( array_intersect( $allowed_roles, $user->roles ) ) {
110 - return true;
111 - }
140 + if ( empty( $user ) || empty( $user->ID ) ) {
141 + return false;
142 + }
112 143
113 - return false;
114 - }
144 + if ( is_string( $roles ) ) {
145 + $allowed_roles = array( $roles );
146 + } elseif ( is_array( $roles ) ) {
147 + $allowed_roles = $roles;
148 + } else {
149 + return false;
150 + }
115 151
116 - /**
117 - * Method get_primary_backup()
118 - *
119 - * Check if using Legacy Backup Solution.
120 - *
121 - * @return mixed False|$enable_legacy_backup.
122 - */
123 - public static function get_primary_backup() {
124 - $enable_legacy_backup = get_option( 'mainwp_enableLegacyBackupFeature' );
125 - if ( ! $enable_legacy_backup ) {
126 - return get_option( 'mainwp_primaryBackup', false );
127 - }
128 - return false;
129 - }
152 + if ( array_intersect( $allowed_roles, $user->roles ) ) {
153 + return true;
154 + }
130 155
131 - /**
132 - * Method get_notification_email()
133 - *
134 - * Check if user wants to recieve MainWP Notification Emails.
135 - *
136 - * @return mixed null|User Email Address.
137 - *
138 - * @uses \MainWP\Dashboard\MainWP_DB_Common::get_user_extension()
139 - */
140 - public static function get_notification_email() {
141 - return get_option( 'admin_email' );
142 - }
156 + return false;
157 + }
143 158
144 - /**
145 - * Method get_base_dir()
146 - *
147 - * Get the base upload directory.
148 - *
149 - * @return string basedir/
150 - */
151 - public static function get_base_dir() {
152 - $upload_dir = wp_upload_dir();
159 + /**
160 + * Method get_primary_backup()
161 + *
162 + * Check if using Legacy Backup Solution.
163 + *
164 + * @return mixed False|$enable_legacy_backup.
165 + */
166 + public static function get_primary_backup() {
167 + $enable_legacy_backup = get_option( 'mainwp_enableLegacyBackupFeature' );
168 + $global_method = false;
169 + if ( ! $enable_legacy_backup ) {
170 + $global_method = get_option( 'mainwp_primaryBackup', false );
171 + }
172 + return $global_method;
173 + }
153 174
154 - return $upload_dir['basedir'] . DIRECTORY_SEPARATOR;
155 - }
175 + /**
176 + * Method get_notification_email()
177 + *
178 + * Check if user wants to recieve MainWP Notification Emails.
179 + *
180 + * @return mixed null|User Email Address.
181 + *
182 + * @uses \MainWP\Dashboard\MainWP_DB_Common::get_user_extension()
183 + */
184 + public static function get_notification_email() {
185 + return get_option( 'admin_email' );
186 + }
156 187
157 - /**
158 - * Method get_icons_dir()
159 - *
160 - * Get MainWP icons directory,
161 - * if it doesn't exist create it.
162 - *
163 - * @return array $dir, $url
164 - */
165 - public static function get_icons_dir() {
166 - $hasWPFileSystem = self::get_wp_file_system();
188 + /**
189 + * Method get_base_dir()
190 + *
191 + * Get the base upload directory.
192 + *
193 + * @return string basedir/
194 + */
195 + public static function get_base_dir() {
196 + $upload_dir = wp_upload_dir();
167 197
168 - /**
169 - * WordPress files system object.
170 - *
171 - * @global object
172 - */
173 - global $wp_filesystem;
198 + return $upload_dir['basedir'] . DIRECTORY_SEPARATOR;
199 + }
174 200
175 - $dirs = self::get_mainwp_dir();
176 - $dir = $dirs[0] . 'icons' . DIRECTORY_SEPARATOR;
177 - $url = $dirs[1] . 'icons/';
178 - if ( ! $wp_filesystem->exists( $dir ) ) {
179 - $wp_filesystem->mkdir( $dir, 0777 );
180 - }
181 - if ( ! $wp_filesystem->exists( $dir . 'index.php' ) ) {
182 - $wp_filesystem->touch( $dir . 'index.php' );
183 - }
184 - return array( $dir, $url );
185 - }
201 + /**
202 + * Method get_icons_dir()
203 + *
204 + * Get MainWP icons directory,
205 + * if it doesn't exist create it.
206 + *
207 + * @return array $dir, $url
208 + */
209 + public static function get_icons_dir() {
210 + static::get_wp_file_system();
186 211
187 - /**
188 - * Method touch().
189 - *
190 - * If the file does not exist, it will be created.
191 - *
192 - * @param string $filename File name.
193 - */
194 - public static function touch( $filename ) {
195 - $hasWPFileSystem = self::get_wp_file_system();
196 - /**
197 - * WordPress files system object.
198 - *
199 - * @global object
200 - */
201 - global $wp_filesystem;
202 - if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
203 - if ( ! $wp_filesystem->exists( $filename ) ) {
204 - $wp_filesystem->touch( $filename );
205 - }
206 - } elseif ( ! file_exists( $filename ) ) { //phpcs:ignore -- ok.
207 - touch( $filename ); //phpcs:ignore -- ok.
208 - }
209 - }
212 + /**
213 + * WordPress files system object.
214 + *
215 + * @global object
216 + */
217 + global $wp_filesystem;
210 218
211 - /**
212 - * Method is_writable().
213 - *
214 - * @param string $file The file.
215 - */
216 - public static function is_writable( $file ) {
217 - $hasWPFileSystem = self::get_wp_file_system();
218 - /**
219 - * WordPress files system object.
220 - *
221 - * @global object
222 - */
223 - global $wp_filesystem;
219 + $dirs = static::get_mainwp_dir();
220 + $dir = $dirs[0] . 'icons' . DIRECTORY_SEPARATOR;
221 + $url = $dirs[1] . 'icons/';
222 + if ( ! $wp_filesystem->exists( $dir ) ) {
223 + $wp_filesystem->mkdir( $dir, 0755 ); // MWP-1558: tightened from 0777 to WP convention; public-asset dir, blocks cross-tenant manipulation on shared hosting.
224 + }
225 + if ( ! $wp_filesystem->exists( $dir . 'index.php' ) ) {
226 + $wp_filesystem->touch( $dir . 'index.php' );
227 + }
228 + return array( $dir, $url );
229 + }
224 230
225 - $is_writable = true;
226 - if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
227 - if ( ! $wp_filesystem->is_writable( $file ) ) {
228 - $is_writable = false;
229 - }
230 - } elseif ( ! is_writable( $file ) ) { //phpcs:ignore -- ok.
231 - $is_writable = false;
232 - }
233 - return $is_writable;
234 - }
231 + /**
232 + * Method touch().
233 + *
234 + * If the file does not exist, it will be created.
235 + *
236 + * @param string $filename File name.
237 + */
238 + public static function touch( $filename ) {
239 + $hasWPFileSystem = static::get_wp_file_system();
240 + /**
241 + * WordPress files system object.
242 + *
243 + * @global object
244 + */
245 + global $wp_filesystem;
246 + if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
247 + if ( ! $wp_filesystem->exists( $filename ) ) {
248 + $wp_filesystem->touch( $filename );
249 + }
250 + } elseif ( ! file_exists( $filename ) ) { //phpcs:ignore -- ok.
251 + touch( $filename ); //phpcs:ignore -- ok.
252 + }
253 + }
235 254
236 - /**
237 - * Method get_mainwp_dir()
238 - *
239 - * Get the MainWP directory,
240 - * if it doesn't exist create it.
241 - *
242 - * @param string|null $subdir mainwp sub diectories.
243 - * @param bool $direct_access Return true if Direct access file system. Default: false.
244 - *
245 - * @return array $dir, $url
246 - */
247 - public static function get_mainwp_dir( $subdir = null, $direct_access = false ) {
248 - $hasWPFileSystem = self::get_wp_file_system();
255 + /**
256 + * Method is_writable().
257 + *
258 + * @param string $file The file.
259 + */
260 + public static function is_writable( $file ) {
261 + $hasWPFileSystem = static::get_wp_file_system();
262 + /**
263 + * WordPress files system object.
264 + *
265 + * @global object
266 + */
267 + global $wp_filesystem;
249 268
250 - /**
251 - * WordPress files system object.
252 - *
253 - * @global object
254 - */
255 - global $wp_filesystem;
269 + $is_writable = true;
270 + if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
271 + if ( ! $wp_filesystem->is_writable( $file ) ) {
272 + $is_writable = false;
273 + }
274 + } elseif ( ! is_writable( $file ) ) { //phpcs:ignore -- ok.
275 + $is_writable = false;
276 + }
277 + return $is_writable;
278 + }
256 279
257 - $upload_dir = wp_upload_dir();
258 - $dir = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'mainwp' . DIRECTORY_SEPARATOR;
259 - $url = $upload_dir['baseurl'] . '/mainwp/';
260 - if ( ! $wp_filesystem->exists( $dir ) ) {
261 - $wp_filesystem->mkdir( $dir, 0777 );
262 - }
263 - if ( ! $wp_filesystem->exists( $dir . 'index.php' ) ) {
264 - $wp_filesystem->touch( $dir . 'index.php' );
265 - }
280 + /**
281 + * Method get_mainwp_dir()
282 + *
283 + * Get the MainWP directory,
284 + * if it doesn't exist create it.
285 + *
286 + * @param string|null $subdir mainwp sub diectories.
287 + * @param bool $direct_access Return true if Direct access file system. Default: false.
288 + *
289 + * @return array $dir, $url
290 + */
291 + public static function get_mainwp_dir( $subdir = null, $direct_access = false ) {
292 + static::get_wp_file_system();
266 293
267 - if ( ! empty( $subdir ) && ! stristr( $subdir, '..' ) ) {
268 - $newdir = $dir . $subdir . DIRECTORY_SEPARATOR;
269 - $url = $url . $subdir . '/';
294 + /**
295 + * WordPress files system object.
296 + *
297 + * @global object
298 + */
299 + global $wp_filesystem;
270 300
271 - if ( ! $wp_filesystem->exists( $newdir ) ) {
272 - $wp_filesystem->mkdir( $newdir, 0777 );
273 - }
301 + $upload_dir = wp_upload_dir();
274 302
275 - if ( $direct_access ) {
276 - if ( ! $wp_filesystem->exists( trailingslashit( $newdir ) . 'index.php' ) ) {
277 - $wp_filesystem->touch( trailingslashit( $newdir ) . 'index.php' );
278 - }
279 - if ( $wp_filesystem->exists( trailingslashit( $newdir ) . '.htaccess' ) ) {
280 - $wp_filesystem->delete( trailingslashit( $newdir ) . '.htaccess' );
281 - }
282 - } elseif ( ! $wp_filesystem->exists( trailingslashit( $newdir ) . '.htaccess' ) ) {
283 - $wp_filesystem->put_contents( trailingslashit( $newdir ) . '.htaccess', 'deny from all' );
284 - }
285 - return array( $newdir, $url );
286 - }
303 + /**
304 + * Allow filtering the upload directory array used by MainWP.
305 + *
306 + * @since 5.4.1.
307 + *
308 + * @param array $upload_dir Array of upload directory info (from wp_upload_dir()).
309 + * @param string $subdir Optional. Sub Directory requested.
310 + * @param bool $direct_access Optional. Direct access.
311 + */
312 + $upload_dir = apply_filters( 'mainwp_get_wp_upload_dir', $upload_dir, $subdir, $direct_access );
287 313
288 - return array( $dir, $url );
289 - }
314 + $dir = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'mainwp' . DIRECTORY_SEPARATOR;
315 + $url = $upload_dir['baseurl'] . '/mainwp/';
316 + if ( ! $wp_filesystem->exists( $dir ) ) {
317 + $wp_filesystem->mkdir( $dir, 0755 ); // MWP-1558: tightened from 0777 to WP convention; closes cross-tenant pk/ substitution attack on shared hosting.
318 + }
319 + if ( ! $wp_filesystem->exists( $dir . 'index.php' ) ) {
320 + $wp_filesystem->touch( $dir . 'index.php' );
321 + }
290 322
291 - /**
292 - * Method get_mainwp_sub_dir()
293 - *
294 - * Get the MainWP directory,
295 - * if it doesn't exist create it.
296 - *
297 - * @param string|null $subdir mainwp sub diectories.
298 - * @param bool $direct_access Return true if Direct access file system. Default: false.
299 - *
300 - * @return string $dir mainwp sub-directory.
301 - */
302 - public static function get_mainwp_sub_dir( $subdir = null, $direct_access = false ) {
303 - $dirs = self::get_mainwp_dir( $subdir, $direct_access );
304 - return $dirs[0];
305 - }
323 + if ( ! empty( $subdir ) && ! stristr( $subdir, '..' ) ) {
324 + $newdir = $dir . $subdir . DIRECTORY_SEPARATOR;
325 + $url = $url . $subdir . '/';
306 326
307 - /**
308 - * Method get_download_dir()
309 - *
310 - * @param mixed $what What url.
311 - * @param mixed $filename File Name.
312 - *
313 - * @return string Download URL.
314 - */
315 - public static function get_download_url( $what, $filename ) {
316 - $specificDir = self::get_mainwp_specific_dir( $what );
317 - $mwpDir = self::get_mainwp_dir();
318 - $mwpDir = $mwpDir[0];
319 - $fullFile = $specificDir . $filename;
327 + if ( ! $wp_filesystem->exists( $newdir ) ) {
328 + // MWP-1558: 0750 for private (htaccess-protected) subdirs, 0755 for public-asset subdirs (icons, client-images, etc.).
329 + $wp_filesystem->mkdir( $newdir, $direct_access ? 0755 : 0750 );
330 + }
320 331
321 - $download_url = admin_url( '?sig=' . self::get_download_sig( $fullFile ) . '&mwpdl=' . rawurlencode( str_replace( $mwpDir, '', $fullFile ) ) );
332 + if ( $direct_access ) {
333 + if ( ! $wp_filesystem->exists( trailingslashit( $newdir ) . 'index.php' ) ) {
334 + $wp_filesystem->touch( trailingslashit( $newdir ) . 'index.php' );
335 + }
336 + if ( $wp_filesystem->exists( trailingslashit( $newdir ) . '.htaccess' ) ) {
337 + $wp_filesystem->delete( trailingslashit( $newdir ) . '.htaccess' );
338 + }
339 + } elseif ( ! $wp_filesystem->exists( trailingslashit( $newdir ) . '.htaccess' ) ) {
340 + $wp_filesystem->put_contents( trailingslashit( $newdir ) . '.htaccess', 'deny from all' );
341 + }
342 + return array( $newdir, $url );
343 + }
322 344
323 - return $download_url;
324 - }
345 + return array( $dir, $url );
346 + }
325 347
348 + /**
349 + * Method get_mainwp_sub_dir()
350 + *
351 + * Get the MainWP directory,
352 + * if it doesn't exist create it.
353 + *
354 + * @param string|null $subdir mainwp sub diectories.
355 + * @param bool $direct_access Return true if Direct access file system. Default: false.
356 + *
357 + * @return string $dir mainwp sub-directory.
358 + */
359 + public static function get_mainwp_sub_dir( $subdir = null, $direct_access = false ) {
360 + $dirs = static::get_mainwp_dir( $subdir, $direct_access );
361 + return $dirs[0];
362 + }
326 363
327 - /**
328 - * Method get_download_sig()
329 - *
330 - * @param string $fullFile File Name.
331 - *
332 - * @return string Sig Download URL.
333 - */
334 - public static function get_download_sig( $fullFile ) {
335 - $key_value = uniqid( 'sig_', true ) . filesize( $fullFile ) . time();
336 - $sig_values = array(
337 - 'sig' => md5( filesize( $fullFile ) ),
338 - 'key_value' => $key_value,
339 - 'hash_key' => wp_hash( $key_value ),
340 - );
341 - $sig_values = wp_json_encode( $sig_values );
342 - $sig_values = rawurlencode( $sig_values );
343 - return $sig_values;
344 - }
364 + /**
365 + * Method get_download_dir()
366 + *
367 + * @param mixed $what What url.
368 + * @param mixed $filename File Name.
369 + *
370 + * @return string Download URL.
371 + */
372 + public static function get_download_url( $what, $filename ) {
373 + $specificDir = static::get_mainwp_specific_dir( $what );
374 + $mwpDir = static::get_mainwp_dir();
375 + $mwpDir = $mwpDir[0];
376 + $fullFile = $specificDir . $filename;
345 377
378 + return admin_url( '?sig=' . static::get_download_sig( $fullFile ) . '&mwpdl=' . rawurlencode( str_replace( $mwpDir, '', $fullFile ) ) );
379 + }
346 380
347 - /**
348 - * Method valid_download_sig()
349 - *
350 - * @param string $file File Name.
351 - * @param string $sig download.
352 - *
353 - * @return bool true|false.
354 - */
355 - public static function valid_download_sig( $file, $sig ) {
356 381
357 - $sig = rawurldecode( $sig );
358 - $value = json_decode( $sig, true );
382 + /**
383 + * Method get_download_sig()
384 + *
385 + * @param string $fullFile File Name.
386 + *
387 + * @return string Sig Download URL.
388 + */
389 + public static function get_download_sig( $fullFile ) {
390 + $key_value = uniqid( 'sig_', true ) . filesize( $fullFile ) . time();
391 + $secret_value = uniqid( 'sig_secret_', true ) . filesize( $fullFile ) . time();
359 392
360 - if ( ! is_array( $value ) || empty( $value['key_value'] ) || empty( $value['sig'] ) ) {
361 - return false;
362 - }
393 + $hashkey = wp_hash( $key_value );
363 394
364 - if ( md5( filesize( $file ) ) !== $value['sig'] ) {
365 - return false;
366 - }
395 + $sig_values = array(
396 + 'sig' => md5( filesize( $fullFile ) ), // NOSONAR - safe for sig file size.
397 + 'key_value' => $key_value,
398 + 'hash_key' => $secret_value,
399 + );
367 400
368 - $hash_key = wp_hash( $value['key_value'] );
369 - if ( ! hash_equals( $hash_key, $value['hash_key'] ) ) {
370 - return false;
371 - }
401 + set_site_transient( 'mainwp_fdl_' . $hashkey, $secret_value, 3 * HOUR_IN_SECONDS );
372 402
373 - return true;
374 - }
403 + $sig_values = wp_json_encode( $sig_values );
404 + $sig_values = rawurlencode( $sig_values );
405 + return $sig_values;
406 + }
375 407
376 - /**
377 - * Method get_mainwp_specific_dir()
378 - *
379 - * Get MainWP Specific directory,
380 - * if it doesn't exist create it.
381 - *
382 - * Update .htaccess.
383 - *
384 - * @param null $dir Current MainWP directory.
385 - *
386 - * @return string $newdir
387 - *
388 - * @uses \MainWP\Dashboard\MainWP_System::is_single_user()
389 - */
390 - public static function get_mainwp_specific_dir( $dir = null ) {
391 - if ( MainWP_System::instance()->is_single_user() ) {
392 - $userid = 0;
393 - } else {
394 408
395 - /**
396 - * Current user global.
397 - *
398 - * @global string
399 - */
400 - global $current_user;
409 + /**
410 + * Method valid_download_sig()
411 + *
412 + * @param string $file File Name.
413 + * @param string $sig download.
414 + *
415 + * @return bool true|false.
416 + */
417 + public static function valid_download_sig( $file, $sig ) {
401 418
402 - $userid = $current_user->ID;
403 - }
419 + $sig = rawurldecode( $sig );
420 + $value = json_decode( $sig, true );
404 421
405 - $hasWPFileSystem = self::get_wp_file_system();
422 + if ( ! is_array( $value ) || empty( $value['key_value'] ) || empty( $value['sig'] ) || md5( filesize( $file ) ) !== $value['sig'] ) { // NOSONAR - it's safe for size matching, file in uploads folder.
423 + return false;
424 + }
406 425
407 - global $wp_filesystem;
426 + $hash_key = wp_hash( $value['key_value'] );
427 + $secure_key = get_site_transient( 'mainwp_fdl_' . $hash_key );
408 428
409 - $dirs = self::get_mainwp_dir();
429 + if ( empty( $secure_key ) || empty( $value['hash_key'] ) || ! hash_equals( $secure_key, $value['hash_key'] ) ) {
430 + return false;
431 + }
410 432
411 - $newdir = $dirs[0] . $userid;
412 - if ( '/' === $dir || null === $dir ) {
413 - $newdir .= DIRECTORY_SEPARATOR;
414 - } else {
415 - $newdir .= DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR;
416 - }
433 + return true;
434 + }
417 435
418 - if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
436 + /**
437 + * Method get_mainwp_specific_dir()
438 + *
439 + * Get MainWP Specific directory,
440 + * if it doesn't exist create it.
441 + *
442 + * Update .htaccess.
443 + *
444 + * @param null $dir Current MainWP directory.
445 + *
446 + * @return string $newdir
447 + *
448 + * @uses \MainWP\Dashboard\MainWP_System::is_single_user()
449 + */
450 + public static function get_mainwp_specific_dir( $dir = null ) { // phpcs:ignore -- NOSONAR - complex.
451 + if ( MainWP_System::instance()->is_single_user() ) {
452 + $userid = 0;
453 + } else {
419 454
420 - if ( ! $wp_filesystem->is_dir( $newdir ) ) {
421 - $wp_filesystem->mkdir( $newdir, 0777 );
422 - }
455 + /**
456 + * Current user global.
457 + *
458 + * @global string
459 + */
460 + global $current_user;
423 461
424 - if ( ! empty( $dirs[0] ) . $userid && ! $wp_filesystem->exists( trailingslashit( $dirs[0] . $userid ) . '.htaccess' ) ) {
425 - $file_htaccess = trailingslashit( $dirs[0] . $userid ) . '.htaccess';
426 - $wp_filesystem->put_contents( $file_htaccess, 'deny from all' );
427 - }
428 - } else {
462 + $userid = $current_user->ID;
463 + }
429 464
430 - if ( ! file_exists( $newdir ) ) {
431 - mkdir( $newdir, 0777, true );
432 - }
465 + $hasWPFileSystem = static::get_wp_file_system();
433 466
434 - if ( ! empty( $dirs[0] ) . $userid && ! file_exists( trailingslashit( $dirs[0] . $userid ) . '.htaccess' ) ) {
435 - $file = fopen( trailingslashit( $dirs[0] . $userid ) . '.htaccess', 'w+' );
436 - fwrite( $file, 'deny from all' );
437 - fclose( $file );
438 - }
439 - }
467 + global $wp_filesystem;
440 468
441 - return $newdir;
442 - }
469 + $dirs = static::get_mainwp_dir();
443 470
444 - /**
445 - * Method get_mainwp_specific_url()
446 - *
447 - * Get MainWP specific URL.
448 - *
449 - * @param mixed $dir MainWP Directory.
450 - *
451 - * @return string MainWP URL.
452 - *
453 - * @uses \MainWP\Dashboard\MainWP_System::is_single_user()
454 - */
455 - public static function get_mainwp_specific_url( $dir ) {
456 - if ( MainWP_System::instance()->is_single_user() ) {
457 - $userid = 0;
458 - } else {
471 + $userdir = $dirs[0] . $userid;
472 + $newdir = $userdir;
459 473
460 - /**
461 - * Current user global.
462 - *
463 - * @global string
464 - */
465 - global $current_user;
474 + if ( '/' === $dir || null === $dir ) {
475 + $newdir .= DIRECTORY_SEPARATOR;
476 + } else {
477 + $newdir .= DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR;
478 + }
466 479
467 - $userid = $current_user->ID;
468 - }
469 - $dirs = self::get_mainwp_dir();
480 + if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
470 481
471 - return $dirs[1] . $userid . '/' . $dir . '/';
472 - }
482 + // need to check user dir first.
483 + if ( ! $wp_filesystem->is_dir( $userdir ) ) {
484 + $wp_filesystem->mkdir( $userdir, 0750 ); // MWP-1558: tightened from 0777; per-user backup root.
485 + }
473 486
474 - /**
475 - * Method get_mainwp_dir_allow_access()
476 - *
477 - * Get MainWP specific sub folder allow access.
478 - *
479 - * @param mixed $sub_dir MainWP Sub Directory.
480 - */
481 - public static function get_mainwp_dir_allow_access( $sub_dir ) {
482 - $dirs = self::get_mainwp_dir( $sub_dir, false );
483 - if ( $dirs ) {
484 - $hasWPFileSystem = self::get_wp_file_system();
485 - global $wp_filesystem;
486 - if ( $wp_filesystem ) {
487 - // to fix issue of do not allow access.
488 - $newdir = $dirs[0];
489 - $content = "Order allow,deny\r\nAllow from all";
490 - // check if the htaccess is deny access all.
491 - if ( $wp_filesystem->exists( trailingslashit( $newdir ) . '.htaccess' ) ) {
492 - if ( $wp_filesystem->size( trailingslashit( $newdir ) . '.htaccess' ) < 25 ) { // 25 bytes: deny from all.
493 - // update the htaccess file to allow direct access.
494 - $wp_filesystem->put_contents( trailingslashit( $newdir ) . '.htaccess', $content );
495 - }
496 - } else {
497 - // update the htaccess file to allow direct access.
498 - $wp_filesystem->put_contents( trailingslashit( $newdir ) . '.htaccess', $content );
499 - }
500 - }
501 - }
502 - return $dirs;
503 - }
487 + if ( ! $wp_filesystem->is_dir( $newdir ) ) {
488 + $wp_filesystem->mkdir( $newdir, 0750 ); // MWP-1558: tightened from 0777; per-user backup subdir.
489 + }
504 490
491 + if ( ! empty( $dirs[0] ) . $userid && ! $wp_filesystem->exists( trailingslashit( $dirs[0] . $userid ) . '.htaccess' ) ) {
492 + $file_htaccess = trailingslashit( $dirs[0] . $userid ) . '.htaccess';
493 + $wp_filesystem->put_contents( $file_htaccess, 'deny from all' );
494 + }
495 + } else {
505 496
506 - /**
507 - * Method get_wp_file_system()
508 - *
509 - * Get WP file system & define Global Variable FS_METHOD.
510 - *
511 - * @return boolean $init True.
512 - */
513 - public static function get_wp_file_system() {
497 + // need to check user dir first.
498 + if ( ! file_exists( $userdir ) ) {
499 + mkdir( $userdir, 0750, true ); // MWP-1558: tightened from 0777. NOSONAR - @newdir is valid.
500 + }
514 501
515 - /**
516 - * WordPress files system object.
517 - *
518 - * @global object
519 - */
520 - global $wp_filesystem;
502 + if ( ! file_exists( $newdir ) ) {
503 + mkdir( $newdir, 0750, true ); // MWP-1558: tightened from 0777. NOSONAR - @newdir is valid.
504 + }
521 505
522 - if ( empty( $wp_filesystem ) ) {
523 - ob_start();
524 - if ( file_exists( ABSPATH . '/wp-admin/includes/screen.php' ) ) {
525 - include_once ABSPATH . '/wp-admin/includes/screen.php';
526 - }
527 - if ( file_exists( ABSPATH . '/wp-admin/includes/template.php' ) ) {
528 - include_once ABSPATH . '/wp-admin/includes/template.php';
529 - }
530 - include_once ABSPATH . 'wp-admin/includes/file.php';
506 + if ( ! empty( $dirs[0] ) . $userid && ! file_exists( trailingslashit( $dirs[0] . $userid ) . '.htaccess' ) ) {
507 + $file = fopen( trailingslashit( $dirs[0] . $userid ) . '.htaccess', 'w+' );
508 + fwrite( $file, 'deny from all' );
509 + fclose( $file );
510 + }
511 + }
531 512
532 - if ( ! function_exists( 'wp_create_nonce' ) ) {
533 - include_once ABSPATH . WPINC . '/pluggable.php';
534 - }
513 + return $newdir;
514 + }
535 515
536 - $creds = request_filesystem_credentials( 'test' );
537 - ob_end_clean();
538 - if ( empty( $creds ) ) {
516 + /**
517 + * Method get_mainwp_specific_url()
518 + *
519 + * Get MainWP specific URL.
520 + *
521 + * @param mixed $dir MainWP Directory.
522 + *
523 + * @return string MainWP URL.
524 + *
525 + * @uses \MainWP\Dashboard\MainWP_System::is_single_user()
526 + */
527 + public static function get_mainwp_specific_url( $dir ) {
528 + if ( MainWP_System::instance()->is_single_user() ) {
529 + $userid = 0;
530 + } else {
539 531
540 - /**
541 - * Define WordPress File system.
542 - *
543 - * @const ( bool ) Default: true
544 - * @source https://code-reference.mainwp.com/classes/MainWP.Dashboard.MainWP_System_Utility.html
545 - */
546 - define( 'FS_METHOD', 'direct' );
547 - }
548 - $init = \WP_Filesystem( $creds );
549 - } else {
550 - $init = true;
551 - }
532 + /**
533 + * Current user global.
534 + *
535 + * @global string
536 + */
537 + global $current_user;
552 538
553 - return $init;
554 - }
539 + $userid = $current_user->ID;
540 + }
541 + $dirs = static::get_mainwp_dir();
555 542
556 - /**
557 - * Method can_edit_website()
558 - *
559 - * Check if current user can edit Child Site.
560 - *
561 - * @param mixed $website Child Site.
562 - *
563 - * @return mixed true|false|userid
564 - *
565 - * @uses \MainWP\Dashboard\MainWP_System::is_single_user()
566 - */
567 - public static function can_edit_website( &$website ) {
568 - if ( empty( $website ) ) {
569 - return false;
570 - }
543 + return $dirs[1] . $userid . '/' . $dir . '/';
544 + }
571 545
572 - if ( MainWP_System::instance()->is_single_user() ) {
573 - return true;
574 - }
546 + /**
547 + * Method get_or_create_filename_secret()
548 + *
549 + * Return a stable per-install secret used to derive opaque private filenames
550 + * for sensitive files under wp-content/uploads/mainwp/. Generated once per
551 + * install and persisted as a plain wp_option (autoload off).
552 + *
553 + * Storage rationale: the secret is stored as a plain wp_option rather than
554 + * via MainWP_Keys_Manager::update_key_value(). The Keys_Manager would store
555 + * the key file inside the very pk/ directory whose filenames this secret
556 + * protects, defeating the purpose. The plaintext-in-DB trade-off is
557 + * acceptable because the threat model targets external URL guessing only;
558 + * a database read primitive that retrieves this secret would also retrieve
559 + * the encrypted privkey ciphertext it would be used to locate.
560 + *
561 + * @return string 64-character hex secret (256 bits of entropy).
562 + */
563 + public static function get_or_create_filename_secret() {
564 + // Use site-option storage so the secret is network-wide on multisite (consistent across blogs)
565 + // and falls through to get_option() on single-site. Matches MainWP_Install's pattern for network state.
566 + $secret = get_site_option( 'mainwp_private_filename_secret' );
567 + if ( ! is_string( $secret ) || ! preg_match( '/^[a-f0-9]{64}$/', $secret ) ) {
568 + $candidate = bin2hex( random_bytes( 32 ) );
569 + // add_site_option is atomic at the DB layer: only one concurrent writer wins.
570 + // Losing requests re-read so all callers see the same secret. Prevents a race where
571 + // two concurrent first-upgrade requests would both generate (and write) different
572 + // secrets, breaking subsequent filename derivations done by the losing request.
573 + if ( add_site_option( 'mainwp_private_filename_secret', $candidate ) ) {
574 + $secret = $candidate;
575 + } else {
576 + $secret = get_site_option( 'mainwp_private_filename_secret' );
577 + }
578 + }
579 + return $secret;
580 + }
575 581
576 - /**
577 - * Current user global.
578 - *
579 - * @global string
580 - */
581 - global $current_user;
582 + /**
583 + * Method get_private_filename()
584 + *
585 + * Compute the opaque filename for a private file under a wp-content/uploads/mainwp/
586 + * subdirectory. Replaces predictable patterns like 'mainwp_priv_encrypt_keys_<site_id>'
587 + * with HMAC-SHA256 derivations that are not externally enumerable.
588 + *
589 + * Filenames are deterministic per install: the same (subdir, key, purpose)
590 + * always yields the same output. Different subdir or purpose values produce
591 + * independent filename spaces with no cross-collision.
592 + *
593 + * @param string $subdir Subdirectory under mainwp/ (e.g., 'pk', 'cookies').
594 + * @param string $key Internal identifier (e.g., site_id, cookie salt).
595 + * @param string $purpose Domain separator to prevent cross-subdir filename collisions.
596 + *
597 + * @return string 64-character hex filename.
598 + */
599 + public static function get_private_filename( $subdir, $key, $purpose ) {
600 + $secret = static::get_or_create_filename_secret();
601 + return hash_hmac( 'sha256', $subdir . ':' . $purpose . ':' . $key, $secret );
602 + }
582 603
583 - return ( $website->userid === $current_user->ID );
584 - }
604 + /**
605 + * Method get_mainwp_dir_allow_access()
606 + *
607 + * Get MainWP specific sub folder allow access.
608 + *
609 + * @param mixed $sub_dir MainWP Sub Directory.
610 + */
611 + public static function get_mainwp_dir_allow_access( $sub_dir ) {
612 + $dirs = static::get_mainwp_dir( $sub_dir, false );
613 + if ( $dirs ) {
614 + static::get_wp_file_system();
615 + global $wp_filesystem;
616 + if ( $wp_filesystem ) {
617 + // to fix issue of do not allow access.
618 + $newdir = $dirs[0];
619 + $content = "Order allow,deny\r\nAllow from all";
620 + // check if the htaccess is deny access all.
621 + if ( $wp_filesystem->exists( trailingslashit( $newdir ) . '.htaccess' ) ) {
622 + if ( $wp_filesystem->size( trailingslashit( $newdir ) . '.htaccess' ) < 25 ) { // 25 bytes: deny from all.
623 + // update the htaccess file to allow direct access.
624 + $wp_filesystem->put_contents( trailingslashit( $newdir ) . '.htaccess', $content );
625 + }
626 + } else {
627 + // update the htaccess file to allow direct access.
628 + $wp_filesystem->put_contents( trailingslashit( $newdir ) . '.htaccess', $content );
629 + }
630 + }
631 + }
632 + return $dirs;
633 + }
585 634
586 - /**
587 - * Gets site tags
588 - *
589 - * @param array $item Array containing child site data.
590 - * @param bool $client_tag It is client tags or not.
591 - *
592 - * @return mixed Single Row Classes Item.
593 - */
594 - public static function get_site_tags( $item, $client_tag = false ) {
595 635
596 - if ( ! is_array( $item ) || ! isset( $item['wpgroups'] ) ) {
597 - return '';
598 - }
636 + /**
637 + * Method get_wp_file_system()
638 + *
639 + * Get WP file system & define Global Variable FS_METHOD.
640 + *
641 + * @return boolean $init True.
642 + */
643 + public static function get_wp_file_system() {
599 644
600 - $href = 'admin.php?page=managesites&g=';
601 - if ( $client_tag ) {
602 - $href = 'admin.php?page=ManageClients&tags=';
603 - }
645 + /**
646 + * WordPress files system object.
647 + *
648 + * @global object
649 + */
650 + global $wp_filesystem;
604 651
605 - $tags = '';
606 - $tags_labels = '';
652 + if ( empty( $wp_filesystem ) ) {
653 + ob_start();
654 + if ( file_exists( ABSPATH . '/wp-admin/includes/screen.php' ) ) {
655 + include_once ABSPATH . '/wp-admin/includes/screen.php'; // NOSONAR - WP compatible.
656 + }
657 + if ( file_exists( ABSPATH . '/wp-admin/includes/template.php' ) ) {
658 + include_once ABSPATH . '/wp-admin/includes/template.php'; // NOSONAR - WP compatible.
659 + }
660 + include_once ABSPATH . 'wp-admin/includes/file.php'; // NOSONAR - WP compatible.
607 661
608 - if ( isset( $item['wpgroups'] ) && ! empty( $item['wpgroups'] ) ) {
662 + if ( ! function_exists( 'wp_create_nonce' ) ) {
663 + include_once ABSPATH . WPINC . '/pluggable.php'; // NOSONAR - WP compatible.
664 + }
609 665
610 - if ( $client_tag ) {
611 - $tags_filter = self::client_tags_filter( $item );
612 - $tags = $tags_filter['wpgroups'];
613 - $tags_ids = $tags_filter['wpgroupids'];
614 - } else {
615 - $tags = $item['wpgroups'];
616 - $tags = explode( ',', $tags );
617 - $tags_ids = $item['wpgroupids'];
618 - $tags_ids = explode( ',', $tags_ids );
619 - }
666 + $creds = request_filesystem_credentials( 'test' );
667 + ob_end_clean();
668 + if ( empty( $creds ) ) {
620 669
621 - if ( is_array( $tags ) ) {
622 - foreach ( $tags as $idx => $tag ) {
623 - $tag = trim( $tag );
624 - $tagx = MainWP_DB_Common::instance()->get_group_by_name( $tag );
670 + /**
671 + * Define WordPress File system.
672 + *
673 + * @const ( bool ) Default: true
674 + * @source https://code-reference.mainwp.com/classes/MainWP.Dashboard.MainWP_System_Utility.html
675 + */
676 + define( 'FS_METHOD', 'direct' );
677 + }
678 + $init = \WP_Filesystem( $creds );
679 + } else {
680 + $init = true;
681 + }
625 682
626 - if ( is_object( $tagx ) && '' !== $tagx->color ) {
627 - $tag_a_style = 'style="color:#fff!important;opacity:1;"';
628 - $tag_style = 'style="background-color:' . esc_html( $tagx->color ) . '"';
629 - } else {
630 - $tag_a_style = '';
631 - $tag_style = '';
632 - }
683 + return $init;
684 + }
633 685
634 - if ( isset( $tags_ids[ $idx ] ) && ! empty( $tags_ids[ $idx ] ) ) {
635 - $tag_id = $tags_ids[ $idx ];
636 - $tags_labels .= '<span ' . $tag_style . ' class="ui tag mini label"><a ' . $tag_a_style . ' href="' . esc_url( $href . $tag_id ) . '">' . esc_html( $tag ) . '</a></span>';
637 - } else {
638 - $tags_labels .= '<span ' . $tag_style . ' class="ui tag mini label">' . esc_html( $tag ) . '</span>';
639 - }
640 - }
641 - }
642 - }
643 - return $tags_labels;
644 - }
686 + /**
687 + * Method can_edit_website()
688 + *
689 + * Check if current user can edit Child Site.
690 + *
691 + * @param mixed $website Child Site.
692 + *
693 + * @return mixed true|false|userid
694 + *
695 + * @uses \MainWP\Dashboard\MainWP_System::is_single_user()
696 + */
697 + public static function can_edit_website( &$website ) {
698 + if ( empty( $website ) ) {
699 + return false;
700 + }
645 701
646 - /**
647 - * Filter client tags
648 - *
649 - * @param array $item Array containing tags.
650 - *
651 - * @return mixed Single Row Classes Item.
652 - */
653 - public static function client_tags_filter( $item ) {
654 - $tags = $item['wpgroups'];
655 - $tags = explode( ',', $tags );
656 - $tags = array_values( array_unique( $tags ) );
702 + if ( MainWP_System::instance()->is_single_user() ) {
703 + return true;
704 + }
657 705
658 - $tags_ids = $item['wpgroupids'];
659 - $tags_ids = explode( ',', $tags_ids );
660 - $tags_ids = array_values( array_unique( $tags_ids ) );
706 + /**
707 + * Current user global.
708 + *
709 + * @global string
710 + */
711 + global $current_user;
661 712
662 - $return = array();
713 + return $website->userid === $current_user->ID;
714 + }
663 715
664 - $return['wpgroups'] = $tags;
665 - $return['wpgroupids'] = $tags_ids;
666 - return $return;
667 - }
716 + /**
717 + * Gets site tags
718 + *
719 + * @param array $item Array containing child site data.
720 + * @param bool $client_tag It is client tags or not.
721 + *
722 + * @return mixed Single Row Classes Item.
723 + */
724 + public static function get_site_tags( $item, $client_tag = false ) { // phpcs:ignore -- NOSONAR - complex.
668 725
669 - /**
670 - * Method is_suspended_site()
671 - *
672 - * Check if enable site.
673 - *
674 - * @param mixed $website The website.
675 - */
676 - public static function is_suspended_site( $website = false ) {
677 - if ( empty( $website ) ) {
678 - return true; // empty so return as suspended.
679 - }
680 - if ( is_array( $website ) ) {
681 - return ( '1' === $website['suspended'] );
682 - } elseif ( is_object( $website ) ) {
683 - if ( ! property_exists( $website, 'suspended' ) && property_exists( $website, 'id' ) ) {
684 - $website = MainWP_DB::instance()->get_website_by_id( $website->id );
685 - }
686 - if ( property_exists( $website, 'suspended' ) ) {
687 - return ( '1' === $website->suspended );
688 - }
689 - } elseif ( is_numeric( $website ) ) {
690 - $siteId = $website;
691 - $website = MainWP_DB::instance()->get_website_by_id( $siteId );
692 - if ( $website ) {
693 - return self::is_suspended_site( $website );
694 - }
695 - }
696 - return false;
697 - }
726 + if ( ! is_array( $item ) || ! isset( $item['wpgroups'] ) ) {
727 + return '';
728 + }
698 729
699 - /**
700 - * Method get_current_wpid()
701 - *
702 - * Get current Child Site ID.
703 - *
704 - * @return string $current_user->current_site_id Current Child Site ID.
705 - */
706 - public static function get_current_wpid() {
730 + $href = 'admin.php?page=managesites&g=';
731 + if ( $client_tag ) {
732 + $href = 'admin.php?page=ManageClients&tags=';
733 + }
707 734
708 - /**
709 - * Current user global.
710 - *
711 - * @global string
712 - */
713 - global $current_user;
735 + $groups_colors = '';
736 + if ( isset( $item['wpgroups_colors'] ) ) {
737 + $groups_colors = explode( ',', $item['wpgroups_colors'] );
738 + }
714 739
715 - return $current_user->current_site_id;
716 - }
740 + $tags = '';
741 + $tags_labels = '';
717 742
718 - /**
719 - * Method set_current_wpid()
720 - *
721 - * Set the current Child Site ID.
722 - *
723 - * @param mixed $wpid Child Site ID.
724 - */
725 - public static function set_current_wpid( $wpid ) {
743 + if ( isset( $item['wpgroups'] ) && ! empty( $item['wpgroups'] ) ) {
726 744
727 - /**
728 - * Current user global.
729 - *
730 - * @global string
731 - */
732 - global $current_user;
745 + if ( $client_tag ) {
746 + $tags_filter = static::client_tags_filter( $item );
747 + $tags = $tags_filter['wpgroups'];
748 + $tags_ids = $tags_filter['wpgroupids'];
749 + } else {
750 + $tags = $item['wpgroups'];
751 + $tags = explode( ',', $tags );
752 + $tags_ids = $item['wpgroupids'];
753 + $tags_ids = explode( ',', $tags_ids );
754 + }
733 755
734 - $current_user->current_site_id = $wpid;
735 - }
756 + if ( is_array( $tags ) ) {
757 + foreach ( $tags as $idx => $tag ) {
758 + $tag = trim( $tag );
759 + $tagc = '';
736 760
737 - /**
738 - * Method get_page_id()
739 - *
740 - * Get current Page ID.
741 - *
742 - * @param null $screen Current Screen ID.
743 - *
744 - * @return string $page Current page ID.
745 - */
746 - public static function get_page_id( $screen = null ) {
761 + // to improved db query.
762 + if ( is_array( $groups_colors ) && isset( $groups_colors[ $idx ] ) ) {
763 + $tagc = $groups_colors[ $idx ];
764 + } else {
765 + $tagx = MainWP_DB_Common::instance()->get_group_by_name( $tag );
766 + $tagc = is_object( $tagx ) && '' !== $tagx->color ? $tagx->color : '';
767 + }
747 768
748 - if ( empty( $screen ) ) {
749 - $screen = get_current_screen();
750 - } elseif ( is_string( $screen ) ) {
751 - $screen = convert_to_screen( $screen );
752 - }
769 + $tag_id = '';
770 + if ( isset( $tags_ids[ $idx ] ) && ! empty( $tags_ids[ $idx ] ) ) {
771 + $tag_id = $tags_ids[ $idx ];
772 + }
753 773
754 - if ( ! isset( $screen->id ) ) {
755 - return;
756 - }
774 + $tags_labels .= self::get_site_tag_label( $tag, $tag_id, $href, $tagc );
775 + }
776 + }
777 + }
778 + return $tags_labels;
779 + }
757 780
758 - $page = $screen->id;
781 + /**
782 + * Gets site tags
783 + *
784 + * @param array $item Array containing child site data.
785 + *
786 + * @return mixed Single Row Classes Item.
787 + */
788 + public static function get_site_tags_belong( $item ) { // phpcs:ignore -- NOSONAR - complex.
759 789
760 - return $page;
761 - }
790 + if ( ! is_array( $item ) || ! isset( $item['wpgroups_belong'] ) ) {
791 + return static::get_site_tags( $item );
792 + }
762 793
763 - /**
764 - * Method get_child_response()
765 - *
766 - * Get response from Child Site.
767 - *
768 - * @param mixed $data Data to process.
769 - *
770 - * @return json $data|true.
771 - */
772 - public static function get_child_response( $data ) {
773 - $resp = json_decode( $data, true );
794 + $href = 'admin.php?page=managesites&g=';
774 795
775 - if ( is_array( $resp ) ) {
776 - if ( isset( $resp['error'] ) ) {
777 - $resp['error'] = MainWP_Utility::esc_content( $resp['error'] );
778 - }
796 + $tags = '';
797 + $tags_labels = '';
779 798
780 - if ( isset( $resp['message'] ) ) {
781 - if ( is_string( $resp['message'] ) ) {
782 - $resp['message'] = MainWP_Utility::esc_content( $resp['message'] );
783 - }
784 - }
799 + if ( isset( $item['wpgroups_belong'] ) && ! empty( $item['wpgroups_belong'] ) ) {
785 800
786 - if ( isset( $resp['error_message'] ) ) {
787 - $resp['error_message'] = MainWP_Utility::esc_content( $resp['error_message'] );
788 - }
801 + $tags = $item['wpgroups_belong'];
802 + $tags = explode( ',', $tags );
803 + $tags_ids = $item['wpgroupids_belong'];
804 + $tags_ids = explode( ',', $tags_ids );
789 805
790 - if ( isset( $resp['notices'] ) ) {
791 - if ( is_string( $resp['notices'] ) ) {
792 - $resp['notices'] = MainWP_Utility::esc_content( $resp['notices'] );
793 - } elseif ( is_array( $resp['notices'] ) ) {
794 - $notices = array();
795 - foreach ( $resp['notices'] as $noti ) {
796 - if ( ! empty( $noti ) && is_string( $noti ) ) {
797 - $notices[] = MainWP_Utility::esc_content( $noti );
798 - }
799 - }
800 - if ( ! empty( $notices ) ) {
801 - $resp['notices'] = implode( ' || ', $notices );
802 - }
803 - }
804 - }
805 - }
806 + $tags_colors = explode( ',', $item['wpgroupcolors_belong'] );
806 807
807 - return $resp;
808 - }
808 + if ( is_array( $tags ) ) {
809 + foreach ( $tags as $idx => $tag ) {
810 + $tag = trim( $tag );
811 + $tagc = $tags_colors[ $idx ];
809 812
810 - /**
811 - * Method maybe_unserialyze()
812 - *
813 - * Check if $data is serialized,
814 - * if it isn't then base64_decode it.
815 - *
816 - * @param mixed $data Data to check.
817 - *
818 - * @return mixed $data.
819 - */
820 - public static function maybe_unserialyze( $data ) {
821 - if ( empty( $data ) || is_array( $data ) ) {
822 - return $data;
823 - } elseif ( is_serialized( $data ) ) {
824 - // phpcs:ignore -- for compatability.
825 - return maybe_unserialize( $data );
826 - } else {
827 - // phpcs:ignore -- for compatability.
828 - return maybe_unserialize( base64_decode( $data ) );
829 - }
830 - }
813 + $tag_id = '';
814 + if ( isset( $tags_ids[ $idx ] ) && ! empty( $tags_ids[ $idx ] ) ) {
815 + $tag_id = $tags_ids[ $idx ];
816 + }
831 817
832 - /**
833 - * Method get_openssl_conf()
834 - *
835 - * Get dashboard openssl configuration.
836 - */
837 - public static function get_openssl_conf() {
818 + $tags_labels .= self::get_site_tag_label( $tag, $tag_id, $href, $tagc );
819 + }
820 + }
821 + }
822 + return $tags_labels;
823 + }
838 824
839 - if ( defined( 'MAINWP_CRYPT_RSA_OPENSSL_CONFIG' ) ) {
840 - return MAINWP_CRYPT_RSA_OPENSSL_CONFIG;
841 - }
842 - $lib_loc = get_option( 'mainwp_opensslLibLocation' );
843 - return ! empty( $lib_loc ) ? $lib_loc : '';
844 - }
825 + /**
826 + * Method get_tag_contrast_color()
827 + *
828 + * Pick white or black text for a tag background.
829 + * White is returned when its contrast is at least 4.5:1.
830 + *
831 + * @param string $background Stored tag color.
832 + *
833 + * @return string #ffffff, #000000, or an empty string when the color is unusable.
834 + */
835 + public static function get_tag_contrast_color( $background ) {
836 + $normalized = self::normalize_tag_color( $background );
837 + if ( '' === $normalized ) {
838 + return '';
839 + }
845 840
846 - /**
847 - * Get tokens of site.
848 - *
849 - * @param object $site The website.
850 - *
851 - * @return array Array of tokens.
852 - *
853 - * @uses \MainWP\Dashboard\MainWP_DB::get_website_option()
854 - */
855 - public static function get_tokens_site_values( $site ) {
841 + if ( isset( self::$tag_contrast_cache[ $normalized ] ) ) {
842 + return self::$tag_contrast_cache[ $normalized ];
843 + }
856 844
857 - $tokens_values = array(
858 - '[site.name]' => $site->name,
859 - '[site.url]' => $site->url,
860 - );
845 + $white_contrast = 1.05 / ( self::tag_relative_luminance( $normalized ) + 0.05 );
846 + if ( $white_contrast >= 4.5 ) {
847 + $foreground = '#ffffff';
848 + } else {
849 + $foreground = '#000000';
850 + }
861 851
862 - $site_info = MainWP_DB::instance()->get_website_option( $site, 'site_info' );
863 - $site_info = ! empty( $site_info ) ? json_decode( $site_info, true ) : array();
852 + self::$tag_contrast_cache[ $normalized ] = $foreground;
853 + return $foreground;
854 + }
864 855
865 - if ( is_array( $site_info ) ) {
866 - $map_site_tokens = array(
867 - 'client.site.version' => 'wpversion', // Displays the WP version of the child site.
868 - 'client.site.theme' => 'themeactivated', // Displays the currently active theme for the child site.
869 - 'client.site.php' => 'phpversion', // Displays the PHP version of the child site.
870 - 'client.site.mysql' => 'mysql_version', // Displays the MySQL version of the child site.
871 - );
872 - foreach ( $map_site_tokens as $tok => $val ) {
873 - $tokens_value[ '[' . $tok . ']' ] = ( is_array( $site_info ) && isset( $site_info[ $val ] ) ) ? $site_info[ $val ] : '';
874 - }
875 - }
856 + /**
857 + * Method normalize_tag_color()
858 + *
859 + * Reduce a stored tag color to lowercase #rrggbb.
860 + *
861 + * @param string $background Stored tag color.
862 + *
863 + * @return string Normalized color, or an empty string when it is not 3 or 6 digit hex.
864 + */
865 + private static function normalize_tag_color( $background ) {
866 + if ( ! is_string( $background ) ) {
867 + return '';
868 + }
876 869
877 - return $tokens_values;
878 - }
870 + $background = trim( $background );
871 + if ( ! preg_match( '/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/', $background ) ) {
872 + return '';
873 + }
879 874
880 - /**
881 - *
882 - * Replace site tokens.
883 - *
884 - * @param string $str String data.
885 - * @param array $replace_tokens array of tokens.
886 - *
887 - * @return string content with replaced tokens.
888 - */
889 - public static function replace_tokens_values( $str, $replace_tokens ) {
890 - $tokens = array_keys( $replace_tokens );
891 - $values = array_values( $replace_tokens );
892 - return str_replace( $tokens, $values, $str );
893 - }
875 + $background = strtolower( $background );
876 + if ( 4 === strlen( $background ) ) {
877 + $background = '#' . $background[1] . $background[1] . $background[2] . $background[2] . $background[3] . $background[3];
878 + }
894 879
895 - /**
896 - *
897 - * Set timeout limit.
898 - *
899 - * @param int $timeout timeout value.
900 - */
901 - public static function set_time_limit( $timeout = 0 ) {
902 - if ( false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) ) {
903 - set_time_limit( $timeout );
904 - }
905 - }
880 + return $background;
881 + }
906 882
907 - /**
908 - *
909 - * Method get_plugin_theme_info().
910 - *
911 - * Get WordPress plugin/theme info.
912 - *
913 - * @param string $what 'plugin' or 'theme'.
914 - * @param array $params Plugin/Theme info params.
915 - */
916 - public static function get_plugin_theme_info( $what, $params = array() ) {
883 + /**
884 + * Method tag_relative_luminance()
885 + *
886 + * WCAG relative luminance for a normalized #rrggbb color.
887 + *
888 + * @param string $hex Normalized background.
889 + *
890 + * @return float Luminance between 0 and 1.
891 + */
892 + private static function tag_relative_luminance( $hex ) {
893 + $red = self::tag_channel_linear( hexdec( substr( $hex, 1, 2 ) ) / 255 );
894 + $green = self::tag_channel_linear( hexdec( substr( $hex, 3, 2 ) ) / 255 );
895 + $blue = self::tag_channel_linear( hexdec( substr( $hex, 5, 2 ) ) / 255 );
917 896
918 - if ( 'plugin' === $what ) {
919 - include_once ABSPATH . '/wp-admin/includes/plugin-install.php';
920 - return plugins_api(
921 - 'plugin_information',
922 - $params
923 - );
924 - } elseif ( 'theme' === $what ) {
925 - include_once ABSPATH . '/wp-admin/includes/theme-install.php';
926 - return themes_api(
927 - 'theme_information',
928 - $params
929 - );
897 + return ( 0.2126 * $red ) + ( 0.7152 * $green ) + ( 0.0722 * $blue );
898 + }
930 899
931 - }
900 + /**
901 + * Method tag_channel_linear()
902 + *
903 + * Convert an sRGB channel to linear light.
904 + *
905 + * @param float $channel Channel from 0 to 1.
906 + *
907 + * @return float Linear channel.
908 + */
909 + private static function tag_channel_linear( $channel ) {
910 + if ( $channel <= 0.04045 ) {
911 + return $channel / 12.92;
912 + }
932 913
933 - return false;
934 - }
914 + return pow( ( $channel + 0.055 ) / 1.055, 2.4 );
915 + }
935 916
936 - /**
937 - * Method update_cached_icons().
938 - *
939 - * Update cached icons
940 - *
941 - * @param string $icon The icon.
942 - * @param string $slug slug.
943 - * @param string $type Type: plugin|theme.
944 - * @param bool $custom_icon Custom icon or not. Default: false.
945 - */
946 - public static function update_cached_icons( $icon, $slug, $type, $custom_icon = false ) {
917 + /**
918 + * Method get_site_tag_label()
919 + *
920 + * Build one tag chip for the linked and plain renderers.
921 + *
922 + * @param string $tag Tag name.
923 + * @param string $tag_id Tag id. An empty id renders a plain chip.
924 + * @param string $href Link prefix.
925 + * @param string $background Stored background color.
926 + *
927 + * @return string Chip HTML.
928 + */
929 + private static function get_site_tag_label( $tag, $tag_id, $href, $background ) {
930 + $foreground = static::get_tag_contrast_color( $background );
931 + $tag_style = '';
947 932
948 - if ( 'plugin' === $type ) {
949 - $option_name = 'plugins_icons';
950 - } elseif ( 'theme' === $type ) {
951 - $option_name = 'themes_icons';
952 - } else {
953 - return false;
954 - }
933 + if ( '' !== $foreground ) {
934 + $normalized = self::normalize_tag_color( $background );
935 + $tag_style = 'style="background-color:' . esc_attr( $normalized ) . ';color:' . esc_attr( $foreground ) . '"';
936 + }
955 937
956 - $cached_icons = MainWP_DB::instance()->get_general_option( $option_name, 'array' );
938 + if ( ! empty( $tag_id ) ) {
939 + $tag_a_style = 'style="opacity:1;"';
940 + if ( '' !== $foreground ) {
941 + $tag_a_style = 'style="color:' . esc_attr( $foreground ) . '!important;opacity:1;"';
942 + }
957 943
958 - $icon = apply_filters( 'mainwp_update_cached_icons', $icon, $slug, $type );
944 + return '<span ' . $tag_style . ' tag_id="' . esc_attr( $tag_id ) . '" class="ui tag mini label"><a ' . $tag_a_style . ' href="' . esc_url( $href . $tag_id ) . '">' . esc_html( $tag ) . '</a></span>';
945 + }
959 946
960 - if ( isset( $cached_icons[ $slug ] ) ) {
961 - $value = $cached_icons[ $slug ];
962 - } else {
963 - $value = array(
964 - 'lasttime_cached' => time(),
965 - 'path_custom' => '',
966 - 'path' => '',
967 - );
968 - }
947 + return '<span ' . $tag_style . ' class="ui tag mini label">' . esc_html( $tag ) . '</span>';
948 + }
969 949
970 - $value['lasttime_cached'] = time();
950 + /**
951 + * Filter client tags
952 + *
953 + * @param array $item Array containing tags.
954 + *
955 + * @return mixed Single Row Classes Item.
956 + */
957 + public static function client_tags_filter( $item ) {
958 + $tags = $item['wpgroups'];
959 + $tags = explode( ',', $tags );
960 + $tags = array_values( array_unique( $tags ) );
971 961
972 - if ( $custom_icon ) {
973 - $value['path_custom'] = $icon;
974 - } else {
975 - $value['path'] = $icon;
976 - }
962 + $tags_ids = $item['wpgroupids'];
963 + $tags_ids = explode( ',', $tags_ids );
964 + $tags_ids = array_values( array_unique( $tags_ids ) );
977 965
978 - // update cache.
979 - $cached_icons[ $slug ] = $value;
966 + $return = array();
980 967
981 - MainWP_DB::instance()->update_general_option( $option_name, $cached_icons, 'array' );
982 - return true;
983 - }
968 + $return['wpgroups'] = $tags;
969 + $return['wpgroupids'] = $tags_ids;
970 + return $return;
971 + }
984 972
985 - /**
986 - * Private function Fetch a plugin|theme icon via API from WordPress.org
987 - *
988 - * @param string $slug Plugin|Theme slug.
989 - * @param string $type Plugin|Theme.
990 - */
991 - private static function fetch_wp_org_icons( $slug, $type ) {
992 - if ( 'plugin' === $type ) {
993 - $fields = array(
994 - 'tags' => false,
995 - 'icons' => true,
996 - 'sections' => false,
997 - 'description' => false,
998 - 'tested' => false,
999 - 'requires' => false,
1000 - 'rating' => false,
1001 - 'downloaded' => false,
1002 - 'downloadlink' => false,
1003 - 'last_updated' => false,
1004 - 'homepage' => false,
1005 - 'compatibility' => false,
1006 - 'ratings' => false,
1007 - 'added' => false,
1008 - 'donate_link' => false,
1009 - );
1010 - } elseif ( 'theme' === $type ) {
1011 - $fields = array(
1012 - 'screenshots' => true,
1013 - 'screenshot_count' => 5,
1014 - 'sections' => false,
1015 - 'rating' => false,
1016 - 'downloaded' => false,
1017 - 'download_link' => false,
1018 - 'last_updated' => false,
1019 - 'tags' => false,
1020 - 'template' => false,
1021 - 'parent' => false,
1022 - 'screenshot_url' => false,
1023 - 'homepage' => false,
1024 - );
973 + /**
974 + * Method is_suspended_site()
975 + *
976 + * Check if enable site.
977 + *
978 + * @param mixed $website The website.
979 + */
980 + public static function is_suspended_site( $website = false ) {
981 + if ( empty( $website ) ) {
982 + return true; // empty so return as suspended.
983 + }
984 + if ( is_array( $website ) ) {
985 + return '1' === $website['suspended'];
986 + } elseif ( is_object( $website ) ) {
987 + if ( ! property_exists( $website, 'suspended' ) && property_exists( $website, 'id' ) ) {
988 + $website = MainWP_DB::instance()->get_website_by_id( $website->id );
989 + }
990 + if ( property_exists( $website, 'suspended' ) ) {
991 + return '1' === $website->suspended;
992 + }
993 + } elseif ( is_numeric( $website ) ) {
994 + $siteId = $website;
995 + $website = MainWP_DB::instance()->get_website_by_id( $siteId );
996 + if ( $website ) {
997 + return static::is_suspended_site( $website );
998 + }
999 + }
1000 + return false;
1001 + }
1025 1002
1026 - } else {
1027 - return false;
1028 - }
1003 + /**
1004 + * Method get_current_wpid()
1005 + *
1006 + * Get current Child Site ID.
1007 + *
1008 + * @return string $current_user->current_site_id Current Child Site ID.
1009 + */
1010 + public static function get_current_wpid() {
1029 1011
1030 - $icon = '';
1031 - if ( 'theme' === $type ) {
1032 - // with $fields empty to get screenshot_url of theme.
1033 - $info = self::get_plugin_theme_info(
1034 - $type,
1035 - array(
1036 - 'slug' => $slug,
1037 - 'timeout' => 60,
1038 - )
1039 - );
1040 - if ( is_object( $info ) && ! empty( $info->screenshot_url ) ) {
1041 - $icon = $info->screenshot_url;
1042 - }
1043 - }
1012 + /**
1013 + * Current user global.
1014 + *
1015 + * @global string
1016 + */
1017 + global $current_user;
1044 1018
1045 - // if get screenshot_url of theme success.
1046 - if ( ! empty( $icon ) ) {
1047 - $option_name = 'themes_icons';
1048 - } else {
1049 - $info = self::get_plugin_theme_info(
1050 - $type,
1051 - array(
1052 - 'slug' => $slug,
1053 - 'fields' => $fields,
1054 - 'timeout' => 60,
1055 - )
1056 - );
1057 - $option_name = 'plugins_icons';
1058 - $icon = '';
1059 - if ( 'plugin' === $type ) {
1060 - if ( is_object( $info ) && property_exists( $info, 'icons' ) && isset( $info->icons['1x'] ) ) {
1061 - $icon = $info->icons['1x'];
1062 - }
1063 - } else {
1064 - if ( is_object( $info ) && property_exists( $info, 'screenshots' ) && isset( $info->screenshots[0] ) ) {
1065 - $icon = $info->screenshots[0];
1066 - }
1067 - $option_name = 'themes_icons';
1068 - }
1069 - }
1019 + return $current_user->current_site_id;
1020 + }
1070 1021
1071 - $fetched_icon = '';
1072 - if ( '' !== $icon ) {
1073 - $fetched_icon = rawurlencode( $icon );
1074 - }
1022 + /**
1023 + * Method set_current_wpid()
1024 + *
1025 + * Set the current Child Site ID.
1026 + *
1027 + * @param mixed $wpid Child Site ID.
1028 + */
1029 + public static function set_current_wpid( $wpid ) {
1075 1030
1076 - $cached_icons = MainWP_DB::instance()->get_general_option( $option_name, 'array' );
1031 + /**
1032 + * Current user global.
1033 + *
1034 + * @global string
1035 + */
1036 + global $current_user;
1077 1037
1078 - if ( isset( $cached_icons[ $slug ] ) ) {
1079 - if ( '' === $fetched_icon && ! empty( $cached_icons[ $slug ]['path'] ) ) {
1080 - // if fetch icon empty then used caching icon.
1081 - $fetched_icon = $cached_icons[ $slug ]['path'];
1082 - $icon = rawurldecode( $fetched_icon );
1083 - }
1084 - }
1038 + $current_user->current_site_id = $wpid;
1039 + }
1085 1040
1086 - self::update_cached_icons( $fetched_icon, $slug, $type );
1041 + /**
1042 + * Method get_page_id()
1043 + *
1044 + * Get current Page ID.
1045 + *
1046 + * @param null $screen Current Screen ID.
1047 + *
1048 + * @return string $page Current page ID.
1049 + */
1050 + public static function get_page_id( $screen = null ) {
1087 1051
1088 - if ( '' !== $icon ) {
1089 - return $icon;
1090 - }
1091 - return false;
1092 - }
1052 + if ( empty( $screen ) ) {
1053 + $screen = get_current_screen();
1054 + } elseif ( is_string( $screen ) ) {
1055 + $screen = convert_to_screen( $screen );
1056 + }
1093 1057
1094 - /**
1095 - * Method handle_get_icon()
1096 - *
1097 - * @param string $slug Plugin slug.
1098 - * @param string $type Type: theme|plugin.
1099 - */
1100 - public static function handle_get_icon( $slug, $type ) {
1101 - if ( empty( $slug ) ) {
1102 - return false;
1103 - }
1104 - if ( 'plugin' === $type || 'theme' === $type ) {
1105 - return self::fetch_wp_org_icons( $slug, $type );
1106 - }
1107 - return '';
1108 - }
1058 + if ( ! isset( $screen->id ) ) {
1059 + return '';
1060 + }
1109 1061
1062 + return $screen->id;
1063 + }
1110 1064
1111 - /**
1112 - * Gets a plugin icon via API from WordPress.org
1113 - *
1114 - * @param string $slug Plugin slug.
1115 - * @param bool $forced_get Forced get icon, default: false.
1116 - */
1117 - public static function get_plugin_icon( $slug, $forced_get = false ) {
1065 + /**
1066 + * Check register error
1067 + *
1068 + * @param string $error Error message.
1069 + *
1070 + * @return string
1071 + */
1072 + public static function check_register_error( $error ) {
1073 + if ( is_string( $error ) ) {
1074 + $error_langs = array(
1075 + 'This site already contains a link. Please deactivate and reactivate the',
1076 + 'Diese Website enthält bereits diesen Link',
1077 + 'Este sitio ya contiene un enlace. Por favor, desactive y vuelva a activar el',
1078 + 'Ce site contient déjà un lien. Veuillez désactiver et réactiver l’extension',
1079 + 'Este site já contém um link. Desative e reative o plug-in',
1080 + );
1081 + foreach ( $error_langs as $err ) {
1082 + if ( false !== stripos( $error, $err ) ) {
1083 + return esc_html__( 'The child site already contains connection data. To proceed, you need to clear the connection data on the child site. Navigate to WP Admin > Settings > MainWP Child > MainWP Child Settings, and click the Clear Connection Data button. After clearing the data, try reconnecting the site.', 'mainwp' );
1084 + }
1085 + }
1086 + }
1087 + return $error;
1088 + }
1118 1089
1119 - $icon = apply_filters( 'mainwp_get_plugin_theme_icon', '', $slug, 'plugin' );
1090 + /**
1091 + * Method get_child_response()
1092 + *
1093 + * Get response from Child Site.
1094 + *
1095 + * @param mixed $data Data to process.
1096 + *
1097 + * @return json $data|true.
1098 + */
1099 + public static function get_child_response( $data ) { // phpcs:ignore -- NOSONAR - complex.
1100 + $resp = json_decode( $data, true );
1120 1101
1121 - if ( ! empty( $icon ) ) {
1122 - return $icon;
1123 - }
1102 + if ( is_array( $resp ) ) {
1103 + if ( isset( $resp['error'] ) ) {
1104 + $resp['error'] = MainWP_Utility::esc_content( $resp['error'] );
1105 + $resp['error'] = static::check_register_error( $resp['error'] );
1106 + }
1124 1107
1125 - $forced_get = apply_filters( 'mainwp_forced_get_plugin_theme_icon', $forced_get, $slug, 'plugin' );
1108 + if ( isset( $resp['message'] ) && is_string( $resp['message'] ) ) {
1109 + $resp['message'] = MainWP_Utility::esc_content( $resp['message'] );
1110 + }
1126 1111
1127 - if ( $forced_get ) {
1128 - $fet_icon = self::fetch_wp_org_icons( $slug, 'plugin' );
1129 - if ( false !== $fet_icon ) {
1130 - $scr = MainWP_Utility::remove_http_prefix( $fet_icon );
1131 - $icon = '<img style="display:inline-block" class="ui mini circular image" updated-icon="true" src="' . esc_attr( $scr ) . '" />';
1132 - return $icon;
1133 - }
1134 - return $icon;
1135 - }
1112 + if ( isset( $resp['error_message'] ) ) {
1113 + $resp['error_message'] = MainWP_Utility::esc_content( $resp['error_message'] );
1114 + }
1136 1115
1137 - // checks expired.
1138 - $cached_icons = MainWP_DB::instance()->get_general_option( 'plugins_icons', 'array' );
1116 + if ( isset( $resp['notices'] ) ) {
1117 + if ( is_string( $resp['notices'] ) ) {
1118 + $resp['notices'] = MainWP_Utility::esc_content( $resp['notices'] );
1119 + } elseif ( is_array( $resp['notices'] ) ) {
1120 + $notices = array();
1121 + foreach ( $resp['notices'] as $noti ) {
1122 + if ( ! empty( $noti ) && is_string( $noti ) ) {
1123 + $notices[] = MainWP_Utility::esc_content( $noti );
1124 + }
1125 + }
1126 + if ( ! empty( $notices ) ) {
1127 + $resp['notices'] = implode( ' || ', $notices );
1128 + }
1129 + }
1130 + }
1131 + }
1139 1132
1140 - if ( ! empty( $cached_icons ) ) {
1141 - $lasttime_clear_cached = MainWP_DB::instance()->get_general_option( 'lasttime_clear_cached_plugins_icon' );
1142 - if ( time() > ( intval( $lasttime_clear_cached ) + MONTH_IN_SECONDS ) ) {
1143 - $updated = false;
1144 - $new_cached = array();
1145 - foreach ( $cached_icons as $sl => $val ) {
1146 - if ( empty( $val['path_custom'] ) && time() < ( intval( $val['lasttime_cached'] ) + 12 * MONTH_IN_SECONDS ) ) {
1147 - $new_cached[ $sl ] = $val; // unset.
1148 - $updated = true;
1149 - }
1150 - }
1151 - if ( $updated ) {
1152 - MainWP_DB::instance()->update_general_option( 'plugins_icons', $new_cached, 'array' );
1153 - }
1154 - MainWP_DB::instance()->update_general_option( 'lasttime_clear_cached_plugins_icon', time() );
1155 - }
1156 - }
1133 + return $resp;
1134 + }
1157 1135
1158 - return self::get_plugin_theme_icon( $slug, 'plugin' );
1159 - }
1136 + /**
1137 + * Method maybe_unserialyze()
1138 + *
1139 + * Check if $data is serialized,
1140 + * if it isn't then base64_decode it.
1141 + *
1142 + * @param mixed $data Data to check.
1143 + *
1144 + * @return mixed $data.
1145 + */
1146 + public static function maybe_unserialyze( $data ) {
1147 + if ( empty( $data ) || is_array( $data ) ) {
1148 + return $data;
1149 + } elseif ( is_serialized( $data ) ) {
1150 + // phpcs:ignore -- for compatability.
1151 + return maybe_unserialize( $data );
1152 + } else {
1153 + // phpcs:ignore -- for compatability.
1154 + return maybe_unserialize( base64_decode( $data ) );
1155 + }
1156 + }
1160 1157
1161 - /**
1162 - * Gets a theme icon via API from WordPress.org
1163 - *
1164 - * @param string $slug Theme slug.
1165 - * @param bool $forced_get Forced get icon, default: false.
1166 - */
1167 - public static function get_theme_icon( $slug, $forced_get = false ) {
1158 + /**
1159 + * Method get_openssl_conf()
1160 + *
1161 + * Get dashboard openssl configuration.
1162 + */
1163 + public static function get_openssl_conf() {
1168 1164
1169 - $icon = apply_filters( 'mainwp_get_plugin_theme_icon', '', $slug, 'theme' );
1165 + if ( defined( 'MAINWP_CRYPT_RSA_OPENSSL_CONFIG' ) ) {
1166 + return MAINWP_CRYPT_RSA_OPENSSL_CONFIG;
1167 + }
1168 + $lib_loc = get_option( 'mainwp_opensslLibLocation' );
1169 + return ! empty( $lib_loc ) ? $lib_loc : '';
1170 + }
1170 1171
1171 - if ( ! empty( $icon ) ) {
1172 - return $icon;
1173 - }
1172 + /**
1173 + * Get tokens of site.
1174 + *
1175 + * @param object $site The website.
1176 + * @param bool $monitor Get tokens for monitor.
1177 + * @param mixed $website_status Object containing the child site status.
1178 + *
1179 + * @return array Array of tokens.
1180 + *
1181 + * @uses \MainWP\Dashboard\MainWP_DB::get_website_option()
1182 + */
1183 + public static function get_tokens_site_values( $site, $monitor = false, $website_status = false) { //phpcs:ignore -- NOSONAR -complex.
1174 1184
1175 - $forced_get = apply_filters( 'mainwp_forced_get_plugin_theme_icon', $forced_get, $slug, 'theme' );
1185 + $tokens_values = array(
1186 + '[site.name]' => $site->name,
1187 + '[site.url]' => $site->url,
1188 + );
1176 1189
1177 - if ( $forced_get ) {
1178 - $fet_icon = self::fetch_wp_org_icons( $slug, 'theme' );
1179 - if ( false !== $fet_icon ) {
1180 - $scr = MainWP_Utility::remove_http_prefix( $fet_icon );
1181 - $icon = '<img style="display:inline-block" class="ui mini circular image" updated-icon="true" src="' . esc_attr( $scr ) . '" />';
1182 - }
1183 - return $icon;
1184 - }
1190 + $site_info = MainWP_DB::instance()->get_website_option( $site, 'site_info' );
1191 + $site_info = ! empty( $site_info ) ? json_decode( $site_info, true ) : array();
1185 1192
1186 - // checks expired.
1187 - $cached_icons = MainWP_DB::instance()->get_general_option( 'themes_icons', 'array' );
1193 + if ( is_array( $site_info ) ) {
1194 + $map_site_tokens = array(
1195 + 'client.site.version' => 'wpversion', // Displays the WP version of the child site.
1196 + 'client.site.theme' => 'themeactivated', // Displays the currently active theme for the child site.
1197 + 'client.site.php' => 'phpversion', // Displays the PHP version of the child site.
1198 + 'client.site.mysql' => 'mysql_version', // Displays the MySQL version of the child site.
1199 + );
1200 + foreach ( $map_site_tokens as $tok => $val ) {
1201 + $tokens_values[ '[' . $tok . ']' ] = ( is_array( $site_info ) && isset( $site_info[ $val ] ) ) ? $site_info[ $val ] : '';
1202 + }
1203 + }
1188 1204
1189 - if ( ! empty( $cached_icons ) ) {
1190 - $lasttime_clear_cached = MainWP_DB::instance()->get_general_option( 'lasttime_clear_cached_themes_icon' );
1191 - if ( time() > ( intval( $lasttime_clear_cached ) + MONTH_IN_SECONDS ) ) {
1192 - $updated = false;
1193 - $new_cached = array();
1194 - foreach ( $cached_icons as $sl => $val ) {
1195 - if ( empty( $val['path_custom'] ) && time() < ( intval( $val['lasttime_cached'] ) + 12 * MONTH_IN_SECONDS ) ) {
1196 - $new_cached[ $sl ] = $val;
1197 - $updated = true;
1198 - }
1199 - }
1200 - if ( $updated ) {
1201 - MainWP_DB::instance()->update_general_option( 'themes_icons', $new_cached, 'array' );
1202 - }
1203 - MainWP_DB::instance()->update_general_option( 'lasttime_clear_cached_themes_icon', time() );
1204 - }
1205 - }
1205 + if ( $monitor ) {
1206 + if ( is_object( $website_status ) && property_exists( $website_status, 'status' ) ) {
1207 + $status = $website_status->status ? 'UP' : 'DOWN';
1208 + } else {
1209 + $active_monitor = 0;
1210 + $primary_monitor = MainWP_DB_Uptime_Monitoring::instance()->get_monitor_by( $site->id, 'issub', 0 );
1211 + if ( $primary_monitor ) {
1212 + $global_settings = MainWP_Uptime_Monitoring_Handle::get_global_monitoring_settings();
1213 + $active_monitor = MainWP_Uptime_Monitoring_Connect::get_apply_setting( 'active', (int) $primary_monitor->active, $global_settings, -1, 60 );
1214 + }
1215 + if ( $active_monitor ) {
1216 + $status = 'PENDING';
1217 + $last = MainWP_DB_Uptime_Monitoring::instance()->get_last_site_heartbeat( $site->id, false );
1218 + if ( $last ) {
1219 + $status = $last && $last->status ? 'UP' : 'DOWN';
1220 + }
1221 + } else {
1222 + $status = 'DISABLED';
1223 + }
1224 + }
1206 1225
1207 - return self::get_plugin_theme_icon( $slug, 'theme' );
1208 - }
1226 + $tokens_values['[uptime.status]'] = $status;
1227 + }
1228 + return $tokens_values;
1229 + }
1209 1230
1231 + /**
1232 + *
1233 + * Replace site tokens.
1234 + *
1235 + * @param string $str String data.
1236 + * @param array $replace_tokens array of tokens.
1237 + *
1238 + * @return string content with replaced tokens.
1239 + */
1240 + public static function replace_tokens_values( $str, $replace_tokens ) {
1241 + $tokens = array_keys( $replace_tokens );
1242 + $values = array_values( $replace_tokens );
1243 + return str_replace( $tokens, $values, $str );
1244 + }
1210 1245
1211 - /**
1212 - * Gets a plugin|theme icon to output.
1213 - *
1214 - * @param string $slug Plugin|Theme slug.
1215 - * @param string $type Type icon, plugin|theme.
1216 - */
1217 - private static function get_plugin_theme_icon( $slug, $type ) { // phpcs:ignore -- Current complexity is the only way to achieve desired results, pull request solutions appreciated.
1246 + /**
1247 + *
1248 + * Set timeout limit.
1249 + *
1250 + * @param int $timeout timeout value.
1251 + */
1252 + public static function set_time_limit( $timeout = 0 ) {
1253 + if ( false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) ) {
1254 + set_time_limit( $timeout );
1255 + }
1256 + }
1218 1257
1219 - if ( 'plugin' === $type ) {
1220 - $option_name = 'plugins_icons';
1221 - } elseif ( 'theme' === $type ) {
1222 - $option_name = 'themes_icons';
1223 - } else {
1224 - return '<i style="font-size: 17px" class="plug circular inverted icon" not-cached-path="true"></i>';
1225 - }
1258 + /**
1259 + *
1260 + * Method get_plugin_theme_info().
1261 + *
1262 + * Get WordPress plugin/theme info.
1263 + *
1264 + * @param string $what 'plugin' or 'theme'.
1265 + * @param array $params Plugin/Theme info params.
1266 + */
1267 + public static function get_plugin_theme_info( $what, $params = array() ) {
1226 1268
1227 - $cached_icons = MainWP_DB::instance()->get_general_option( $option_name, 'array' );
1269 + if ( 'plugin' === $what ) {
1270 + include_once ABSPATH . '/wp-admin/includes/plugin-install.php'; // NOSONAR - WP compatible.
1271 + return plugins_api(
1272 + 'plugin_information',
1273 + $params
1274 + );
1275 + } elseif ( 'theme' === $what ) {
1276 + include_once ABSPATH . '/wp-admin/includes/theme-install.php'; // NOSONAR - WP compatible.
1277 + return themes_api(
1278 + 'theme_information',
1279 + $params
1280 + );
1228 1281
1229 - $cached_days = apply_filters( 'mainwp_plugin_theme_icon_cache_days', 15, $slug, $type ); // default 15 days.
1282 + }
1230 1283
1231 - $attr_slug = ' icon-type="' . esc_attr( $type ) . '" item-slug="' . esc_attr( $slug ) . '" ';
1232 - $cls_expired = ' cached-icon-expired ';
1233 - $cls_uploadable = ' cached-icon-customable ';
1284 + return false;
1285 + }
1234 1286
1235 - $icon = '';
1287 + /**
1288 + * Method save_cached_icons().
1289 + *
1290 + * Save cached icons
1291 + *
1292 + * @param string $icon The icon.
1293 + * @param string $slug slug.
1294 + * @param string $type Type: plugin|theme.
1295 + */
1296 + public static function save_cached_icons( $icon, $slug, $type ) {
1297 + $file_extension = strtolower( pathinfo( $icon, PATHINFO_EXTENSION ) );
1236 1298
1237 - if ( isset( $cached_icons[ $slug ] ) ) {
1238 - $scr = '';
1239 - $is_custom_icon = false;
1240 - if ( ! empty( $cached_icons[ $slug ]['path_custom'] ) ) {
1241 - if ( 'plugin' === $type ) {
1242 - $dirs = self::get_mainwp_dir( 'plugin-icons', true );
1243 - } elseif ( 'theme' === $type ) {
1244 - $dirs = self::get_mainwp_dir( 'theme-icons', true );
1245 - }
1246 - $scr = $dirs[1] . rawurldecode( $cached_icons[ $slug ]['path_custom'] );
1247 - $is_custom_icon = true; // custom icons will not expired.
1248 - } elseif ( ! empty( $cached_icons[ $slug ]['path'] ) ) {
1249 - $scr = rawurldecode( $cached_icons[ $slug ]['path'] );
1250 - $scr = MainWP_Utility::remove_http_prefix( $scr );
1251 - }
1299 + $file_exts = apply_filters(
1300 + 'mainwp_save_cached_icons_file_ext',
1301 + array(
1302 + 'jpeg',
1303 + 'jpg',
1304 + 'gif',
1305 + 'ico',
1306 + 'png',
1307 + )
1308 + );
1252 1309
1253 - $set_cached_expired = apply_filters( 'mainwp_cache_icon_expired', false, $slug, 'theme' );
1254 - $set_expired = false;
1310 + if ( ! in_array( $file_extension, $file_exts ) ) {
1311 + $icon = '';
1312 + }
1313 + static::update_cached_icons( $icon, $slug, $type, false, true );
1314 + }
1255 1315
1256 - if ( $set_cached_expired ) {
1257 - if ( time() > ( intval( $cached_icons[ $slug ]['lasttime_cached'] ) + 15 * MINUTE_IN_SECONDS ) ) {
1258 - $set_expired = true;
1259 - }
1260 - }
1316 + /**
1317 + * Method update_cached_icons().
1318 + *
1319 + * Update cached icons
1320 + *
1321 + * @param string $icon The icon.
1322 + * @param string $slug slug.
1323 + * @param string $type Type: plugin|theme.
1324 + * @param bool $custom_icon Custom icon or not. Default: false.
1325 + * @param bool $noexp No expire icon - that for sync icon.
1326 + */
1327 + public static function update_cached_icons( $icon, $slug, $type, $custom_icon = false, $noexp = false ) {
1261 1328
1262 - $forced_exprided = 1700238511;
1263 - $lasttime_cached = isset( $cached_icons[ $slug ]['lasttime_cached'] ) ? intval( $cached_icons[ $slug ]['lasttime_cached'] ) : 0;
1329 + if ( 'plugin' === $type ) {
1330 + $option_name = 'plugins_icons';
1331 + } elseif ( 'theme' === $type ) {
1332 + $option_name = 'themes_icons';
1333 + } else {
1334 + return false;
1335 + }
1264 1336
1265 - if ( time() > ( $lasttime_cached + $cached_days * DAY_IN_SECONDS ) || $lasttime_cached < $forced_exprided ) { // expired.
1266 - if ( ! empty( $scr ) ) {
1267 - $icon = '<img style="display:inline-block" class="ui mini circular image ' . ( $is_custom_icon ? $cls_uploadable : $cls_expired ) . '" ' . $attr_slug . 'src="' . esc_attr( $scr ) . '"/>'; // to update expired icon.
1268 - } else {
1269 - $icon = '<i style="font-size: 17px" class="plug circular inverted icon ' . $cls_expired . $cls_uploadable . '" ' . $attr_slug . '></i>'; // to update expired icon.
1270 - }
1271 - } elseif ( ! empty( $scr ) ) {
1272 - $icon = '<img style="display:inline-block" class="ui mini circular image ' . ( $is_custom_icon ? $cls_uploadable : ( $set_expired ? $cls_expired : '' ) ) . '" ' . $attr_slug . ' cached-path-icon="true" src="' . esc_attr( $scr ) . '"/>';
1273 - } else {
1274 - $icon = '<i style="font-size: 17px" class="plug circular inverted icon ' . ( $set_expired ? $cls_expired : '' ) . $cls_uploadable . '" ' . $attr_slug . ' cached-path-icon="true"></i>';
1275 - }
1276 - } elseif ( empty( $icon ) ) {
1277 - $icon = '<i style="font-size: 17px" class="plug circular inverted icon ' . $cls_expired . '" ' . $attr_slug . ' not-cached-path="true"></i>'; // not upload when not existed in the cached.
1278 - }
1279 - return $icon;
1280 - }
1337 + $cached_icons = MainWP_DB::instance()->get_general_option( $option_name, 'array' );
1281 1338
1339 + $icon = apply_filters( 'mainwp_update_cached_icons', $icon, $slug, $type );
1282 1340
1283 - /**
1284 - * Method handle_upload_image().
1285 - *
1286 - * Handle upload icons.
1287 - *
1288 - * @param string $sub_folder The sub folder.
1289 - * @param mixed $file_uploader The file uploader.
1290 - * @param mixed $file_index The index of file uploader.
1291 - * @param bool $file_subindex Is file with sub index.
1292 - * @param int $max_width max image width.
1293 - * @param int $max_height max image height.
1294 - */
1295 - public static function handle_upload_image( $sub_folder, $file_uploader, $file_index = 0, $file_subindex = false, $max_width = 300, $max_height = 300 ) { // phpcs:ignore -- complex function. Current complexity is the only way to achieve desired results, pull request solutions appreciated.
1341 + if ( isset( $cached_icons[ $slug ] ) ) {
1342 + $value = $cached_icons[ $slug ];
1343 + } else {
1344 + $value = array(
1345 + 'lasttime_cached' => time(),
1346 + 'path_custom' => '',
1347 + 'path' => '',
1348 + );
1349 + }
1296 1350
1297 - $dirs = self::get_mainwp_dir( $sub_folder, true );
1298 - $base_dir = $dirs[0];
1299 - $base_url = $dirs[1];
1351 + $value['lasttime_cached'] = time();
1300 1352
1301 - $output = array();
1302 - $filename = '';
1303 - $filepath = '';
1353 + if ( $custom_icon ) {
1304 1354
1305 - $file_types = array(
1306 - 'image/jpeg',
1307 - 'image/jpg',
1308 - 'image/gif',
1309 - 'image/x-icon',
1310 - 'image/png',
1311 - );
1355 + if ( isset( $value['path_custom'] ) && isset( $value['path'] ) && $value['path_custom'] === $icon && '' === $value['path'] ) {
1356 + return true; // no change.
1357 + }
1312 1358
1313 - $file_exts = array(
1314 - 'jpeg',
1315 - 'jpg',
1316 - 'gif',
1317 - 'ico',
1318 - 'png',
1319 - );
1359 + $value['path_custom'] = $icon;
1360 + $value['path'] = '';
1361 + } else {
1320 1362
1321 - $upload_ok = ( false === $file_subindex ) ? ( UPLOAD_ERR_OK === $file_uploader['error'][ $file_index ] ) : ( UPLOAD_ERR_OK === $file_uploader['error'][ $file_index ][ $file_subindex ] );
1363 + if ( isset( $value['path_custom'] ) && isset( $value['path'] ) && $value['path'] === $icon && '' === $value['path_custom'] ) {
1364 + return true; // no change.
1365 + }
1322 1366
1323 - if ( $upload_ok ) {
1324 - $tmp_file = ( false === $file_subindex ) ? ( $file_uploader['tmp_name'][ $file_index ] ) : ( $file_uploader['tmp_name'][ $file_index ][ $file_subindex ] );
1367 + $value['path'] = $icon;
1368 + $value['path_custom'] = '';
1369 + }
1325 1370
1326 - if ( is_uploaded_file( $tmp_file ) ) {
1327 - if ( false === $file_subindex ) {
1328 - $file_size = $file_uploader['size'][ $file_index ];
1329 - $file_type = $file_uploader['type'][ $file_index ];
1330 - $file_name = $file_uploader['name'][ $file_index ];
1331 - } else {
1332 - $file_size = $file_uploader['size'][ $file_index ][ $file_subindex ];
1333 - $file_type = $file_uploader['type'][ $file_index ][ $file_subindex ];
1334 - $file_name = $file_uploader['name'][ $file_index ][ $file_subindex ];
1335 - }
1371 + if ( $noexp ) {
1372 + $value['noexpire'] = 1;
1373 + } elseif ( isset( $value['noexpire'] ) ) {
1374 + unset( $value['noexpire'] );
1375 + }
1336 1376
1337 - $file_extension = strtolower( pathinfo( $file_name, PATHINFO_EXTENSION ) );
1377 + // update cache.
1378 + $cached_icons[ $slug ] = $value;
1338 1379
1339 - if ( ( $file_size > 500 * 1025 ) ) {
1340 - $output['error'][] = 3;
1341 - } elseif ( ! in_array( $file_type, $file_types ) ) {
1342 - $output['error'][] = 4;
1343 - } elseif ( ! in_array( $file_extension, $file_exts ) ) {
1344 - $output['error'][] = 5;
1345 - } else {
1380 + /**
1381 + * @since 5.4.0.18
1382 + */
1383 + $cached_icons = apply_filters( 'mainwp_before_save_cached_icons', $cached_icons, $icon, $slug, $type, $custom_icon, $noexp );
1346 1384
1347 - $dest_file = $base_dir . '/' . $file_name;
1348 - $dest_file = dirname( $dest_file ) . '/' . wp_unique_filename( dirname( $dest_file ), basename( $dest_file ) );
1385 + MainWP_DB::instance()->update_general_option( $option_name, $cached_icons, 'array' );
1386 + return true;
1387 + }
1349 1388
1350 - if ( move_uploaded_file( $tmp_file, $dest_file ) ) {
1351 - if ( file_exists( $dest_file ) ) {
1352 - list( $width, $height, $type, $attr ) = getimagesize( $dest_file );
1353 - }
1389 + /**
1390 + * Private function Fetch a plugin|theme icon via API from WordPress.org
1391 + *
1392 + * @param string $slug Plugin|Theme slug.
1393 + * @param string $type Plugin|Theme.
1394 + */
1395 + private static function fetch_wp_org_icons( $slug, $type ) { // phpcs:ignore -- NOSONAR - complex.
1396 + if ( 'plugin' === $type ) {
1397 + $fields = array(
1398 + 'tags' => false,
1399 + 'icons' => true,
1400 + 'sections' => false,
1401 + 'description' => false,
1402 + 'tested' => false,
1403 + 'requires' => false,
1404 + 'rating' => false,
1405 + 'downloaded' => false,
1406 + 'downloadlink' => false,
1407 + 'last_updated' => false,
1408 + 'homepage' => false,
1409 + 'compatibility' => false,
1410 + 'ratings' => false,
1411 + 'added' => false,
1412 + 'donate_link' => false,
1413 + );
1414 + } elseif ( 'theme' === $type ) {
1415 + $fields = array(
1416 + 'screenshots' => true,
1417 + 'screenshot_count' => 5,
1418 + 'sections' => false,
1419 + 'rating' => false,
1420 + 'downloaded' => false,
1421 + 'download_link' => false,
1422 + 'last_updated' => false,
1423 + 'tags' => false,
1424 + 'template' => false,
1425 + 'parent' => false,
1426 + 'screenshot_url' => false,
1427 + 'homepage' => false,
1428 + );
1354 1429
1355 - $resize = false;
1356 - if ( $width > $max_width ) {
1357 - $dst_width = $max_width;
1358 - if ( $height > $max_height ) {
1359 - $dst_height = $max_height;
1360 - } else {
1361 - $dst_height = $height;
1362 - }
1363 - $resize = true;
1364 - } elseif ( $height > $max_height ) {
1365 - $dst_width = $width;
1366 - $dst_height = $max_height;
1367 - $resize = true;
1368 - }
1430 + } else {
1431 + return false;
1432 + }
1369 1433
1370 - if ( $resize ) {
1371 - $src = $dest_file;
1372 - $cropped_file = wp_crop_image( $src, 0, 0, $width, $height, $dst_width, $dst_height, false );
1373 - if ( ! $cropped_file || is_wp_error( $cropped_file ) ) {
1374 - $output['error'][] = 9;
1375 - } else {
1376 - wp_delete_file( $dest_file );
1377 - $filename = basename( $cropped_file );
1378 - $filepath = $cropped_file;
1379 - }
1380 - } else {
1381 - $filename = basename( $dest_file );
1382 - $filepath = $dest_file;
1383 - }
1384 - } else {
1385 - $output['error'][] = 6;
1386 - }
1387 - }
1388 - }
1389 - }
1390 - $output['fileurl'] = ! empty( $filename ) ? $base_url . '/' . $filename : '';
1391 - $output['filepath'] = ! empty( $filepath ) ? $filepath : '';
1392 - $output['filename'] = ! empty( $filename ) ? $filename : '';
1434 + $icon = '';
1435 + if ( 'theme' === $type ) {
1436 + // with $fields empty to get screenshot_url of theme.
1437 + $info = static::get_plugin_theme_info(
1438 + $type,
1439 + array(
1440 + 'slug' => $slug,
1441 + 'timeout' => 60,
1442 + )
1443 + );
1444 + if ( is_object( $info ) && ! empty( $info->screenshot_url ) ) {
1445 + $icon = $info->screenshot_url;
1446 + }
1447 + }
1393 1448
1394 - return $output;
1395 - }
1449 + // if get screenshot_url of theme success.
1450 + if ( ! empty( $icon ) ) {
1451 + $option_name = 'themes_icons';
1452 + } else {
1453 + $info = static::get_plugin_theme_info(
1454 + $type,
1455 + array(
1456 + 'slug' => $slug,
1457 + 'fields' => $fields,
1458 + 'timeout' => 60,
1459 + )
1460 + );
1461 + $option_name = 'plugins_icons';
1462 + $icon = '';
1463 + if ( 'plugin' === $type ) {
1464 + if ( is_object( $info ) && property_exists( $info, 'icons' ) && isset( $info->icons['1x'] ) ) {
1465 + $icon = $info->icons['1x'];
1466 + }
1467 + } else {
1468 + if ( is_object( $info ) && property_exists( $info, 'screenshots' ) && isset( $info->screenshots[0] ) ) {
1469 + $icon = $info->screenshots[0];
1470 + }
1471 + $option_name = 'themes_icons';
1472 + }
1473 + }
1396 1474
1397 - /**
1398 - * Method disabled_wpcore_update_by().
1399 - *
1400 - * Get disabled wpcore update by.
1401 - *
1402 - * @param string $website The website.
1403 - */
1404 - public static function disabled_wpcore_update_by( $website ) {
1405 - $by = self::get_disabled_wpcore_update_host( $website );
1406 - if ( 'flywheel' === $by ) {
1407 - return esc_html__( 'FlyWheel disables WP core updates. For more information contact FlyWheel support.', 'mainwp' );
1408 - } elseif ( 'pressable' === $by ) {
1409 - return esc_html__( 'Pressable disables WP core updates. For more information contact Pressable support.', 'mainwp' );
1410 - }
1411 - return '';
1412 - }
1475 + $fetched_icon = '';
1476 + if ( '' !== $icon ) {
1477 + $fetched_icon = rawurlencode( $icon );
1478 + }
1413 1479
1480 + $cached_icons = MainWP_DB::instance()->get_general_option( $option_name, 'array' );
1414 1481
1415 - /**
1416 - * Method get_disabled_wpcore_update_host().
1417 - *
1418 - * Get wpcore update disabled for the websites on FlyWheel host or Pressable host.
1419 - *
1420 - * @param mixed $website data.
1421 - */
1422 - public static function get_disabled_wpcore_update_host( $website ) {
1423 - if ( empty( $website ) ) {
1424 - return '';
1425 - }
1426 - $wphost = MainWP_DB::instance()->get_website_option( $website, 'wphost' );
1427 - if ( ! empty( $wphost ) && ( 'flywheel' !== $wphost && 'pressable' !== $wphost ) ) {
1428 - $wphost = '';
1429 - }
1430 - return empty( $wphost ) ? '' : $wphost;
1431 - }
1482 + if ( isset( $cached_icons[ $slug ] ) && '' === $fetched_icon && ! empty( $cached_icons[ $slug ]['path'] ) ) {
1483 + // if fetch icon empty then used caching icon.
1484 + $fetched_icon = $cached_icons[ $slug ]['path'];
1485 + $icon = rawurldecode( $fetched_icon );
1486 + }
1432 1487
1488 + static::update_cached_icons( $fetched_icon, $slug, $type );
1433 1489
1434 - /**
1435 - * Method get_connect_sign_algorithm().
1436 - *
1437 - * Get supported sign algorithms.
1438 - *
1439 - * @param mixed $website The Website object.
1440 - *
1441 - * @return mixed $alg Algorithm connect.
1442 - */
1443 - public static function get_connect_sign_algorithm( $website ) {
1444 - $alg = is_object( $website ) && property_exists( $website, 'signature_algo' ) && ! empty( $website->signature_algo ) ? $website->signature_algo : false;
1490 + if ( '' !== $icon ) {
1491 + return $icon;
1492 + }
1493 + return false;
1494 + }
1445 1495
1446 - // to fix.
1447 - if ( is_numeric( $alg ) ) {
1448 - $alg = intval( $alg );
1449 - }
1496 + /**
1497 + * Method handle_get_icon()
1498 + *
1499 + * @param string $slug Plugin slug.
1500 + * @param string $type Type: theme|plugin.
1501 + */
1502 + public static function handle_get_icon( $slug, $type ) {
1503 + if ( empty( $slug ) ) {
1504 + return false;
1505 + }
1506 + if ( 'plugin' === $type || 'theme' === $type ) {
1507 + return static::fetch_wp_org_icons( $slug, $type );
1508 + }
1509 + return '';
1510 + }
1450 1511
1451 - $default_alg = false;
1452 - if ( defined( 'OPENSSL_ALGO_SHA256' ) ) {
1453 - $default_alg = OPENSSL_ALGO_SHA256;
1454 - }
1455 1512
1456 - if ( ! empty( $alg ) ) {
1457 - if ( 9999 === $alg ) {
1458 - $alg = get_option( 'mainwp_connect_signature_algo', $default_alg );
1459 - // to fix.
1460 - if ( is_numeric( $alg ) ) {
1461 - $alg = intval( $alg );
1462 - }
1463 - }
1464 - }
1513 + /**
1514 + * Gets a plugin icon via API from WordPress.org
1515 + *
1516 + * @param string $slug Plugin slug.
1517 + * @param bool $forced_get Forced get icon, default: false.
1518 + */
1519 + public static function get_plugin_icon( $slug, $forced_get = false ) { // phpcs:ignore -- NOSONAR - complex.
1465 1520
1466 - if ( empty( $alg ) ) {
1467 - $site_info = MainWP_DB::instance()->get_website_option( $website, 'site_info' );
1468 - $site_info = ! empty( $site_info ) ? json_decode( $site_info, true ) : array();
1469 - if ( is_array( $site_info ) && ! empty( $site_info['child_version'] ) ) {
1470 - if ( version_compare( $site_info['child_version'], '4.5', '>=' ) ) {
1471 - $alg = $default_alg;
1472 - }
1473 - }
1474 - }
1521 + $icon = apply_filters( 'mainwp_get_plugin_theme_icon', '', $slug, 'plugin' );
1475 1522
1476 - if ( ! self::is_valid_supported_sign_alg( $alg ) ) {
1477 - $alg = false;
1478 - }
1523 + if ( ! empty( $icon ) ) {
1524 + return $icon;
1525 + }
1479 1526
1480 - $alg = apply_filters( 'mainwp_connect_sign_algo', $alg, $website );
1527 + $forced_get = apply_filters( 'mainwp_forced_get_plugin_theme_icon', $forced_get, $slug, 'plugin' );
1481 1528
1482 - return $alg;
1483 - }
1529 + if ( $forced_get ) {
1530 + $fet_icon = static::fetch_wp_org_icons( $slug, 'plugin' );
1531 + if ( false !== $fet_icon ) {
1532 + $scr = MainWP_Utility::remove_http_prefix( $fet_icon );
1533 + return '<img style="display:inline-block" class="ui mini circular image" updated-icon="true" src="' . esc_attr( $scr ) . '" />';
1534 + }
1535 + return $icon;
1536 + }
1484 1537
1485 - /**
1486 - * Method is_valid_supported_sign_alg()
1487 - *
1488 - * Check if is supported sign algorithms.
1489 - *
1490 - * @param int $alg The Sign Algo value.
1491 - */
1492 - public static function is_valid_supported_sign_alg( $alg ) {
1493 - $valid = false;
1494 - if ( defined( 'OPENSSL_ALGO_SHA1' ) && OPENSSL_ALGO_SHA1 === $alg ) {
1495 - $valid = true;
1496 - } elseif ( defined( 'OPENSSL_ALGO_SHA224' ) && OPENSSL_ALGO_SHA224 === $alg ) {
1497 - $valid = true;
1498 - } elseif ( defined( 'OPENSSL_ALGO_SHA256' ) && OPENSSL_ALGO_SHA256 === $alg ) {
1499 - $valid = true;
1500 - } elseif ( defined( 'OPENSSL_ALGO_SHA384' ) && OPENSSL_ALGO_SHA384 === $alg ) {
1501 - $valid = true;
1502 - } elseif ( defined( 'OPENSSL_ALGO_SHA512' ) && OPENSSL_ALGO_SHA512 === $alg ) {
1503 - $valid = true;
1504 - }
1505 - return $valid;
1506 - }
1538 + // checks expired.
1539 + $cached_icons = MainWP_DB::instance()->get_general_option( 'plugins_icons', 'array' );
1507 1540
1508 - /**
1509 - * Method get_signature_alg()
1510 - *
1511 - * Get custom signature algorithms.
1512 - */
1513 - public static function get_open_ssl_sign_algos() {
1514 - $values = array();
1541 + if ( ! empty( $cached_icons ) ) {
1542 + $lasttime_clear_cached = MainWP_DB::instance()->get_general_option( 'lasttime_clear_cached_plugins_icon' );
1543 + if ( time() > ( intval( $lasttime_clear_cached ) + MONTH_IN_SECONDS ) ) {
1544 + $updated = false;
1545 + $new_cached = array();
1546 + foreach ( $cached_icons as $sl => $val ) {
1547 + if ( empty( $val['noexpire'] ) && empty( $val['path_custom'] ) && time() < ( intval( $val['lasttime_cached'] ) + 12 * MONTH_IN_SECONDS ) ) {
1548 + $new_cached[ $sl ] = $val; // unset.
1549 + $updated = true;
1550 + }
1551 + }
1552 + if ( $updated ) {
1553 + MainWP_DB::instance()->update_general_option( 'plugins_icons', $new_cached, 'array' );
1554 + }
1555 + MainWP_DB::instance()->update_general_option( 'lasttime_clear_cached_plugins_icon', time() );
1556 + }
1557 + }
1515 1558
1516 - if ( defined( 'OPENSSL_ALGO_SHA1' ) ) {
1517 - $values[ OPENSSL_ALGO_SHA1 ] = 'OPENSSL_ALGO_SHA1';
1518 - }
1519 - if ( defined( 'OPENSSL_ALGO_SHA224' ) ) {
1520 - $values[ OPENSSL_ALGO_SHA224 ] = 'OPENSSL_ALGO_SHA224';
1521 - }
1559 + return static::get_plugin_theme_icon( $slug, 'plugin' );
1560 + }
1522 1561
1523 - if ( defined( 'OPENSSL_ALGO_SHA256' ) ) {
1524 - $values[ OPENSSL_ALGO_SHA256 ] = 'OPENSSL_ALGO_SHA256 ' . esc_html__( '(Default)', 'mainwp' );
1525 - }
1562 + /**
1563 + * Gets a theme icon via API from WordPress.org
1564 + *
1565 + * @param string $slug Theme slug.
1566 + * @param bool $forced_get Forced get icon, default: false.
1567 + */
1568 + public static function get_theme_icon( $slug, $forced_get = false ) { // phpcs:ignore -- NOSONAR - complex.
1526 1569
1527 - if ( defined( 'OPENSSL_ALGO_SHA384' ) ) {
1528 - $values[ OPENSSL_ALGO_SHA384 ] = 'OPENSSL_ALGO_SHA384';
1529 - }
1570 + $icon = apply_filters( 'mainwp_get_plugin_theme_icon', '', $slug, 'theme' );
1530 1571
1531 - if ( defined( 'OPENSSL_ALGO_SHA512' ) ) {
1532 - $values[ OPENSSL_ALGO_SHA512 ] = 'OPENSSL_ALGO_SHA512';
1533 - }
1572 + if ( ! empty( $icon ) ) {
1573 + return $icon;
1574 + }
1534 1575
1535 - return $values;
1536 - }
1576 + $forced_get = apply_filters( 'mainwp_forced_get_plugin_theme_icon', $forced_get, $slug, 'theme' );
1537 1577
1538 - /**
1539 - * Method get_default_map_site_fields()
1540 - *
1541 - * Get default map site fields.
1542 - */
1543 - public static function get_default_map_site_fields() {
1544 - $data_fields = array(
1545 - 'id',
1546 - 'url',
1547 - 'name',
1548 - 'adminname',
1549 - 'privkey',
1550 - 'http_user',
1551 - 'http_pass',
1552 - 'ssl_version',
1553 - 'sync_errors',
1554 - 'signature_algo',
1555 - 'verify_method',
1556 - );
1557 - return $data_fields;
1558 - }
1578 + if ( $forced_get ) {
1579 + $fet_icon = static::fetch_wp_org_icons( $slug, 'theme' );
1580 + if ( false !== $fet_icon ) {
1581 + $scr = MainWP_Utility::remove_http_prefix( $fet_icon );
1582 + $icon = '<img style="display:inline-block" class="ui mini circular image" updated-icon="true" src="' . esc_attr( $scr ) . '" />';
1583 + }
1584 + return $icon;
1585 + }
1559 1586
1560 - /**
1561 - * Method get_staging_options_sites_view_for_current_users()
1562 - *
1563 - * Get staging options sites view for current users.
1564 - *
1565 - * @return string Site views.
1566 - */
1567 - public static function get_staging_options_sites_view_for_current_users() {
1568 - $view = apply_filters( 'mainwp_staging_current_user_sites_view', 'undefined' );
1569 - if ( 'undefined' === $view ) { // to compatible.
1570 - $view = get_user_option( 'mainwp_staging_options_updates_view' );
1571 - }
1572 - return $view;
1573 - }
1587 + // checks expired.
1588 + $cached_icons = MainWP_DB::instance()->get_general_option( 'themes_icons', 'array' );
1589 +
1590 + if ( ! empty( $cached_icons ) ) {
1591 + $lasttime_clear_cached = MainWP_DB::instance()->get_general_option( 'lasttime_clear_cached_themes_icon' );
1592 + if ( time() > ( intval( $lasttime_clear_cached ) + MONTH_IN_SECONDS ) ) {
1593 + $updated = false;
1594 + $new_cached = array();
1595 + foreach ( $cached_icons as $sl => $val ) {
1596 + if ( empty( $val['path_custom'] ) && time() < ( intval( $val['lasttime_cached'] ) + 12 * MONTH_IN_SECONDS ) ) {
1597 + $new_cached[ $sl ] = $val;
1598 + $updated = true;
1599 + }
1600 + }
1601 + if ( $updated ) {
1602 + MainWP_DB::instance()->update_general_option( 'themes_icons', $new_cached, 'array' );
1603 + }
1604 + MainWP_DB::instance()->update_general_option( 'lasttime_clear_cached_themes_icon', time() );
1605 + }
1606 + }
1607 +
1608 + return static::get_plugin_theme_icon( $slug, 'theme' );
1609 + }
1610 +
1611 +
1612 + /**
1613 + * Gets a plugin|theme icon to output.
1614 + *
1615 + * @param string $slug Plugin|Theme slug.
1616 + * @param string $type Type icon, plugin|theme.
1617 + */
1618 + private static function get_plugin_theme_icon( $slug, $type ) { // phpcs:ignore -- NOSONAR -Current complexity is the only way to achieve desired results, pull request solutions appreciated.
1619 +
1620 + $icon_placeholder_cls = '';
1621 +
1622 + if ( 'plugin' === $type ) {
1623 + $option_name = 'plugins_icons';
1624 + $icon_placeholder_cls = 'plug circular inverted';
1625 + } elseif ( 'theme' === $type ) {
1626 + $option_name = 'themes_icons';
1627 + $icon_placeholder_cls = 'tint circular inverted';
1628 + } else {
1629 + return '<i style="font-size: 17px" class="plug circular inverted icon" not-cached-path="true"></i>';
1630 + }
1631 +
1632 + $cached_icons = MainWP_DB::instance()->get_general_option( $option_name, 'array' );
1633 +
1634 + if ( ! is_array( $cached_icons ) ) {
1635 + $cached_icons = array();
1636 + }
1637 +
1638 + $cached_days = apply_filters( 'mainwp_plugin_theme_icon_cache_days', 15, $slug, $type ); // default 15 days.
1639 +
1640 + $attr_slug = ' icon-type="' . esc_attr( $type ) . '" item-slug="' . esc_attr( $slug ) . '" ';
1641 + $cls_expired = ' cached-icon-expired ';
1642 + $cls_uploadable = ' cached-icon-customable ';
1643 +
1644 + if ( empty( $slug ) ) {
1645 + $cls_uploadable = '';
1646 + }
1647 +
1648 + $icon = '';
1649 +
1650 + if ( isset( $cached_icons[ $slug ] ) ) {
1651 + $scr = '';
1652 + $is_custom_icon = false;
1653 + if ( ! empty( $cached_icons[ $slug ]['path_custom'] ) ) {
1654 + if ( 'plugin' === $type ) {
1655 + $dirs = static::get_mainwp_dir( 'plugin-icons', true );
1656 + } elseif ( 'theme' === $type ) {
1657 + $dirs = static::get_mainwp_dir( 'theme-icons', true );
1658 + }
1659 + $scr = $dirs[1] . rawurldecode( $cached_icons[ $slug ]['path_custom'] );
1660 + $is_custom_icon = true; // custom icons will not expired.
1661 + } elseif ( ! empty( $cached_icons[ $slug ]['path'] ) ) {
1662 + $scr = rawurldecode( $cached_icons[ $slug ]['path'] );
1663 + $scr = MainWP_Utility::remove_http_prefix( $scr );
1664 + }
1665 +
1666 + $set_cached_expired = apply_filters( 'mainwp_cache_icon_expired', false, $slug, 'theme' );
1667 + $set_expired = false;
1668 +
1669 + if ( $set_cached_expired && time() > ( intval( $cached_icons[ $slug ]['lasttime_cached'] ) + 15 * MINUTE_IN_SECONDS ) ) {
1670 + $set_expired = true;
1671 + }
1672 +
1673 + $forced_exprided = 1700238511;
1674 + $lasttime_cached = isset( $cached_icons[ $slug ]['lasttime_cached'] ) ? intval( $cached_icons[ $slug ]['lasttime_cached'] ) : 0;
1675 +
1676 + if ( time() > ( $lasttime_cached + $cached_days * DAY_IN_SECONDS ) || $lasttime_cached < $forced_exprided ) { // expired.
1677 + if ( ! empty( $scr ) ) {
1678 + $icon = '<img style="display:inline-block" class="ui mini circular image ' . ( $is_custom_icon ? $cls_uploadable : $cls_expired ) . '" ' . $attr_slug . 'src="' . esc_attr( $scr ) . '" alt="Icon"/>'; // to update expired icon.
1679 + } else {
1680 + $icon = '<i style="font-size: 17px" class="' . esc_attr( $icon_placeholder_cls ) . ' icon ' . $cls_expired . $cls_uploadable . '" ' . $attr_slug . '></i>'; // to update expired icon.
1681 + }
1682 + } elseif ( ! empty( $scr ) ) {
1683 + $use_cls_expired = $set_expired ? $cls_expired : '';
1684 + $icon = '<img style="display:inline-block" class="ui mini circular image ' . ( $is_custom_icon ? $cls_uploadable : $use_cls_expired ) . '" ' . $attr_slug . ' cached-path-icon="true" src="' . esc_attr( $scr ) . '" alt="Icon"/>';
1685 + } else {
1686 + $icon = '<i style="font-size: 17px" class="' . esc_attr( $icon_placeholder_cls ) . ' icon ' . ( $set_expired ? $cls_expired : '' ) . $cls_uploadable . '" ' . $attr_slug . ' cached-path-icon="true"></i>';
1687 + }
1688 + } elseif ( empty( $icon ) ) {
1689 + $icon = '<i style="font-size: 17px" class="' . esc_attr( $icon_placeholder_cls ) . ' icon ' . $cls_expired . $cls_uploadable . '" ' . $attr_slug . ' not-cached-path="true"></i>'; // not upload when not existed in the cached.
1690 + }
1691 + return $icon;
1692 + }
1693 +
1694 +
1695 + /**
1696 + * Method handle_upload_image().
1697 + *
1698 + * Handle upload icons.
1699 + *
1700 + * @param string $sub_folder The sub folder.
1701 + * @param mixed $file_uploader The file uploader.
1702 + * @param mixed $file_index The index of file uploader.
1703 + * @param bool $file_subindex Is file with sub index.
1704 + * @param int $max_width max image width.
1705 + * @param int $max_height max image height.
1706 + */
1707 + public static function handle_upload_image( $sub_folder, $file_uploader, $file_index = 0, $file_subindex = false, $max_width = 300, $max_height = 300 ) { // phpcs:ignore -- NOSONAR - complex function. Current complexity is the only way to achieve desired results, pull request solutions appreciated.
1708 +
1709 + $dirs = static::get_mainwp_dir( $sub_folder, true );
1710 + $base_dir = $dirs[0];
1711 + $base_url = $dirs[1];
1712 +
1713 + /**
1714 + * WordPress files system object.
1715 + *
1716 + * @global object
1717 + */
1718 + global $wp_filesystem;
1719 +
1720 + $output = array();
1721 + $filename = '';
1722 + $filepath = '';
1723 +
1724 + $file_types = array(
1725 + 'image/jpeg',
1726 + 'image/jpg',
1727 + 'image/gif',
1728 + 'image/x-icon',
1729 + 'image/png',
1730 + );
1731 +
1732 + $file_exts = array(
1733 + 'jpeg',
1734 + 'jpg',
1735 + 'gif',
1736 + 'ico',
1737 + 'png',
1738 + );
1739 +
1740 + $upload_ok = ( false === $file_subindex ) ? ( UPLOAD_ERR_OK === $file_uploader['error'][ $file_index ] ) : ( UPLOAD_ERR_OK === $file_uploader['error'][ $file_index ][ $file_subindex ] );
1741 +
1742 + if ( $upload_ok ) {
1743 + $tmp_file = ( false === $file_subindex ) ? ( $file_uploader['tmp_name'][ $file_index ] ) : ( $file_uploader['tmp_name'][ $file_index ][ $file_subindex ] );
1744 +
1745 + if ( is_uploaded_file( $tmp_file ) ) {
1746 + if ( false === $file_subindex ) {
1747 + $file_size = $file_uploader['size'][ $file_index ];
1748 + $file_type = $file_uploader['type'][ $file_index ];
1749 + $file_name = $file_uploader['name'][ $file_index ];
1750 + } else {
1751 + $file_size = $file_uploader['size'][ $file_index ][ $file_subindex ];
1752 + $file_type = $file_uploader['type'][ $file_index ][ $file_subindex ];
1753 + $file_name = $file_uploader['name'][ $file_index ][ $file_subindex ];
1754 + }
1755 +
1756 + $file_extension = strtolower( pathinfo( $file_name, PATHINFO_EXTENSION ) );
1757 +
1758 + if ( $file_size > 500 * 1025 ) {
1759 + $output['error'][] = 3;
1760 + } elseif ( ! in_array( $file_type, $file_types ) ) {
1761 + $output['error'][] = 4;
1762 + } elseif ( ! in_array( $file_extension, $file_exts ) ) {
1763 + $output['error'][] = 5;
1764 + } else {
1765 +
1766 + $dest_file = $base_dir . '/' . $file_name;
1767 + $dest_file = dirname( $dest_file ) . '/' . wp_unique_filename( dirname( $dest_file ), basename( $dest_file ) );
1768 +
1769 + if ( $wp_filesystem->put_contents( $dest_file, $wp_filesystem->get_contents( $tmp_file ) ) ) {
1770 + if ( file_exists( $dest_file ) ) {
1771 + list( $width, $height ) = getimagesize( $dest_file );
1772 + }
1773 +
1774 + $resize = false;
1775 + if ( $width > $max_width ) {
1776 + $dst_width = $max_width;
1777 + if ( $height > $max_height ) {
1778 + $dst_height = $max_height;
1779 + } else {
1780 + $dst_height = $height;
1781 + }
1782 + $resize = true;
1783 + } elseif ( $height > $max_height ) {
1784 + $dst_width = $width;
1785 + $dst_height = $max_height;
1786 + $resize = true;
1787 + }
1788 +
1789 + if ( $resize ) {
1790 + $src = $dest_file;
1791 + $cropped_file = wp_crop_image( $src, 0, 0, $width, $height, $dst_width, $dst_height, false );
1792 + if ( ! $cropped_file || is_wp_error( $cropped_file ) ) {
1793 + $output['error'][] = 9;
1794 + } else {
1795 + wp_delete_file( $dest_file );
1796 + $filename = basename( $cropped_file );
1797 + $filepath = $cropped_file;
1798 + }
1799 + } else {
1800 + $filename = basename( $dest_file );
1801 + $filepath = $dest_file;
1802 + }
1803 + } else {
1804 + $output['error'][] = 6;
1805 + }
1806 + }
1807 + }
1808 + }
1809 + $output['fileurl'] = ! empty( $filename ) ? $base_url . '/' . $filename : '';
1810 + $output['filepath'] = ! empty( $filepath ) ? $filepath : '';
1811 + $output['filename'] = ! empty( $filename ) ? $filename : '';
1812 +
1813 + return $output;
1814 + }
1815 +
1816 + /**
1817 + * Method disabled_wpcore_update_by().
1818 + *
1819 + * Get disabled wpcore update by.
1820 + *
1821 + * @param string $website The website.
1822 + */
1823 + public static function disabled_wpcore_update_by( $website ) {
1824 + $by = static::get_disabled_wpcore_update_host( $website );
1825 + if ( 'flywheel' === $by ) {
1826 + return esc_html__( 'FlyWheel disables WP core updates. For more information contact FlyWheel support.', 'mainwp' );
1827 + } elseif ( 'pressable' === $by ) {
1828 + return esc_html__( 'Pressable disables WP core updates. For more information contact Pressable support.', 'mainwp' );
1829 + }
1830 + return '';
1831 + }
1832 +
1833 +
1834 + /**
1835 + * Method get_disabled_wpcore_update_host().
1836 + *
1837 + * Get wpcore update disabled for the websites on FlyWheel host or Pressable host.
1838 + *
1839 + * @param mixed $website data.
1840 + */
1841 + public static function get_disabled_wpcore_update_host( $website ) {
1842 + if ( empty( $website ) ) {
1843 + return '';
1844 + }
1845 + $wphost = MainWP_DB::instance()->get_website_option( $website, 'wphost' );
1846 + if ( ! empty( $wphost ) && ( 'flywheel' !== $wphost && 'pressable' !== $wphost ) ) {
1847 + $wphost = '';
1848 + }
1849 + return empty( $wphost ) ? '' : $wphost;
1850 + }
1851 +
1852 +
1853 + /**
1854 + * Method get_connect_sign_algorithm().
1855 + *
1856 + * Get supported sign algorithms.
1857 + *
1858 + * @param mixed $website The Website object.
1859 + *
1860 + * @return mixed $alg Algorithm connect.
1861 + */
1862 + public static function get_connect_sign_algorithm( $website ) { // phpcs:ignore -- NOSONAR - complex.
1863 + $alg = is_object( $website ) && property_exists( $website, 'signature_algo' ) && ! empty( $website->signature_algo ) ? $website->signature_algo : false;
1864 +
1865 + // to fix.
1866 + if ( is_numeric( $alg ) ) {
1867 + $alg = intval( $alg );
1868 + }
1869 +
1870 + $default_alg = false;
1871 + if ( defined( 'OPENSSL_ALGO_SHA256' ) ) {
1872 + $default_alg = OPENSSL_ALGO_SHA256;
1873 + }
1874 +
1875 + if ( ! empty( $alg ) && 9999 === $alg ) {
1876 + $alg = get_option( 'mainwp_connect_signature_algo', $default_alg );
1877 + // to fix.
1878 + if ( is_numeric( $alg ) ) {
1879 + $alg = intval( $alg );
1880 + }
1881 + }
1882 +
1883 + if ( empty( $alg ) ) {
1884 + $site_info = MainWP_DB::instance()->get_website_option( $website, 'site_info' );
1885 + $site_info = ! empty( $site_info ) ? json_decode( $site_info, true ) : array();
1886 + if ( is_array( $site_info ) && ! empty( $site_info['child_version'] ) && version_compare( $site_info['child_version'], '4.5', '>=' ) ) {
1887 + $alg = $default_alg;
1888 + }
1889 + }
1890 +
1891 + if ( ! static::is_valid_supported_sign_alg( $alg ) ) {
1892 + $alg = false;
1893 + }
1894 +
1895 + $alg = apply_filters( 'mainwp_connect_sign_algo', $alg, $website );
1896 +
1897 + return $alg;
1898 + }
1899 +
1900 + /**
1901 + * Method is_valid_supported_sign_alg()
1902 + *
1903 + * Check if is supported sign algorithms.
1904 + *
1905 + * @param int $alg The Sign Algo value.
1906 + */
1907 + public static function is_valid_supported_sign_alg( $alg ) {
1908 + $valid = false;
1909 + if ( ( defined( 'OPENSSL_ALGO_SHA1' ) && OPENSSL_ALGO_SHA1 === $alg ) || ( defined( 'OPENSSL_ALGO_SHA224' ) && OPENSSL_ALGO_SHA224 === $alg ) || ( defined( 'OPENSSL_ALGO_SHA256' ) && OPENSSL_ALGO_SHA256 === $alg ) || ( defined( 'OPENSSL_ALGO_SHA384' ) && OPENSSL_ALGO_SHA384 === $alg ) || ( defined( 'OPENSSL_ALGO_SHA512' ) && OPENSSL_ALGO_SHA512 === $alg ) ) {
1910 + $valid = true;
1911 + }
1912 + return $valid;
1913 + }
1914 +
1915 + /**
1916 + * Method get_signature_alg()
1917 + *
1918 + * Get custom signature algorithms.
1919 + */
1920 + public static function get_open_ssl_sign_algos() {
1921 + $values = array();
1922 +
1923 + if ( defined( 'OPENSSL_ALGO_SHA1' ) ) {
1924 + $values[ OPENSSL_ALGO_SHA1 ] = 'OPENSSL_ALGO_SHA1';
1925 + }
1926 + if ( defined( 'OPENSSL_ALGO_SHA224' ) ) {
1927 + $values[ OPENSSL_ALGO_SHA224 ] = 'OPENSSL_ALGO_SHA224';
1928 + }
1929 +
1930 + if ( defined( 'OPENSSL_ALGO_SHA256' ) ) {
1931 + $values[ OPENSSL_ALGO_SHA256 ] = 'OPENSSL_ALGO_SHA256 ' . esc_html__( '(Default)', 'mainwp' );
1932 + }
1933 +
1934 + if ( defined( 'OPENSSL_ALGO_SHA384' ) ) {
1935 + $values[ OPENSSL_ALGO_SHA384 ] = 'OPENSSL_ALGO_SHA384';
1936 + }
1937 +
1938 + if ( defined( 'OPENSSL_ALGO_SHA512' ) ) {
1939 + $values[ OPENSSL_ALGO_SHA512 ] = 'OPENSSL_ALGO_SHA512';
1940 + }
1941 +
1942 + return $values;
1943 + }
1944 +
1945 + /**
1946 + * Method get_default_map_site_fields()
1947 + *
1948 + * Get default map site fields.
1949 + */
1950 + public static function get_default_map_site_fields() {
1951 + return array(
1952 + 'id',
1953 + 'url',
1954 + 'name',
1955 + 'adminname',
1956 + 'privkey',
1957 + 'http_user',
1958 + 'http_pass',
1959 + 'ssl_version',
1960 + 'sync_errors',
1961 + 'signature_algo',
1962 + 'verify_method',
1963 + 'suspended',
1964 + );
1965 + }
1966 +
1967 + /**
1968 + * Method get_select_staging_view_sites()
1969 + *
1970 + * Get staging options sites view for current users.
1971 + *
1972 + * @return string Site views.
1973 + */
1974 + public static function get_select_staging_view_sites() {
1975 +
1976 + $view = get_user_option( 'mainwp_staging_options_updates_view' );
1977 +
1978 + if ( empty( $view ) ) {
1979 + $view = 'livesites';
1980 + }
1981 +
1982 + return apply_filters( 'mainwp_staging_current_user_sites_view', $view );
1983 + }
1984 +
1985 + /**
1986 + * Method get_custom_nonce().
1987 + *
1988 + * @param string $type Type.
1989 + * @param string $slug Slug to create nonce.
1990 + * @return string
1991 + */
1992 + public static function get_custom_nonce( $type, $slug ) {
1993 + global $current_user;
1994 + if ( empty( $current_user ) || empty( $current_user->ID ) ) {
1995 + return '';
1996 + }
1997 + return wp_create_nonce( $type . '-' . $current_user->ID . '-' . $slug );
1998 + }
1999 +
2000 + /**
2001 + * Method is_valid_custom_nonce().
2002 + *
2003 + * @param string $type Type.
2004 + * @param string $slug Slug to create nonce.
2005 + * @param string $nonce Nonce value.
2006 + * @return string
2007 + */
2008 + public static function is_valid_custom_nonce( $type, $slug, $nonce ) {
2009 + global $current_user;
2010 + if ( empty( $current_user ) || empty( $current_user->ID ) ) {
2011 + return false;
2012 + }
2013 + return wp_verify_nonce( $nonce, $type . '-' . $current_user->ID . '-' . $slug );
2014 + }
2015 +
2016 +
2017 + /**
2018 + * Method get_http_version_const_str().
2019 + *
2020 + * @param int $http_ver_int Version value.
2021 + *
2022 + * @return string Version const string.
2023 + */
2024 + public static function get_http_version_const_str( $http_ver_int ) {
2025 + $const_names = array(
2026 + 'CURL_HTTP_VERSION_1_0',
2027 + 'CURL_HTTP_VERSION_1_1',
2028 + 'CURL_HTTP_VERSION_2',
2029 + 'CURL_HTTP_VERSION_2TLS',
2030 + 'CURL_HTTP_VERSION_2_0',
2031 + 'CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE',
2032 + 'CURL_HTTP_VERSION_3',
2033 + 'CURL_HTTP_VERSION_3ONLY',
2034 + 'CURL_HTTP_VERSION_NONE',
2035 + );
2036 + foreach ( $const_names as $const ) {
2037 + if ( defined( $const ) && constant( $const ) === $http_ver_int ) {
2038 + return $const;
2039 + }
2040 + }
2041 + return $http_ver_int;
2042 + }
2043 +
2044 +
2045 + /**
2046 + * Method test_self_connect()
2047 + *
2048 + * Server self-connect test.
2049 + *
2050 + * @param string $url Dashboard self-connect URL.
2051 + *
2052 + * @return array|WP_Error
2053 + */
2054 + public static function test_self_connect( $url ) {
2055 +
2056 + $secret = static::get_self_connect_secret();
2057 + $timestamp = time();
2058 +
2059 + if ( empty( $secret ) ) {
2060 + return new \WP_Error(
2061 + 'mainwp_self_connect_secret',
2062 + __( 'Unable to generate self-connect secret.', 'mainwp' )
2063 + );
2064 + }
2065 +
2066 + $message = 'mainwp_self_connect|' . $timestamp;
2067 +
2068 + $signature = hash_hmac(
2069 + 'sha256',
2070 + $message,
2071 + $secret
2072 + );
2073 +
2074 + /**
2075 + * Filter: https_local_ssl_verify
2076 + *
2077 + * Filters whether the server-self check shoul verify SSL Cert.
2078 + *
2079 + * @since Unknown
2080 + */
2081 + $args = array(
2082 + 'blocking' => true,
2083 + 'sslverify' => apply_filters( 'https_local_ssl_verify', true ),
2084 + 'timeout' => 15,
2085 + 'headers' => array(
2086 + 'X-MainWP-Self-Connect-Timestamp' => (string) $timestamp,
2087 + 'X-MainWP-Self-Connect-Signature' => $signature,
2088 + ),
2089 + 'body' => array(
2090 + 'action' => 'mainwp_self_connect',
2091 + 'mainwp_run' => 'self_connect',
2092 + ),
2093 + );
2094 + return wp_remote_post( $url, $args );
2095 + }
2096 +
2097 +
2098 + /**
2099 + * Method handle_self_connect()
2100 + *
2101 + * Server self-connect response.
2102 + *
2103 + * @return void
2104 + */
2105 + public function handle_self_connect() {
2106 +
2107 + if ( ! $this->verify_self_connect_request() ) {
2108 + status_header( 403 );
2109 + exit;
2110 + }
2111 +
2112 + status_header( 200 );
2113 + header( 'Content-Type: text/plain; charset=utf-8' );
2114 +
2115 + echo 'MainWP Self Connect OK';
2116 + exit;
2117 + }
2118 +
2119 +
2120 + /**
2121 + * Method verify_self_connect_request()
2122 + *
2123 + * Server self-connect test.
2124 + *
2125 + * @return bool
2126 + */
2127 + private function verify_self_connect_request() {
2128 +
2129 + $signature = isset( $_SERVER['HTTP_X_MAINWP_SELF_CONNECT_SIGNATURE'] )
2130 + ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_MAINWP_SELF_CONNECT_SIGNATURE'] ) )
2131 + : '';
2132 +
2133 + $timestamp = isset( $_SERVER['HTTP_X_MAINWP_SELF_CONNECT_TIMESTAMP'] )
2134 + ? (int) $_SERVER['HTTP_X_MAINWP_SELF_CONNECT_TIMESTAMP']
2135 + : 0;
2136 +
2137 + if ( 64 !== strlen( $signature ) || empty( $timestamp ) ) {
2138 + return false;
2139 + }
2140 +
2141 + // Prevent replay attacks.
2142 + if ( abs( time() - $timestamp ) > 300 ) {
2143 + return false;
2144 + }
2145 +
2146 + $secret = static::get_self_connect_secret();
2147 +
2148 + if ( empty( $secret ) ) {
2149 + return false;
2150 + }
2151 +
2152 + $message = 'mainwp_self_connect|' . $timestamp;
2153 +
2154 + $expected = hash_hmac(
2155 + 'sha256',
2156 + $message,
2157 + $secret
2158 + );
2159 +
2160 + return hash_equals( $expected, $signature );
2161 + }
2162 +
2163 +
2164 + /**
2165 + * Method get_self_connect_secret()
2166 + *
2167 + * @return string Self connect secret.
2168 + */
2169 + public static function get_self_connect_secret() {
2170 +
2171 + $option_name = 'mainwp_self_connect_secret';
2172 + $secret = get_option( $option_name, '' );
2173 +
2174 + if ( ! is_string( $secret ) || 64 !== strlen( $secret ) ) {
2175 + try {
2176 + $secret = bin2hex( random_bytes( 32 ) );
2177 + } catch ( \Exception $e ) {
2178 + return '';
2179 + }
2180 +
2181 + update_option( $option_name, $secret, false );
2182 + }
2183 +
2184 + return $secret;
2185 + }
1574 2186 }