PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.2.4
Defender Security – Malware Scanner, Login Security & Firewall v6.2.4
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / component / config / class-config-hub-helper.php

class-config-hub-helper.php in Defender Security – Malware Scanner, Login Security & Firewall 6.2.4, at src/component/config/class-config-hub-helper.php

558 lines 15.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles interactions with the WPMU DEV Hub for configurations storage and retrieval.
4 *
5 * @package WP_Defender\Component\Config
6 */
7
8 namespace WP_Defender\Component\Config;
9
10 use WP_Defender\Behavior\WPMUDEV;
11 use WP_Defender\Component\Backup_Settings;
12
13 /**
14 * Handles configuration settings related to the WPMU DEV Hub.
15 */
16 class Config_Hub_Helper {
17
18 public const CONFIGS_TRANSIENT_KEY = 'wpdefender_preset_configs';
19 public const CONFIGS_TRANSIENT_TIME_KEY = 'wpdefender_preset_configs_transient_time';
20 public const ACTIVE_FLAG_CLEAR_KEY = 'wpdefender_config_clear_active_tag';
21 public const CONFIGS_TRANSIENT_TIME = 600; // 600 = 10 minutes.
22 public const DELETED_HUB_IDS_OPTION = 'wpdefender_deleted_hub_config_ids';
23
24 /**
25 * WPMU DEV Plugin ID.
26 */
27 public const WDP_ID = 1081723;
28
29 /**
30 * Service class.
31 *
32 * @var Backup_Settings
33 */
34 private $service;
35
36 /**
37 * Get configs from transient.
38 *
39 * @param Backup_Settings $service Config service.
40 *
41 * @return mixed|array
42 */
43 public static function get_configs( Backup_Settings $service ) {
44 $configs = get_site_transient( self::CONFIGS_TRANSIENT_KEY );
45
46 if ( false === $configs ) {
47 $_this = new self();
48 $_this->service = $service;
49 $configs = $_this->fetch_current_configs();
50
51 set_site_transient( self::CONFIGS_TRANSIENT_KEY, $configs, self::CONFIGS_TRANSIENT_TIME );
52 update_site_option( self::CONFIGS_TRANSIENT_TIME_KEY, time() );
53 }
54
55 return $configs;
56 }
57
58 /**
59 * Get our special WDP ID header line from the file.
60 *
61 * @return array Plugin details: name, id.
62 */
63 private function get_plugin_details(): array {
64 $wpmudev = new WPMUDEV();
65
66 return array(
67 'name' => 'Defender' . ( $wpmudev->is_pro() ? ' Pro' : '' ),
68 'id' => (string) self::WDP_ID,
69 );
70 }
71
72 /**
73 * Fetch and prepare configs to store to transient.
74 *
75 * @return array
76 */
77 private function fetch_current_configs(): array {
78 $stored_configs = $this->service->get_configs();
79 $wpmudev = new WPMUDEV();
80 $def_details = $this->get_plugin_details();
81
82 // Check config has `hub_id`. If not then send request to HUB and set it.
83 $stored_configs = $this->check_and_save_configs_to_hub( $stored_configs, $def_details, $wpmudev );
84
85 if ( defined( WPMUDEV::class . '::API_PACKAGE_CONFIGS' ) ) {
86 // Fetch configs from API.
87 $response = $wpmudev->make_wpmu_request(
88 WPMUDEV::API_PACKAGE_CONFIGS,
89 array( 'package_id' => $def_details['id'] ),
90 array( 'method' => 'GET' )
91 );
92 } else {
93 $response = false;
94 }
95
96 $final_configs = array();
97
98 // Loop to set default config.
99 // Exclude default config from match with HUB config.
100 foreach ( $stored_configs as $sc_key => $sc_value ) {
101 if ( 0 === strpos( $sc_key, 'wp_defender_config_default' ) ) {
102 $final_configs[ $sc_key ] = $sc_value;
103 break;
104 }
105
106 // Sometimes key does not match for default config.
107 if ( isset( $sc_value['immortal'] ) && $sc_value['immortal'] ) {
108 $final_configs[ $sc_key ] = $sc_value;
109 break;
110 }
111 }
112
113 return $this->prepare_hub_configs_response( $response, $final_configs, $stored_configs );
114 }
115
116 /**
117 * Check configs are saved on hub. If not in hub then create it.
118 *
119 * @param array $stored_configs All configs from DB.
120 * @param array $def_details Defender plugin details.
121 * @param WPMUDEV $wpmudev Defender plugin details.
122 *
123 * @return array
124 */
125 private function check_and_save_configs_to_hub( array $stored_configs, array $def_details, $wpmudev ): array {
126 foreach ( $stored_configs as $sc_key => &$sc_value ) {
127 // Check it is for default config.
128 if ( 0 === strpos( $sc_key, 'wp_defender_config_default' ) ) {
129 continue;
130 }
131
132 // Sometimes key does not match for default config.
133 if ( isset( $sc_value['immortal'] ) && $sc_value['immortal'] ) {
134 continue;
135 }
136
137 if ( isset( $sc_value['hub_id'] ) ) {
138 continue;
139 }
140
141 $hub_id = $this->insert_to_hub( $sc_value, $def_details, $wpmudev );
142
143 // If data is not inserted to HUB.
144 if ( ! $hub_id ) {
145 continue;
146 }
147
148 $sc_value['hub_id'] = $hub_id;
149
150 update_site_option( $sc_key, $sc_value );
151 }
152
153 return $stored_configs;
154 }
155
156 /**
157 * Insert new config to hub.
158 *
159 * @param array $sc_value Single config from DB.
160 * @param array $def_details Defender plugin details.
161 * @param WPMUDEV $wpmudev Defender plugin details.
162 *
163 * @return bool|int ID from hub.
164 */
165 private function insert_to_hub( array $sc_value, array $def_details, $wpmudev ) {
166 if ( ! defined( WPMUDEV::class . '::API_PACKAGE_CONFIGS' ) ) {
167 return false;
168 }
169
170 if ( ! isset( $sc_value['configs'] ) || ! is_array( $sc_value['configs'] ) || array() === $sc_value['configs'] ) {
171 return false;
172 }
173
174 if ( ! isset( $sc_value['labels'] ) || ! is_array( $sc_value['labels'] ) || array() === $sc_value['labels'] ) {
175 $config_component = wd_di()->get( Backup_Settings::class );
176 $sc_value['labels'] = $config_component->prepare_config_labels( $sc_value['configs'] );
177 }
178
179 $data = array(
180 'name' => $sc_value['name'],
181 'description' => $sc_value['description'],
182 'package' => $def_details,
183 'config' => wp_json_encode(
184 array(
185 'strings' => $sc_value['strings'],
186 'configs' => $sc_value['configs'],
187 'labels' => $sc_value['labels'],
188 )
189 ),
190 );
191
192 $response = $wpmudev->make_wpmu_request(
193 WPMUDEV::API_PACKAGE_CONFIGS,
194 $data,
195 array( 'method' => 'POST' )
196 );
197
198 if ( is_wp_error( $response ) ) {
199 return false;
200 }
201
202 return $response['id'];
203 }
204
205 /**
206 * Add config to HUB.
207 *
208 * @param array $data Config data.
209 *
210 * @return bool|int
211 */
212 public static function add_configs_to_hub( array $data ) {
213 $_this = new self();
214 $wpmudev = new WPMUDEV();
215 $def_details = $_this->get_plugin_details();
216
217 delete_site_transient( self::CONFIGS_TRANSIENT_KEY );
218
219 return $_this->insert_to_hub( $data, $def_details, $wpmudev );
220 }
221
222 /**
223 * Delete config from HUB.
224 *
225 * @param int $hub_id ID of config from HUB.
226 *
227 * @return bool
228 */
229 public static function delete_configs_from_hub( int $hub_id ): bool {
230 // Always tombstone the hub_id locally, so Hub re-sync can't re-create it.
231 self::add_deleted_hub_id( $hub_id );
232
233 if ( ! defined( WPMUDEV::class . '::API_PACKAGE_CONFIGS' ) ) {
234 return false;
235 }
236
237 $wpmudev = new WPMUDEV();
238 $url = $wpmudev->get_endpoint( WPMUDEV::API_PACKAGE_CONFIGS );
239 $response = self::send_request(
240 $url . '/' . $hub_id,
241 array(),
242 $wpmudev->get_apikey(),
243 'DELETE'
244 );
245 // Emergency exit, for example, if the API key is not valid.
246 if ( ! is_array( $response ) ) {
247 return false;
248 }
249
250 if ( isset( $response['deleted'] ) && $response['deleted'] ) {
251 delete_site_transient( self::CONFIGS_TRANSIENT_KEY );
252 self::remove_deleted_hub_id( $hub_id );
253
254 return true;
255 }
256
257 return false;
258 }
259
260 /**
261 * Add a Hub config ID to the locally-deleted blocklist.
262 *
263 * @param int $hub_id The Hub config ID.
264 */
265 public static function add_deleted_hub_id( int $hub_id ): void {
266 $ids = get_site_option( self::DELETED_HUB_IDS_OPTION, array() );
267 $ids[] = $hub_id;
268 update_site_option( self::DELETED_HUB_IDS_OPTION, array_unique( $ids ) );
269 }
270
271 /**
272 * Remove a Hub config ID from the locally-deleted blocklist.
273 *
274 * @param int $hub_id The Hub config ID.
275 */
276 public static function remove_deleted_hub_id( int $hub_id ): void {
277 $ids = get_site_option( self::DELETED_HUB_IDS_OPTION, array() );
278 $ids = array_values( array_filter( $ids, fn( $id ) => $id !== $hub_id ) );
279 update_site_option( self::DELETED_HUB_IDS_OPTION, $ids );
280 }
281
282 /**
283 * Update config on HUB.
284 *
285 * @param array $config Full config data.
286 *
287 * @return bool
288 */
289 public static function update_on_hub( array $config ): bool {
290 if ( ! isset( $config['hub_id'] ) ) {
291 return false;
292 }
293
294 if ( ! defined( WPMUDEV::class . '::API_PACKAGE_CONFIGS' ) ) {
295 return false;
296 }
297
298 $wpmudev = new WPMUDEV();
299 $url = $wpmudev->get_endpoint( WPMUDEV::API_PACKAGE_CONFIGS );
300
301 $data = array(
302 'name' => $config['name'],
303 'description' => $config['description'],
304 );
305
306 $response = self::send_request(
307 $url . '/' . $config['hub_id'],
308 $data,
309 $wpmudev->get_apikey(),
310 'PUT'
311 );
312 // Emergency exit, for example, if the API key is not valid.
313 if ( ! is_array( $response ) ) {
314 return false;
315 }
316
317 if ( isset( $response['id'] ) && $response['id'] ) {
318 delete_site_transient( self::CONFIGS_TRANSIENT_KEY );
319
320 return true;
321 }
322
323 return false;
324 }
325
326 /**
327 * Sends an API request to the specified URL.
328 *
329 * @param string $url URL to send the request to.
330 * @param array $body Body of the request.
331 * @param mixed $api_key API key for authorization.
332 * @param string $method HTTP method to be used (GET, POST, PUT, DELETE).
333 *
334 * @return bool|array
335 */
336 private static function send_request( string $url, array $body, $api_key, string $method ) {
337 if ( false === $api_key || '' === $api_key ) {
338 return false;
339 }
340
341 $request = wp_remote_request(
342 $url,
343 array(
344 'method' => $method,
345 'headers' => array(
346 'Authorization' => $api_key,
347 ),
348 'body' => $body,
349 )
350 );
351
352 if ( is_wp_error( $request ) ) {
353 return false;
354 }
355
356 return json_decode( $request['body'], true );
357 }
358
359 /**
360 * Delete transient for matching condition.
361 *
362 * @return void
363 */
364 public static function clear_config_transient(): void {
365 delete_site_transient( self::CONFIGS_TRANSIENT_KEY );
366 }
367
368 /**
369 * Prepare configs getting from HUB.
370 *
371 * @param mixed $response Response from the HUB.
372 * @param array $final_configs Final configurations to be prepared.
373 * @param array $stored_configs Stored configurations for comparison.
374 *
375 * @return array
376 */
377 private function prepare_hub_configs_response( $response, array $final_configs, array $stored_configs ): array {
378 if ( ! is_array( $response ) || is_wp_error( $response ) ) {
379 return $stored_configs;
380 }
381
382 $deleted_hub_ids = get_site_option( self::DELETED_HUB_IDS_OPTION, array() );
383
384 // Store id of keys that need to delete. Because they are deleted on HUB.
385 $delete_hub_ids = array();
386 $once_delete_unset = array();
387
388 // Loop through all items found in the API.
389 foreach ( $response as $api_config ) {
390 // Skip configs that were explicitly deleted locally but not yet removed from Hub.
391 if ( in_array( $api_config['id'], $deleted_hub_ids, true ) ) {
392 continue;
393 }
394
395 $found = false;
396 $api_config_array = json_decode( $api_config['config'], true );
397
398 // Find key and value from stored configs.
399 foreach ( $stored_configs as $sc_key => $sc_value ) {
400 if ( isset( $sc_value['hub_id'] ) ) {
401 // Once it is unset from the deleted array don't re-add it.
402 if ( ! isset( $once_delete_unset[ $sc_value['hub_id'] ] ) ) {
403 $delete_hub_ids[ $sc_value['hub_id'] ] = $sc_key;
404 }
405
406 if ( $sc_value['hub_id'] === $api_config['id'] ) {
407 // Sanitize data.
408 $sc_value['name'] = sanitize_text_field( $api_config['name'] );
409 $sc_value['description'] = ! isset( $api_config['description'] ) || ! is_string( $api_config['description'] )
410 ? ''
411 : sanitize_textarea_field( $api_config['description'] );
412 $sc_value['immortal'] = $api_config_array['immortal'] ?? $sc_value['immortal'];
413 $sc_value['is_removable'] = $api_config['is_removable'] ?? true;
414 $final_configs[ $sc_key ] = $sc_value;
415 $found = true;
416
417 unset( $delete_hub_ids[ $sc_value['hub_id'] ] );
418 $once_delete_unset[ $sc_value['hub_id'] ] = $sc_key;
419 break;
420 }
421 }
422 }
423
424 if ( $found ) {
425 continue;
426 }
427
428 // Need to prepare this data because these are coming from HUB.
429 $key = 'wp_defender_config_hub_' . $api_config['id'];
430 $final_configs[ $key ] = $this->format_hub_config_to_save( $api_config );
431 $this->service->index_key( $key );
432 }
433
434 $this->delete_hub_removed_configs( $delete_hub_ids );
435
436 return $final_configs;
437 }
438
439 /**
440 * Delete configs that are delete from HUB.
441 *
442 * @param array $delete_hub_ids Array of configuration IDs to be deleted.
443 *
444 * @return void
445 */
446 private function delete_hub_removed_configs( array $delete_hub_ids ): void {
447 foreach ( $delete_hub_ids as $key ) {
448 delete_site_option( $key );
449 $this->service->remove_index( $key );
450 }
451 }
452
453 /**
454 * Need to prepare this data because these are coming from HUB.
455 *
456 * @param array $api_config The hub config data.
457 *
458 * @return array
459 */
460 private function format_hub_config_to_save( array $api_config ): array {
461 $api_config_array = json_decode( $api_config['config'], true );
462 $api_config_array['hub_id'] = $api_config['id'];
463 // Sanitize data.
464 $api_config_array['name'] = sanitize_text_field( $api_config['name'] );
465 $api_config_array['description'] = ! isset( $api_config['description'] ) || ! is_string( $api_config['description'] )
466 ? ''
467 : sanitize_textarea_field( $api_config['description'] );
468 $api_config_array['immortal'] = $api_config_array['immortal'] ?? false;
469 $api_config_array['is_removable'] = $api_config['is_removable'] ?? true;
470
471 unset( $api_config_array['labels'] );
472
473 return $api_config_array;
474 }
475
476 /**
477 * Delete config cache and create a new cache.
478 *
479 * @param Backup_Settings $service The backup settings service.
480 *
481 * @return mixed|array
482 */
483 public static function get_fresh_frontend_configs( Backup_Settings $service ) {
484 delete_site_transient( self::CONFIGS_TRANSIENT_KEY );
485
486 $configs = self::get_configs( $service );
487
488 foreach ( $configs as $key => &$config ) {
489 $config['key'] = $key;
490 unset( $config['configs'] );
491 }
492
493 return $configs;
494 }
495
496 /**
497 * Set a flag to clear active tag.
498 *
499 * @return void
500 */
501 public static function set_clear_active_flag(): void {
502 $value = get_site_option( self::ACTIVE_FLAG_CLEAR_KEY, 'cleared' );
503
504 if ( 'clear' !== $value ) {
505 update_site_option( self::ACTIVE_FLAG_CLEAR_KEY, 'clear' );
506 }
507 }
508
509 /**
510 * Check active flag need to remove or not.
511 *
512 * @return bool
513 */
514 public static function check_remove_active_flag(): bool {
515 $value = get_site_option( self::ACTIVE_FLAG_CLEAR_KEY, 'cleared' );
516
517 if ( 'clear' === $value ) {
518 update_site_option( self::ACTIVE_FLAG_CLEAR_KEY, 'cleared' );
519
520 return true;
521 }
522
523 return false;
524 }
525
526 /**
527 * Updates the active configuration from the given hub ID.
528 *
529 * @param int $hub_id The hub ID of the configuration to activate.
530 *
531 * @return void
532 */
533 public static function active_config_from_hub_id( int $hub_id ): void {
534 $config_component = wd_di()->get( Backup_Settings::class );
535 $configs = $config_component->get_configs();
536
537 foreach ( $configs as $key => $config ) {
538 $need_update = false;
539 // Remove previous active status.
540 if ( isset( $config['is_active'] ) && $config['is_active'] ) {
541 $config['is_active'] = false;
542 $need_update = true;
543 }
544 // Add current active status.
545 if ( isset( $config['hub_id'] ) && (int) $config['hub_id'] === $hub_id ) {
546 $config['is_active'] = true;
547 $need_update = true;
548 }
549
550 if ( $need_update ) {
551 update_site_option( $key, $config );
552 }
553 }
554 // Clear cache to reflect in frontend.
555 delete_site_transient( self::CONFIGS_TRANSIENT_KEY );
556 }
557 }
558