PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
6.2.3 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 All 156 releases
← All changes | class/class-mainwp-keys-manager.php +720 -429 5.0.1 → 6.2.1 View file →
@@ -12,9 +12,9 @@
12 12 use phpseclib3\Crypt\Random;
13 13
14 14 // Exit if accessed directly.
15 15 if ( ! defined( 'ABSPATH' ) ) {
16 - exit;
16 + exit;
17 17 }
18 18
19 19 /**
20 20 * Class MainWP_Keys_Manager
@@ -20,496 +20,787 @@
20 20 * Class MainWP_Keys_Manager
21 21 *
22 22 * @package MainWP/MainWP_Keys_Manager
23 23 */
24 -class MainWP_Keys_Manager {
24 +class MainWP_Keys_Manager { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
25 25
26 - /**
27 - * Private static variable to hold the single instance of the class.
28 - *
29 - * @static
30 - *
31 - * @var mixed Default null
32 - */
33 - private static $instance = null;
26 + /**
27 + * Private static variable to hold the single instance of the class.
28 + *
29 + * @static
30 + *
31 + * @var mixed Default null
32 + */
33 + private static $instance = null;
34 34
35 - /**
36 - * Method instance()
37 - *
38 - * Create a public static instance.
39 - *
40 - * @static
41 - * @return Instance class.
42 - */
43 - public static function instance() {
44 - if ( null === self::$instance ) {
45 - self::$instance = new self();
46 - }
47 - self::auto_load_files(); // to fix.
48 - return self::$instance;
49 - }
35 + /**
36 + * Method instance()
37 + *
38 + * Create a public static instance.
39 + *
40 + * @static
41 + * @return Instance class.
42 + */
43 + public static function instance() {
44 + if ( null === static::$instance ) {
45 + static::$instance = new self();
46 + }
47 + static::auto_load_files(); // to fix.
48 + return static::$instance;
49 + }
50 50
51 - /**
52 - * Method get_class_name()
53 - *
54 - * Get Class Name.
55 - *
56 - * @return object Class name.
57 - */
58 - public static function get_class_name() {
59 - return __CLASS__;
60 - }
51 + /**
52 + * Method get_class_name()
53 + *
54 + * Get Class Name.
55 + *
56 + * @return object Class name.
57 + */
58 + public static function get_class_name() {
59 + return __CLASS__;
60 + }
61 61
62 - /**
63 - * Method auto_load_files()
64 - *
65 - * Handle autoload files.
66 - */
67 - public static function auto_load_files() {
68 - require_once MAINWP_PLUGIN_DIR . 'libs' . DIRECTORY_SEPARATOR . 'phpseclib' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
69 - }
62 + /**
63 + * Method auto_load_files()
64 + *
65 + * Handle autoload files.
66 + */
67 + public static function auto_load_files() {
68 + require_once MAINWP_PLUGIN_DIR . 'libs' . DIRECTORY_SEPARATOR . 'phpseclib' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; // NOSONAR -- WP compatible.
69 + }
70 70
71 - /**
72 - * Method get_keys_value()
73 - *
74 - * Get decrypt value.
75 - *
76 - * @param string $name Name of key.
77 - * @param mixed $default_value Default value.
78 - *
79 - * @return string Decrypt value.
80 - */
81 - public function get_keys_value( $name, $default_value = false ) {
82 - $opt = get_option( $name );
83 - if ( ! empty( $opt ) && is_array( $opt ) && ! empty( $opt['file_key'] ) ) {
84 - return $this->decrypt_keys_data( $opt, $default_value );
85 - }
86 - return $default_value;
87 - }
71 + /**
72 + * Method get_keys_value()
73 + *
74 + * Get decrypt value.
75 + *
76 + * @param string $name Name of key.
77 + * @param mixed $default_value Default value.
78 + *
79 + * @return string Decrypt value.
80 + */
81 + public function get_keys_value( $name, $default_value = false ) {
82 + $opt = get_option( $name );
83 + if ( ! empty( $opt ) && is_array( $opt ) && ! empty( $opt['file_key'] ) ) {
84 + return $this->decrypt_keys_data( $opt, $default_value );
85 + }
86 + return $default_value;
87 + }
88 88
89 - /**
90 - * Method update_key_value()
91 - *
92 - * Get decrypt value.
93 - *
94 - * @param mixed $option_name option name.
95 - * @param mixed $value The option value.
96 - * @param mixed $prefix The prefix value.
97 - *
98 - * @return string Decrypt value.
99 - */
100 - public function update_key_value( $option_name, $value = false, $prefix = 'dash_' ) {
101 - self::init_keys_dir();
89 + /**
90 + * Method update_key_value()
91 + *
92 + * Get decrypt value.
93 + *
94 + * @param mixed $option_name option name.
95 + * @param mixed $value The option value.
96 + * @param mixed $prefix The prefix value.
97 + *
98 + * @return string Decrypt value.
99 + */
100 + public function update_key_value( $option_name, $value = false, $prefix = 'dash_' ) {
101 + static::init_keys_dir();
102 102
103 - if ( false === $value || '' === $value ) {
104 - $opt = get_option( $option_name );
105 - if ( ! empty( $opt ) && is_array( $opt ) && ! empty( $opt['file_key'] ) ) {
106 - $this->delete_key_file( $opt['file_key'] );
107 - }
108 - return delete_option( $option_name );
109 - }
103 + if ( false === $value || '' === $value ) {
104 + $opt = get_option( $option_name );
105 + if ( ! empty( $opt ) && is_array( $opt ) && ! empty( $opt['file_key'] ) ) {
106 + $this->delete_key_file( $opt['file_key'] );
107 + }
108 + return delete_option( $option_name );
109 + }
110 110
111 - try {
112 - $result = $this->encrypt_value( $value, $option_name, $prefix );
113 - } catch ( \Exception $ex ) {
114 - $err = $ex->getMessage();
115 - if ( is_string( $err ) ) {
116 - MainWP_Logger::instance()->debug( 'encrypt :: name[' . $option_name . '] :: error[' . $err . ']' );
117 - }
118 - return false;
119 - }
111 + try {
112 + $result = $this->encrypt_value( $value, $option_name, $prefix );
113 + } catch ( \Exception $ex ) {
114 + $err = $ex->getMessage();
115 + if ( is_string( $err ) ) {
116 + MainWP_Logger::instance()->debug( 'encrypt :: name[' . $option_name . '] :: error[' . $err . ']' );
117 + }
118 + return false;
119 + }
120 120
121 - if ( is_array( $result ) && ! empty( $result['encrypted_value'] ) ) {
122 - $key = $result['key'];
123 - $file = $result['file_key'];
124 - $pw = $result['encrypted_value'];
125 - if ( $this->save_key_file( $file, $key ) ) {
126 - $update = array(
127 - 'encrypted_val' => $pw,
128 - 'file_key' => $file,
129 - );
130 - update_option( $option_name, $update );
131 - return true;
132 - }
133 - }
134 - return false;
135 - }
121 + if ( is_array( $result ) && ! empty( $result['encrypted_value'] ) ) {
122 + $key = $result['key'];
123 + $file = $result['file_key'];
124 + $pw = $result['encrypted_value'];
125 + if ( $this->save_key_file( $file, $key ) ) {
126 + $update = array(
127 + 'encrypted_val' => $pw,
128 + 'file_key' => $file,
129 + );
130 + update_option( $option_name, $update );
131 + return true;
132 + }
133 + }
134 + return false;
135 + }
136 136
137 - /**
138 - * Method delete_key_file()
139 - *
140 - * Delete key file.
141 - *
142 - * @param string $file_key Name of key file.
143 - *
144 - * @return string Deleted.
145 - */
146 - public function delete_key_file( $file_key ) {
147 - $key_dir = self::get_keys_dir();
148 - $file_path = $key_dir . $file_key;
149 - MainWP_Utility::delete_file( $file_path ); // delete file content key.
150 - return true;
151 - }
137 + /**
138 + * Method delete_key_file()
139 + *
140 + * Delete key file.
141 + *
142 + * @param string $file_key Name of key file.
143 + *
144 + * @return string Deleted.
145 + */
146 + public function delete_key_file( $file_key ) {
147 + $key_dir = static::get_keys_dir();
148 + $file_path = $key_dir . $file_key;
149 + MainWP_Utility::delete_file( $file_path ); // delete file content key.
150 + return true;
151 + }
152 152
153 - /**
154 - * Method get_decrypt_values()
155 - *
156 - * Get decrypt value.
157 - *
158 - * @param mixed $encodedValue Encoded The value to decrypt.
159 - * @param mixed $key_file The value key.
160 - * @param mixed $default_value The default value.
161 - *
162 - * @return string Decrypt value.
163 - */
164 - private function get_decrypt_values( $encodedValue, $key_file, $default_value = '' ) {
165 - // find the key file, and get saved key.
166 - $key = $this->get_key_val( $key_file );
167 - if ( ! empty( $key ) ) {
168 - return $this->decrypt_value( $encodedValue, $key );
169 - }
170 - return $default_value;
171 - }
153 + /**
154 + * Delete one canonical private key file with exact absence readback.
155 + *
156 + * @param string $file_key Name of key file.
157 + *
158 + * @return bool Whether exact absence was proved.
159 + */
160 + public function delete_key_file_with_result( $file_key ) {
161 + if ( ! is_string( $file_key ) || 1 !== preg_match( '/^[A-Za-z0-9][A-Za-z0-9_-]{0,254}$/D', $file_key ) ) {
162 + return false;
163 + }
172 164
173 - /**
174 - * Method encrypt_value()
175 - *
176 - * Handle encrypt value.
177 - *
178 - * @param mixed $keypass The value to encrypt.
179 - * @param string $name Option name of encrypted data.
180 - * @param string $prefix using for prefix key file name.
181 - *
182 - * @return string Encrypted value.
183 - */
184 - private function encrypt_value( $keypass, $name, $prefix ) {
165 + $key_dir = static::get_keys_dir();
166 + if ( ! is_string( $key_dir ) || '' === $key_dir ) {
167 + return false;
168 + }
185 169
186 - if ( '_' !== substr( $prefix, -1 ) ) {
187 - $prefix .= '_';
188 - }
170 + $file_path = trailingslashit( $key_dir ) . $file_key;
171 + if ( ! $this->key_entry_present( $file_path ) ) {
172 + return true;
173 + }
189 174
190 - $opt = get_option( $name );
175 + MainWP_Utility::delete_file( $file_path );
176 + clearstatcache( true, $file_path );
191 177
192 - if ( ! empty( $opt ) && is_array( $opt ) && ! empty( $opt['file_key'] ) ) {
193 - $file_name = $opt['file_key'];
194 - } else {
195 - $file_name = $prefix . sha1( sha1( $prefix . $name . time() ) . 'key_files' );
196 - }
178 + // A dangling symlink fails every exists() check, so the transport-aware helper skips it and leaves the entry behind.
179 + if ( is_link( $file_path ) ) {
180 + wp_delete_file( $file_path );
181 + clearstatcache( true, $file_path );
182 + }
197 183
198 - MainWP_Logger::instance()->debug( 'encrypt :: option name[' . $name . '] :: K file[' . $file_name . ']' );
184 + return ! $this->key_entry_present( $file_path );
185 + }
199 186
200 - $key = Random::string( 32 ); // supported key length: 16, 24, 32.
187 + /**
188 + * Whether any directory entry, file or symlink, still occupies the key path.
189 + *
190 + * @param string $file_path Absolute key path.
191 + *
192 + * @return bool Whether an entry is present.
193 + */
194 + private function key_entry_present( $file_path ) {
195 + return $this->key_file_exists( $file_path ) || is_link( $file_path );
196 + }
201 197
202 - $encrypted = $this->encrypt_with_key( $keypass, $key );
198 + /**
199 + * Check one exact private key path through the active filesystem boundary.
200 + *
201 + * @param string $file_path Absolute key path.
202 + *
203 + * @return bool Whether the path exists.
204 + */
205 + private function key_file_exists( $file_path ) {
206 + global $wp_filesystem;
203 207
204 - return array(
205 - 'key' => $key,
206 - 'file_key' => $file_name,
207 - 'encrypted_value' => $encrypted,
208 - );
209 - }
208 + if ( is_object( $wp_filesystem ) && method_exists( $wp_filesystem, 'exists' ) ) {
209 + return (bool) $wp_filesystem->exists( $file_path );
210 + }
210 211
211 - /**
212 - * Method decrypt_value()
213 - *
214 - * Handle decrypt value.
215 - *
216 - * @param mixed $encodedValue The value to decrypt.
217 - * @param mixed $key Key to decrypt.
218 - *
219 - * @return string Decrypt value.
220 - */
221 - private function decrypt_value( $encodedValue, $key ) {
222 - return $this->decrypt_with_key( $encodedValue, $key );
223 - }
212 + return file_exists( $file_path );
213 + }
224 214
225 - /**
226 - * Method save_key_file()
227 - *
228 - * Handle save key passwd.
229 - *
230 - * @param mixed $key_file The value key.
231 - * @param mixed $key_val The value.
232 - *
233 - * @return mixed Result.
234 - */
235 - private function save_key_file( $key_file, $key_val ) {
236 - self::init_keys_dir();
237 - $key_dir = self::get_keys_dir();
238 - $file_path = $key_dir . $key_file;
239 - $saved = file_put_contents( $file_path, $key_val ); //phpcs:ignore
240 - return false === $saved ? false : true;
241 - }
215 + /**
216 + * Method get_decrypt_values()
217 + *
218 + * Get decrypt value.
219 + *
220 + * @param mixed $encodedValue Encoded The value to decrypt.
221 + * @param mixed $key_file The value key.
222 + * @param mixed $default_value The default value.
223 + *
224 + * @return string Decrypt value.
225 + */
226 + private function get_decrypt_values( $encodedValue, $key_file, $default_value = '' ) {
227 + // find the key file, and get saved key.
228 + $key = $this->get_key_val( $key_file );
229 + if ( ! empty( $key ) ) {
230 + return $this->decrypt_value( $encodedValue, $key );
231 + }
232 + return $default_value;
233 + }
242 234
243 - /**
244 - * Method get_key_val()
245 - *
246 - * Get decrypt value.
247 - *
248 - * @param mixed $key_file The value key.
249 - *
250 - * @return string Decrypt value.
251 - */
252 - public function get_key_val( $key_file ) {
253 - $key_dir = self::get_keys_dir();
254 - $path = $key_dir . $key_file;
255 - if ( file_exists( $path ) ) {
256 - return file_get_contents( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- private key files.
257 - }
258 - return '';
259 - }
235 + /**
236 + * Method encrypt_value()
237 + *
238 + * Handle encrypt value.
239 + *
240 + * @param mixed $keypass The value to encrypt.
241 + * @param string $name Option name of encrypted data.
242 + * @param string $prefix using for prefix key file name.
243 + *
244 + * @return string Encrypted value.
245 + */
246 + private function encrypt_value( $keypass, $name, $prefix ) {
260 247
248 + if ( '_' !== substr( $prefix, -1 ) ) {
249 + $prefix .= '_';
250 + }
261 251
262 - /**
263 - * Method encrypt_with_key()
264 - *
265 - * Handle encrypt value.
266 - *
267 - * @param mixed $keypass The value to encrypt.
268 - * @param mixed $key Key to encrypt.
269 - *
270 - * @return string Encrypted value.
271 - */
272 - private function encrypt_with_key( $keypass, $key ) {
252 + $opt = get_option( $name );
273 253
274 - // Generate a random IV (Initialization Vector).
275 - $iv = Random::string( 16 );
254 + if ( ! empty( $opt ) && is_array( $opt ) && ! empty( $opt['file_key'] ) ) {
255 + $file_name = $opt['file_key'];
256 + } else {
257 + $file_name = $prefix . sha1( sha1( $prefix . $name . time() ) . 'key_files' ); // NOSONAR - safe for salt file name.
258 + }
276 259
277 - // Create AES instance.
278 - $aes = new AES( 'gcm' ); // MODE_GCM.
279 - $aes->setKey( $key );
260 + MainWP_Logger::instance()->debug( 'encrypt :: option name[' . $name . '] :: K file[' . $file_name . ']' );
280 261
281 - $aes->setNonce( $iv ); // Nonces are only used in GCM mode.
282 - $aes->setAAD( 'authentication_data' );
262 + $key = Random::string( 32 ); // supported key length: 16, 24, 32.
283 263
284 - // Encrypt the value.
285 - $ciphertext = $aes->encrypt( $keypass );
264 + $encrypted = $this->encrypt_with_key( $keypass, $key );
286 265
287 - // Get the authentication tag.
288 - $tag = $aes->getTag();
266 + return array(
267 + 'key' => $key,
268 + 'file_key' => $file_name,
269 + 'encrypted_value' => $encrypted,
270 + );
271 + }
289 272
290 - // Combine IV, ciphertext, and tag.
291 - $encryptedValue = $iv . $ciphertext . $tag;
273 + /**
274 + * Method decrypt_value()
275 + *
276 + * Handle decrypt value.
277 + *
278 + * @param mixed $encodedValue The value to decrypt.
279 + * @param mixed $key Key to decrypt.
280 + *
281 + * @return string Decrypt value.
282 + */
283 + private function decrypt_value( $encodedValue, $key ) {
284 + return $this->decrypt_with_key( $encodedValue, $key );
285 + }
292 286
293 - // Encode the encrypted value using base64 for storage.
294 - $encodedValue = base64_encode( $encryptedValue ); //phpcs:ignore
287 + /**
288 + * Method save_key_file()
289 + *
290 + * Handle save key passwd.
291 + *
292 + * @param mixed $key_file The value key.
293 + * @param mixed $key_val The value.
294 + *
295 + * @return mixed Result.
296 + */
297 + public function save_key_file( $key_file, $key_val ) {
298 + static::init_keys_dir();
299 + $key_dir = static::get_keys_dir();
300 + $file_path = $key_dir . $key_file;
301 + $saved = file_put_contents( $file_path, $key_val ); //phpcs:ignore
302 + return false === $saved ? false : true;
303 + }
295 304
296 - return $encodedValue;
297 - }
305 + /**
306 + * Method get_key_val()
307 + *
308 + * Get decrypt value.
309 + *
310 + * @param mixed $key_file The value key.
311 + *
312 + * @return string Decrypt value.
313 + */
314 + public function get_key_val( $key_file ) {
315 + $key_dir = static::get_keys_dir();
316 + $path = $key_dir . $key_file;
317 + if ( file_exists( $path ) ) {
318 + return file_get_contents( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- private key files.
319 + }
320 + return '';
321 + }
298 322
299 - /**
300 - * Method decrypt_with_key()
301 - *
302 - * Handle decrypt value.
303 - *
304 - * @param mixed $encodedValue The string to decrypt.
305 - * @param mixed $key Key to decrypt.
306 - *
307 - * @return string Decrypt value.
308 - */
309 - private function decrypt_with_key( $encodedValue, $key ) {
310 - if ( empty( $encodedValue ) ) {
311 - return '';
312 - }
313 - try {
314 - // Decode the base64 encoded value.
315 - $encryptedValue = base64_decode( $encodedValue ); //phpcs:ignore
316 323
317 - // Extract the IV, ciphertext, and tag.
318 - $iv = substr( $encryptedValue, 0, 16 );
319 - $ciphertext = substr( $encryptedValue, 16, -16 );
320 - $tag = substr( $encryptedValue, -16 );
324 + /**
325 + * Method encrypt_with_key()
326 + *
327 + * Handle encrypt value.
328 + *
329 + * @param mixed $keypass The value to encrypt.
330 + * @param mixed $key Key to encrypt.
331 + *
332 + * @return string Encrypted value.
333 + */
334 + private function encrypt_with_key( $keypass, $key ) {
321 335
322 - // Create AES instance.
323 - $aes = new AES( 'gcm' ); // MODE_GCM.
324 - $aes->setKey( $key );
336 + // Generate a random IV (Initialization Vector).
337 + $iv = Random::string( 16 );
325 338
326 - $aes->setNonce( $iv ); // Nonces are only used in GCM mode.
327 - $aes->setAAD( 'authentication_data' );
339 + // Create AES instance.
340 + $aes = new AES( 'gcm' ); // MODE_GCM.
341 + $aes->setKey( $key );
328 342
329 - // Set the authentication tag.
330 - $aes->setTag( $tag );
343 + $aes->setNonce( $iv ); // Nonces are only used in GCM mode.
344 + $aes->setAAD( 'authentication_data' );
331 345
332 - // Decrypt the value.
333 - $keypass = $aes->decrypt( $ciphertext );
346 + // Encrypt the value.
347 + $ciphertext = $aes->encrypt( $keypass );
334 348
335 - return $keypass;
336 - } catch ( \Exception $ex ) {
337 - // error.
338 - }
339 - return '';
340 - }
349 + // Get the authentication tag.
350 + $tag = $aes->getTag();
341 351
342 - /**
343 - * Method init_keys_dir()
344 - *
345 - * Check for keys directory and create it if it doesn't already exist,
346 - * set the file permissions and update htaccess.
347 - *
348 - * @param mixed $keysDir Keys directory.
349 - *
350 - * @return void
351 - */
352 - public static function init_keys_dir( $keysDir = '' ) {
352 + // Combine IV, ciphertext, and tag.
353 + $encryptedValue = $iv . $ciphertext . $tag;
353 354
354 - if ( '' === $keysDir ) {
355 - $keysDir = self::get_keys_dir();
356 - }
355 + // Encode the encrypted value using base64 for storage.
356 + return base64_encode( $encryptedValue ); //phpcs:ignore
357 + }
357 358
358 - if ( ! is_string( $keysDir ) || stristr( $keysDir, '..' ) ) {
359 - return;
360 - }
359 + /**
360 + * Method decrypt_with_key()
361 + *
362 + * Handle decrypt value.
363 + *
364 + * @param mixed $encodedValue The string to decrypt.
365 + * @param mixed $key Key to decrypt.
366 + *
367 + * @return string Decrypt value.
368 + */
369 + private function decrypt_with_key( $encodedValue, $key ) {
370 + if ( empty( $encodedValue ) ) {
371 + return '';
372 + }
373 + try {
374 + // Decode the base64 encoded value.
375 + $encryptedValue = base64_decode( $encodedValue ); //phpcs:ignore
361 376
362 - $hasWPFileSystem = MainWP_System_Utility::get_wp_file_system();
377 + // Extract the IV, ciphertext, and tag.
378 + $iv = substr( $encryptedValue, 0, 16 );
379 + $ciphertext = substr( $encryptedValue, 16, -16 );
380 + $tag = substr( $encryptedValue, -16 );
363 381
364 - /**
365 - * WordPress files system object.
366 - *
367 - * @global object
368 - */
369 - global $wp_filesystem;
382 + // Create AES instance.
383 + $aes = new AES( 'gcm' ); // MODE_GCM.
384 + $aes->setKey( $key );
370 385
371 - if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
386 + $aes->setNonce( $iv ); // Nonces are only used in GCM mode.
387 + $aes->setAAD( 'authentication_data' );
372 388
373 - if ( ! $wp_filesystem->is_dir( $keysDir ) ) {
374 - $wp_filesystem->mkdir( $keysDir, 0777 );
375 - }
389 + // Set the authentication tag.
390 + $aes->setTag( $tag );
376 391
377 - if ( ! file_exists( $keysDir . '.htaccess' ) ) {
378 - $file_htaccess = $keysDir . '.htaccess';
379 - $wp_filesystem->put_contents( $file_htaccess, 'deny from all' );
380 - }
392 + // Decrypt the value.
393 + return $aes->decrypt( $ciphertext );
394 + } catch ( \Exception $ex ) {
395 + // error.
396 + }
397 + return '';
398 + }
381 399
382 - if ( ! file_exists( $keysDir . 'index.php' ) ) {
383 - $file_index = $keysDir . 'index.php';
384 - $wp_filesystem->touch( $file_index );
385 - }
386 - } else {
400 + /**
401 + * Method init_keys_dir()
402 + *
403 + * Check for keys directory and create it if it doesn't already exist,
404 + * set the file permissions and update htaccess.
405 + *
406 + * @param mixed $keysDir Keys directory.
407 + *
408 + * @return void
409 + */
410 + public static function init_keys_dir( $keysDir = '' ) { //phpcs:ignore -- NOSONAR - complex.
387 411
388 - //phpcs:disable
389 - if ( ! file_exists( $keysDir ) ) {
390 - mkdir( $keysDir, 0777, true );
391 - }
412 + if ( '' === $keysDir ) {
413 + $keysDir = static::get_keys_dir();
414 + }
392 415
393 - if ( ! file_exists( $keysDir . '.htaccess' ) ) {
394 - $file_htaccess = @fopen( $keysDir . '.htaccess', 'w+' );
395 - fwrite( $file_htaccess, 'deny from all' );
396 - fclose( $file_htaccess );
397 - }
416 + if ( ! is_string( $keysDir ) || stristr( $keysDir, '..' ) ) {
417 + return;
418 + }
398 419
399 - if ( ! file_exists( $keysDir . 'index.php' ) ) {
400 - $file_index = @fopen( $keysDir . 'index.php', 'w+' );
401 - fclose( $file_index );
402 - }
403 - // phpcs:enable
404 - }
405 - }
420 + $hasWPFileSystem = MainWP_System_Utility::get_wp_file_system();
406 421
407 - /**
408 - * Method get_keys_dir().
409 - *
410 - * Check for keys directory and create it if it doesn't already exist.
411 - * set the file permissions and update htaccess.
412 - *
413 - * @return string Keys dir.
414 - */
415 - public static function get_keys_dir() {
416 - $dirs = MainWP_System_Utility::get_mainwp_dir();
417 - return $dirs[0] . 'pk' . DIRECTORY_SEPARATOR;
418 - }
422 + /**
423 + * WordPress files system object.
424 + *
425 + * @global object
426 + */
427 + global $wp_filesystem;
419 428
429 + if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
420 430
421 - /**
422 - * Method encrypt_keys_data()
423 - *
424 - * Handle encrypt value.
425 - *
426 - * @param mixed $data The value to encrypt.
427 - * @param string $prefix prefix key file name.
428 - * @param string $key_file key file name.
429 - *
430 - * @return string Encrypted value.
431 - */
432 - public function encrypt_keys_data( $data, $prefix, $key_file = false ) {
431 + if ( ! $wp_filesystem->is_dir( $keysDir ) ) {
432 + // MWP-1557: 0700 preferred (owner only), 0750 fallback for shared-hosting umask edge cases. Mirrors migrate_private_filenames().
433 + if ( ! $wp_filesystem->mkdir( $keysDir, 0700 ) ) {
434 + $wp_filesystem->mkdir( $keysDir, 0750 );
435 + }
436 + }
433 437
434 - if ( empty( $data ) ) {
435 - if ( ! empty( $key_file ) ) {
436 - $this->delete_key_file( $key_file );
437 - }
438 - return $data;
439 - }
438 + if ( ! file_exists( $keysDir . '.htaccess' ) ) {
439 + $file_htaccess = $keysDir . '.htaccess';
440 + $wp_filesystem->put_contents( $file_htaccess, 'deny from all' );
441 + }
440 442
441 - if ( '_' !== substr( $prefix, -1 ) ) {
442 - $prefix .= '_';
443 - }
443 + if ( ! file_exists( $keysDir . 'index.php' ) ) {
444 + $file_index = $keysDir . 'index.php';
445 + $wp_filesystem->touch( $file_index );
446 + }
447 + } else {
444 448
445 - if ( ! function_exists( '\wp_rand' ) ) {
446 - include_once ABSPATH . WPINC . '/pluggable.php';
447 - }
449 + //phpcs:disable
450 + if ( ! file_exists( $keysDir ) ) {
451 + // MWP-1557: 0700 preferred (owner only), 0750 fallback for shared-hosting umask edge cases. Mirrors migrate_private_filenames().
452 + if ( ! mkdir( $keysDir, 0700, true ) ) {
453 + mkdir( $keysDir, 0750, true );
454 + }
455 + }
448 456
449 - if ( ! empty( $key_file ) && is_string( $key_file ) ) {
450 - $file_name = $key_file;
451 - } elseif ( ! empty( $data ) && is_array( $data ) && ! empty( $data['file_key'] ) ) {
452 - $file_name = $data['file_key'];
453 - } else {
454 - $ran = wp_rand( 0, 9990 ); // to fix repeat value.
455 - $file_name = $prefix . sha1( sha1( $prefix . time() . $ran ) . 'key_files' );
456 - }
457 + if ( ! file_exists( $keysDir . '.htaccess' ) ) {
458 + $file_htaccess = @fopen( $keysDir . '.htaccess', 'w+' );
459 + fwrite( $file_htaccess, 'deny from all' );
460 + fclose( $file_htaccess );
461 + }
457 462
458 - MainWP_Logger::instance()->debug( 'encrypt :: K file[' . $file_name . ']' );
463 + if ( ! file_exists( $keysDir . 'index.php' ) ) {
464 + $file_index = @fopen( $keysDir . 'index.php', 'w+' );
465 + fclose( $file_index );
466 + }
467 + // phpcs:enable
468 + }
469 + }
459 470
460 - try {
461 - $key = Random::string( 32 ); // supported key length: 16, 24, 32.
462 - $encrypted = $this->encrypt_with_key( $data, $key );
463 - $result = array(
464 - 'key' => $key,
465 - 'file_key' => $file_name,
466 - 'encrypted_value' => $encrypted,
467 - );
468 - } catch ( \Exception $ex ) {
469 - $err = $ex->getMessage();
470 - if ( is_string( $err ) ) {
471 - MainWP_Logger::instance()->debug( 'encrypt :: error[' . $err . ']' );
472 - }
473 - return false;
474 - }
471 + /**
472 + * Method get_keys_dir().
473 + *
474 + * Check for keys directory and create it if it doesn't already exist.
475 + * set the file permissions and update htaccess.
476 + *
477 + * @return string Keys dir.
478 + */
479 + public static function get_keys_dir() {
480 + $dirs = MainWP_System_Utility::get_mainwp_dir();
481 + return $dirs[0] . 'pk' . DIRECTORY_SEPARATOR;
482 + }
475 483
476 - if ( is_array( $result ) && ! empty( $result['encrypted_value'] ) ) {
477 - $key = $result['key'];
478 - $file = $result['file_key'];
479 - $pw = $result['encrypted_value'];
480 - if ( $this->save_key_file( $file, $key ) ) {
481 - return array(
482 - 'encrypted_val' => $pw,
483 - 'file_key' => $file,
484 - );
485 - }
486 - }
487 - return false;
488 - }
484 + /**
485 + * Method register_migration_hooks()
486 + *
487 + * Register the post-upgrade hook that triggers the one-time pk/ filename
488 + * migration. Called from MainWP_System::activate_this_plugin() before
489 + * MainWP_Install::install() runs, so the action handler is registered when
490 + * `mainwp_db_after_update` fires.
491 + *
492 + * @return void
493 + */
494 + public static function register_migration_hooks() {
495 + add_action( 'mainwp_db_after_update', array( static::class, 'migrate_private_filenames' ), 10, 2 );
496 + add_action( 'mainwp_db_after_update', array( static::class, 'migrate_sibling_dir_perms' ), 10, 2 );
497 + add_action( 'mainwp_db_after_update', array( static::class, 'fix_sibling_dir_perms_9023' ), 10, 2 );
498 + }
489 499
500 + /**
501 + * Method migrate_private_filenames()
502 + *
503 + * One-time bulk migration for MWP-1557: rename legacy pk/ filenames
504 + * (`mainwp_priv_encrypt_keys_<site_id>`) to the opaque HMAC-derived names
505 + * computed by MainWP_System_Utility::get_private_filename(). Also tightens
506 + * directory permissions from the legacy 0777 to 0700 (or 0750 fallback).
507 + *
508 + * Idempotent: only matches the legacy filename pattern, so re-running on
509 + * already-migrated installs is a no-op. Lazy migration in
510 + * MainWP_Encrypt_Data_Lib::get_key_file() handles any files this bulk
511 + * pass might miss.
512 + *
513 + * @param string $from_version Pre-upgrade mainwp_db_version.
514 + * @param string $to_version Post-upgrade mainwp_db_version.
515 + *
516 + * @return void
517 + */
518 + public static function migrate_private_filenames( $from_version, $to_version ) {
519 + unset( $to_version );
520 + if ( ! version_compare( $from_version, '9.0.2.0', '<' ) ) {
521 + return;
522 + }
523 + static::init_keys_dir();
524 + $key_dir = static::get_keys_dir();
525 + if ( ! is_dir( $key_dir ) ) {
526 + return;
527 + }
490 528
491 - /**
492 - * Method decrypt_keys_data()
493 - *
494 - * Get decrypt value.
495 - *
496 - * @param string $encrypted Name of key.
497 - * @param mixed $default_value Default value.
498 - *
499 - * @return string Decrypt value.
500 - */
501 - public function decrypt_keys_data( $encrypted, $default_value = false ) {
502 - if ( is_array( $encrypted ) && ! empty( $encrypted['file_key'] ) && ! empty( $encrypted['encrypted_val'] ) ) {
503 - try {
504 - return $this->get_decrypt_values( $encrypted['encrypted_val'], $encrypted['file_key'], $default_value );
505 - } catch ( \Exception $ex ) {
506 - $err = $ex->getMessage();
507 - if ( is_string( $err ) ) {
508 - MainWP_Logger::instance()->debug( 'decrypt :: error[' . $err . ']' );
509 - }
510 - return false;
511 - }
512 - }
513 - return $default_value;
514 - }
529 + // Tighten directory permissions; 0700 preferred, 0750 if a shared web group needs read access.
530 + if ( ! @chmod( $key_dir, 0700 ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
531 + @chmod( $key_dir, 0750 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
532 + }
533 +
534 + // Rename legacy pk files to opaque HMAC-derived names.
535 + $entries = @scandir( $key_dir ); // phpcs:ignore WordPress.PHP.NoSilencedErrors -- best-effort directory walk.
536 + if ( ! is_array( $entries ) ) {
537 + return;
538 + }
539 + foreach ( $entries as $entry ) {
540 + if ( ! preg_match( '/^mainwp_priv_encrypt_keys_(\d+)$/', $entry, $m ) ) {
541 + continue;
542 + }
543 + $site_id = (int) $m[1];
544 + $new_name = MainWP_System_Utility::get_private_filename( 'pk', $site_id, 'priv_encrypt_keys' );
545 + $old_path = $key_dir . $entry;
546 + $new_path = $key_dir . $new_name;
547 + if ( file_exists( $new_path ) ) {
548 + // New file already present (rare race); legacy is stale, remove it.
549 + wp_delete_file( $old_path );
550 + continue;
551 + }
552 + @rename( $old_path, $new_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors, WordPress.WP.AlternativeFunctions.rename_rename -- best-effort direct rename inside the keys dir, before WP_Filesystem is initialized; lazy migration in get_key_file() handles failures.
553 + }
554 + }
555 +
556 + /**
557 + * Method migrate_sibling_dir_perms()
558 + *
559 + * One-time chmod sweep for installs that created mainwp/ subdirs before
560 + * MWP-1558's mkdir tightening landed in 9.0.2.0. mkdir() does not touch
561 + * the mode of an existing directory, so pre-fix installs keep their
562 + * legacy 0777 even after upgrading. Reported by Daan Kortenbach in the
563 + * MWP-1557/1558 follow-up sweep (MWP-1566).
564 + *
565 + * Targets the known set of subdirs the plugin manages. Idempotent:
566 + * chmodding an already-correct dir is a no-op. @chmod failures are
567 + * swallowed (Windows hosts, shared-hosting suexec mismatches, dirs
568 + * owned by a different system user -- all expected).
569 + *
570 + * @param string $from_version Pre-upgrade mainwp_db_version.
571 + * @param string $to_version Post-upgrade mainwp_db_version.
572 + *
573 + * @return void
574 + */
575 + public static function migrate_sibling_dir_perms( $from_version, $to_version ) {
576 + unset( $to_version );
577 + if ( ! version_compare( $from_version, '9.0.2.1', '<' ) ) {
578 + return;
579 + }
580 + $dirs = MainWP_System_Utility::get_mainwp_dir();
581 + if ( empty( $dirs[0] ) || ! is_dir( $dirs[0] ) ) {
582 + return;
583 + }
584 + $base = rtrim( $dirs[0], '/\\' ) . DIRECTORY_SEPARATOR;
585 +
586 + // mainwp/ root: 0755 (public-asset convention, holds index.php + subdirs).
587 + @chmod( rtrim( $base, '/\\' ), 0755 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
588 +
589 + // Public-asset subdirs. cost-tracker-products-icons is module-specific (constant-gated)
590 + // but uses the same get_mainwp_dir(..., true) public pattern, so legacy installs that ran
591 + // Cost Tracker pre-9.0.2.0 need the same chmod.
592 + foreach ( array( 'icons', 'plugin-icons', 'theme-icons', 'client-images', 'site-icons', 'themes', 'cost-tracker-products-icons' ) as $sub ) {
593 + $p = $base . $sub;
594 + if ( is_dir( $p ) ) {
595 + @chmod( $p, 0755 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
596 + }
597 + }
598 +
599 + // Private (htaccess-protected) subdirs.
600 + foreach ( array( 'cookies', 'templates', 'templates' . DIRECTORY_SEPARATOR . 'emails', 'bulk' ) as $sub ) {
601 + $p = $base . $sub;
602 + if ( is_dir( $p ) ) {
603 + @chmod( $p, 0750 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
604 + }
605 + }
606 +
607 + // Per-user dirs and all descendants: mainwp/<userid>/, /<userid>/bulk/, per-site
608 + // <siteid>/ dirs from get_mainwp_specific_dir($website->id), and any deeper paths
609 + // that backup_download_file() may have materialized via dirname($pFile) on legacy
610 + // installs with custom backup filenames. All private (0750).
611 +
612 + $public_access_dirs = array( 'favorites' );
613 +
614 + $chmod_recursive = function ( $dir ) use ( &$chmod_recursive, $public_access_dirs ) {
615 + @chmod( $dir, 0750 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
616 + $subs = @glob( $dir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort directory walk.
617 + if ( is_array( $subs ) ) {
618 + foreach ( $subs as $s ) {
619 + if ( in_array( basename( $s ), $public_access_dirs, true ) ) {
620 + @chmod( $s, 0755 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
621 + continue;
622 + }
623 + $chmod_recursive( $s );
624 + }
625 + }
626 + };
627 +
628 + $userdirs = @glob( $base . '[0-9]*', GLOB_ONLYDIR ); // phpcs:ignore WordPress.PHP.NoSilencedErrors -- NOSONAR - best-effort directory walk.
629 +
630 + if ( is_array( $userdirs ) ) {
631 + foreach ( $userdirs as $udir ) {
632 + @chmod( $udir, 0751 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
633 + $subs = @glob( $udir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort directory walk.
634 + if ( is_array( $subs ) ) {
635 + foreach ( $subs as $s ) {
636 + if ( in_array( basename( $s ), $public_access_dirs, true ) ) {
637 + @chmod( $s, 0755 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
638 + continue;
639 + }
640 + $chmod_recursive( $s );
641 + }
642 + }
643 + }
644 + }
645 + }
646 +
647 + /**
648 + * Method fix_sibling_dir_perms_9023()
649 + *
650 + * One-time chmod sweep for MWP-1566: fix a small number of installs that ran the MWP-1558/1566
651 + * follow-up sweep (MWP-1566) on.
652 + *
653 + * @param string $from_version Pre-upgrade mainwp_db_version.
654 + * @param string $to_version Post-upgrade mainwp_db_version.
655 + *
656 + * @return void
657 + */
658 + public static function fix_sibling_dir_perms_9023( $from_version, $to_version ) {
659 + unset( $to_version );
660 +
661 + if ( empty( $from_version ) || version_compare( $from_version, '9.0.2.1', '<' ) || version_compare( $from_version, '9.0.2.3', '>=' ) ) {
662 + return;
663 + }
664 +
665 + $dirs = MainWP_System_Utility::get_mainwp_dir();
666 + if ( empty( $dirs[0] ) || ! is_dir( $dirs[0] ) ) {
667 + return;
668 + }
669 + $base = rtrim( $dirs[0], '/\\' ) . DIRECTORY_SEPARATOR;
670 +
671 + // Per-user dirs and all descendants: mainwp/<userid>/, /<userid>/bulk/, per-site
672 + // <siteid>/ dirs from get_mainwp_specific_dir($website->id), and any deeper paths
673 + // that backup_download_file() may have materialized via dirname($pFile) on legacy
674 + // installs with custom backup filenames. All private (0750).
675 +
676 + $public_access_dirs = array( 'favorites' );
677 +
678 + $chmod_recursive = function ( $dir ) use ( &$chmod_recursive, $public_access_dirs ) {
679 + @chmod( $dir, 0750 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
680 + $subs = @glob( $dir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR ); // phpcs:ignore WordPress.PHP.NoSilencedErrors -- NOSONAR - best-effort directory walk.
681 + if ( is_array( $subs ) ) {
682 + foreach ( $subs as $s ) {
683 + if ( in_array( basename( $s ), $public_access_dirs, true ) ) {
684 + @chmod( $s, 0755 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
685 + continue;
686 + }
687 + $chmod_recursive( $s );
688 + }
689 + }
690 + };
691 +
692 + $userdirs = @glob( $base . '[0-9]*', GLOB_ONLYDIR ); // phpcs:ignore WordPress.PHP.NoSilencedErrors -- NOSONAR - best-effort directory walk.
693 +
694 + if ( is_array( $userdirs ) ) {
695 + foreach ( $userdirs as $udir ) {
696 + @chmod( $udir, 0751 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
697 + $subs = @glob( $udir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort directory walk.
698 + if ( is_array( $subs ) ) {
699 + foreach ( $subs as $s ) {
700 + if ( in_array( basename( $s ), $public_access_dirs, true ) ) {
701 + @chmod( $s, 0755 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
702 + continue;
703 + }
704 + $chmod_recursive( $s );
705 + }
706 + }
707 + }
708 + }
709 + }
710 +
711 +
712 + /**
713 + * Method encrypt_keys_data()
714 + *
715 + * Handle encrypt value.
716 + *
717 + * @param mixed $data The value to encrypt.
718 + * @param string $prefix prefix key file name.
719 + * @param string $key_file key file name.
720 + *
721 + * @return string Encrypted value.
722 + */
723 + public function encrypt_keys_data( $data, $prefix, $key_file = false ) { //phpcs:ignore -- NOSONAR - complex.
724 +
725 + if ( empty( $data ) ) {
726 + if ( ! empty( $key_file ) ) {
727 + $this->delete_key_file( $key_file );
728 + }
729 + return $data;
730 + }
731 +
732 + if ( '_' !== substr( $prefix, -1 ) ) {
733 + $prefix .= '_';
734 + }
735 +
736 + if ( ! function_exists( '\wp_rand' ) ) {
737 + include_once ABSPATH . WPINC . '/pluggable.php'; // NOSONAR - WP compatible.
738 + }
739 +
740 + if ( ! empty( $key_file ) && is_string( $key_file ) ) {
741 + $file_name = $key_file;
742 + } elseif ( ! empty( $data ) && is_array( $data ) && ! empty( $data['file_key'] ) ) {
743 + $file_name = $data['file_key'];
744 + } else {
745 + $ran = wp_rand( 0, 9990 ); // to fix repeat value.
746 + $file_name = $prefix . sha1( sha1( $prefix . time() . $ran ) . 'key_files' ); // NOSONAR - safe for salt file name.
747 + }
748 +
749 + MainWP_Logger::instance()->debug( 'encrypt :: K file[' . $file_name . ']' );
750 +
751 + try {
752 + $key = Random::string( 32 ); // supported key length: 16, 24, 32.
753 + $encrypted = $this->encrypt_with_key( $data, $key );
754 + $result = array(
755 + 'key' => $key,
756 + 'file_key' => $file_name,
757 + 'encrypted_value' => $encrypted,
758 + );
759 + } catch ( \Exception $ex ) {
760 + $err = $ex->getMessage();
761 + if ( is_string( $err ) ) {
762 + MainWP_Logger::instance()->debug( 'encrypt :: error[' . $err . ']' );
763 + }
764 + return false;
765 + }
766 +
767 + if ( is_array( $result ) && ! empty( $result['encrypted_value'] ) ) {
768 + $key = $result['key'];
769 + $file = $result['file_key'];
770 + $pw = $result['encrypted_value'];
771 + if ( $this->save_key_file( $file, $key ) ) {
772 + return array(
773 + 'encrypted_val' => $pw,
774 + 'file_key' => $file,
775 + );
776 + }
777 + }
778 + return false;
779 + }
780 +
781 +
782 + /**
783 + * Method decrypt_keys_data()
784 + *
785 + * Get decrypt value.
786 + *
787 + * @param string $encrypted Name of key.
788 + * @param mixed $default_value Default value.
789 + *
790 + * @return string Decrypt value.
791 + */
792 + public function decrypt_keys_data( $encrypted, $default_value = false ) {
793 + if ( is_array( $encrypted ) && ! empty( $encrypted['file_key'] ) && ! empty( $encrypted['encrypted_val'] ) ) {
794 + try {
795 + return $this->get_decrypt_values( $encrypted['encrypted_val'], $encrypted['file_key'], $default_value );
796 + } catch ( \Exception $ex ) {
797 + $err = $ex->getMessage();
798 + if ( is_string( $err ) ) {
799 + MainWP_Logger::instance()->debug( 'decrypt :: error[' . $err . ']' );
800 + }
801 + return false;
802 + }
803 + }
804 + return $default_value;
805 + }
515 806 }