GhostManSec
Server: LiteSpeed
System: Linux premium117.web-hosting.com 4.18.0-553.54.1.lve.el8.x86_64 #1 SMP Wed Jun 4 13:01:13 UTC 2025 x86_64
User: eblama1 (1214)
PHP: 8.2.31
Disabled: NONE
Upload Files
File: //home/eblama1/mintawon.farm/wp-content/plugins/complianz-gdpr/websitescan/class-wsc-settings.php
<?php

defined( 'ABSPATH' ) or die();

if ( ! class_exists( 'cmplz_wsc_settings' ) ) {

	class cmplz_wsc_settings {


		public function init_hooks() {
			add_action( 'cmplz_do_action', array( $this, 'handle_wsc_settings_action' ), 10, 3 );
			add_filter( 'cmplz_menu', array( $this, 'add_website_scan_menu' ) );
			add_filter( 'cmplz_fields', array( $this, 'add_website_scan_fields' ), 80 );
			add_filter( 'cmplz_field', array( $this, 'maybe_disable_batch_scan_field' ), 10, 2 );
			add_action( 'cmplz_before_save_option', array( $this, 'cmplz_before_save_option' ), 100, 4 );
			add_action( 'cmplz_every_day_hook', array( $this, 'check_failed_user_deletion' ) );
			add_action( 'cmplz_store_newsletter_onboarding_consent', array( $this, 'cmplz_store_newsletter_onboarding_consent_handler' ), 10, 2 );
		}

		/**
		 * Lock the background scan checkbox while the Website Scan is not enabled.
		 *
		 * In premium, the field is normally toggleable. When WSC is off (no auth, or
		 * disabled by the admin), toggling the opt-in has no effect — batch_dispatch()
		 * bails out at the wsc_scan_enabled() gate. Disabling the field here and
		 * surfacing a tooltip explains why.
		 *
		 * Free is unaffected (it ships with disabled=true as the upsell showcase).
		 *
		 * @param array  $field    Field definition.
		 * @param string $field_id Field id being filtered.
		 * @return array
		 */
		public function maybe_disable_batch_scan_field( $field, $field_id ) {
			if ( 'wsc_batch_scan_enabled' !== $field_id ) {
				return $field;
			}
			if ( ! defined( 'cmplz_premium' ) ) {
				return $field;
			}
			if ( ! cmplz_wsc_is_enabled() ) {
				$field['disabled'] = true;
				$field['tooltip']  = __( 'Activate the Website Scan first to use background scanning.', 'complianz-gdpr' );
			}
			return $field;
		}


		/**
		 * Handles various onboarding actions.
		 *
		 * This method is responsible for handling different actions related to onboarding.
		 * It checks the user's capability to manage, and based on the action provided,
		 * performs the necessary operations such as sending authentication emails,
		 * resetting website scan, enabling or disabling the website scan, checking token status,
		 * and signing up for the newsletter.
		 *
		 * @param array           $data The data associated with the action.
		 * @param string          $action The action to be performed.
		 * @param WP_REST_Request $request The REST request object.
		 * @return array The updated data after performing the action.
		 */
		public function handle_wsc_settings_action( array $data, string $action, WP_REST_Request $request ): array {
			if ( ! cmplz_user_can_manage() ) {
				return array();
			}
			switch ( $action ) {
				case 'request_activation_email':
					$stored_email = cmplz_get_option( cmplz_wsc::WSC_EMAIL_OPTION_KEY );
					if ( is_email( $stored_email ) ) {
						cmplz_wsc_auth::send_auth_email( $stored_email );
						// set an option with the signup date.
						update_option( 'cmplz_wsc_signup_date', time(), false );
					}
					break;
				// reset Website Scan.
				case 'reset_wsc':
					$reset = $this->cmplz_wsc_reset_websitescan(); // true false
					$data  = array(
						'result'   => $reset,
						'redirect' => cmplz_admin_url( '#settings/settings-cd' ),
					);
					break;
				// just enable the wsc, update the option 'cmplz_wsc_status'.
				case 'enable_wsc':
					$data = $this->handle_wsc_actions( 'enable_wsc' );
					break;
				// just disable the wsc, update the option 'cmplz_wsc_status'.
				case 'disable_wsc':
					$data = $this->handle_wsc_actions( 'disable_wsc' );
					break;
				// check the token status, return 'updated', 'token_status', 'wsc_status', 'wsc_signup_date'.
				case 'get_wsc_status':
					$data = $this->handle_wsc_actions( 'get_wsc_status' );
					break;
				// to be defined.
				case 'signup_newsletter':
					$posted_data = $request->get_json_params();
					$email       = sanitize_email( $posted_data['email'] );
					if ( is_email( $email ) ) {
						cmplz_wsc_auth::newsletter_sign_up( $email );
						// newsletter_consent.
						cmplz_wsc_onboarding::schedule_store_onboarding_consent( 'cmplz_store_newsletter_onboarding_consent', 1200, array( 'newsletter_consent', $posted_data ) );
					}
					break;
			}
			return $data;
		}

		/**
		 * Handles WSC (Website Scan) actions.
		 *
		 * This method handles actions related to enabling or disabling the WSC feature.
		 * It updates the WSC status option based on the provided action.
		 * It also retrieves the token, WSC status, and WSC signup date options.
		 *
		 * @param string $action The action to be performed (enable_wsc or disable_wsc).
		 * @return array An array containing the updated date, token status, WSC status, and WSC signup date.
		 */
		private function handle_wsc_actions( string $action ): array {
			if ( empty( $action ) ) {
				return array();
			}
			if ( $action === 'enable_wsc' ) {
				update_option( 'cmplz_wsc_status', 'enabled', false );
			}
			if ( $action === 'disable_wsc' ) {
				update_option( 'cmplz_wsc_status', 'disabled', false );
			}

			$token_status = $this->get_token_status(); // could be false

			$updated_wsc_status = get_option( 'cmplz_wsc_status' ); // Website Scan status
			$wsc_signup_date    = get_option( 'cmplz_wsc_signup_date' ) ? get_option( 'cmplz_wsc_signup_date' ) : false;

			return apply_filters(
				'cmplz_wsc_actions_response',
				array(
					'token_status'    => $token_status,
					'wsc_status'      => $updated_wsc_status,
					'wsc_signup_date' => $wsc_signup_date,
				)
			);
		}

		/**
		 * Retrieves the token status.
		 *
		 * This method retrieves the token status by checking the value of the 'cmplz_wsc_signup_status' option.
		 * If the option is not set, it retrieves the token using the 'get_token' method and sets the status accordingly.
		 * The possible values for the status are 'pending', 'enabled', and 'disabled'.
		 *
		 * @return string The token status.
		 */
		public static function get_token_status(): string {
			$status          = get_option( 'cmplz_wsc_signup_status', false );
			$token           = cmplz_get_transient( 'cmplz_wsc_access_token' );
			$error_token_api = get_option( 'cmplz_wsc_error_token_api' );

			if ( ! $token ) {
				$token = cmplz_wsc_auth::get_token( true );
			}

			if ( $token ) {
				$status = 'enabled';
			} elseif ( ! $token && $error_token_api ) {
				$status = 'error';
			} else {
				$status = 'disabled';
			}

			if ( $status !== get_option( 'cmplz_wsc_signup_status' ) ) {
				update_option( 'cmplz_wsc_signup_status', $status );
			}

			return $status;
		}


		/**
		 * Add the website scan ux to Settings > APIs
		 * It modifies the existing menu array by appending a new menu item for website scan under the 'settings-cd' menu item.
		 *
		 * @param array $menu The existing menu array.
		 * @return array The modified menu array with the website scan menu item added.
		 */
		public function add_website_scan_menu( array $menu ): array {
			foreach ( $menu as $key => $item ) {
				if ( $item['id'] === 'settings' ) {
					foreach ( $item['menu_items'] as $menu_key => $menu_item ) {
						if ( $menu_item['id'] === 'settings-cd' ) {
							$websiteScanItem                                     = array(
								'id'           => 'settings-websitescan',
								'title'        => __( 'Website Scan', 'complianz-gdpr' ),
								'intro'        => __( 'Here you can manage your credentials. If you don’t want to use the Website Scan, you can reset it. A token will be created to verify your website. After creating your credentials, please make sure to check your email for a confirmation.', 'complianz-gdpr' ),
								'premium_text' => __( 'View and manage Processing Agreements with %sComplianz GDPR Premium%s', 'complianz-gdpr' ),
								'helpLink'     => 'https://complianz.io/about-the-website-scan/',
							);
							$menu[ $key ]['menu_items'][ $menu_key ]['groups'][] = $websiteScanItem;
						}
					}
				}
			}
			return $menu;
		}


		/**
		 * Add Website Scan fields
		 *
		 * This method is used to add website scan fields to an array of fields.
		 *
		 * @param array $fields The array of fields to add the website scan fields to.
		 * @return array The updated array of fields with the website scan fields added.
		 */
		public function add_website_scan_fields( array $fields ): array {
			return array_merge(
				$fields,
				array(
					// wsc fields
					array(
						'id'       => cmplz_wsc::WSC_EMAIL_OPTION_KEY,
						'menu_id'  => 'settings-cd',
						'group_id' => 'settings-websitescan',
						'type'     => 'email',
						'required' => false,
						'default'  => $this->retrieve_default_email_address(),
						'label'    => __( 'E-mail address', 'complianz-gdpr' ),
					),
					array(
						'id'                => cmplz_wsc::WSC_CLIENT_ID_OPTION_KEY,
						'menu_id'           => 'settings-cd',
						'group_id'          => 'settings-websitescan',
						'type'              => 'text',
						'required'          => false,
						'default'           => '',
						'label'             => __( 'Client ID', 'complianz-gdpr' ),
						'server_conditions' => array(
							'relation' => 'AND',
							array(
								'cmplz_wsc_is_enabled()' => true,
							),
						),
					),
					array(
						'id'                => cmplz_wsc::WSC_CLIENT_SECRET_OPTION_KEY,
						'menu_id'           => 'settings-cd',
						'group_id'          => 'settings-websitescan',
						'type'              => 'password',
						'required'          => false,
						'default'           => '',
						'label'             => __( 'Client Secret', 'complianz-gdpr' ),
						'server_conditions' => array(
							'relation' => 'AND',
							array(
								'cmplz_wsc_is_enabled()' => true,
							),
						),
					),
					array(
						'id'       => cmplz_wsc::WSC_SITE_ID_OPTION_KEY,
						'type'     => 'hidden',
						'required' => false,
						'visible'  => false,
						'default'  => '',
					),
					array(
						'id'       => 'websitescan_status',
						'menu_id'  => 'settings-cd',
						'group_id' => 'settings-websitescan',
						'type'     => 'websitescan_status',
						'required' => false,
						'default'  => '',
					),
					array(
						'id'       => 'wsc_batch_scan_enabled',
						'menu_id'  => 'settings-cd',
						'group_id' => 'settings-websitescan',
						'type'     => 'checkbox',
						'default'  => false,
						'disabled' => true,
						'required' => false,
						'label'    => __( 'Background scanning', 'complianz-gdpr' ),
						'tooltip'  => __( 'When enabled, the Website Scan periodically processes all selected post types in the background to detect newly added cookies.', 'complianz-gdpr' ),
						'comment'  => __( 'When enabled, the Website Scan processes one page every few minutes until every selected post type has been covered, detecting new cookies on your site as third-party scripts change over time.', 'complianz-gdpr' ),
						'premium'  => array(
							'disabled' => false,
							'url'      => 'https://complianz.io/pricing-subpages/',
						),
					),
					array(
						'id'       => 'websitescan_actions',
						'menu_id'  => 'settings-cd',
						'group_id' => 'settings-websitescan',
						'type'     => 'websitescan_actions',
						'required' => false,
						'default'  => '',
					),
					array(
						'id'       => 'wsc_scan_post_types',
						'menu_id'  => 'cookie-scan',
						'group_id' => 'cookie-scan',
						'type'     => 'multicheckbox',
						'options'  => $this->scan_post_type_options(),
						// Free baseline: posts and pages only, field locked (upsell showcase).
						'default'  => array( 'post', 'page' ),
						'disabled' => true,
						'required' => false,
						'label'    => __( 'Post types to scan', 'complianz-gdpr' ),
						'tooltip'  => __( 'Pages of the selected post types are included in the Website Scan and show the scan column in the post overview.', 'complianz-gdpr' ),
						'comment'  => __( 'The free scan covers posts and pages. Upgrade to Premium to include custom post types.', 'complianz-gdpr' ),
						'premium'  => array(
							// Premium: all post types freely selectable; custom post types opt-in.
							'disabled' => false,
							'url'      => 'https://complianz.io/pricing-subpages/',
							'comment'  => __( 'Posts and pages are scanned by default. Select the custom post types you want to include.', 'complianz-gdpr' ),
						),
					),
				)
			);
		}

		/**
		 * Build the multicheckbox options for the scannable post types field.
		 *
		 * Delegates to the scan class universe helper so the exclusion list
		 * lives in one place. The scan class is loaded in every context where
		 * fields are built (admin / authenticated REST); the empty fallback
		 * only guards unexpected contexts.
		 *
		 * @return array<string,string> post type slug => plural label.
		 */
		private function scan_post_type_options(): array {
			if ( isset( COMPLIANZ::$scan ) && COMPLIANZ::$scan ) {
				return COMPLIANZ::$scan->get_public_scannable_post_types( true );
			}
			return array();
		}

		/**
		 * Retrieve a default email address for the onboarding dialog
		 *
		 * This method retrieves a default email address for the onboarding dialog.
		 * It first tries to retrieve the email address from the "cmplz wsc email" option.
		 * If the option is empty or not set, it falls back to the WordPress admin email address.
		 *
		 * @return string The default email address for the onboarding dialog.
		 */
		public function retrieve_default_email_address(): string {
			// Retrieve the email address from cmplz wsc email option
			$email_address = cmplz_get_option( cmplz_wsc::WSC_EMAIL_OPTION_KEY );

			// If cmplz wsc email doesn't exists, fallback to admin_email
			if ( empty( $email_address ) || $email_address === '' ) {
				$email_address = get_bloginfo( 'admin_email' );
			}

			// Return the email address if it is valid, otherwise return an empty string
			return filter_var( $email_address, FILTER_VALIDATE_EMAIL ) ? $email_address : '';
		}


		/**
		 * This method is called before saving an option in the plugin.
		 *
		 * It performs various actions based on the field being saved, such as sending
		 * an authentication email if a user change the one associated to the wsc application,
		 * enabling/disabling the newsletter or signing up.
		 *
		 * @param string $field_id The ID of the field being saved.
		 * @param mixed  $field_value The new value of the field being saved.
		 * @param mixed  $prev_value The previous value of the field being saved.
		 * @return void
		 */
		public function cmplz_before_save_option( $field_id, $field_value, $prev_value ): void {
			if ( ! cmplz_user_can_manage() ) {
				return;
			}

			// nothing change
			if ( $field_value === $prev_value ) {
				return;
			}

			switch ( $field_id ) {
				// check if the user changed the email used for the wsc activation
				case cmplz_wsc::WSC_EMAIL_OPTION_KEY:
					// prevent orphan site deleting the application
					$this->cmplz_wsc_reset_websitescan();
					// create a new application
					$email = sanitize_email( $field_value );
					if ( $email ) {
						cmplz_wsc_auth::send_auth_email( $email );
						// add an action to be executed after saving the fields to reset the client_id and client_secret.
						add_action( 'cmplz_after_saved_fields', array( $this, 'after_saved_fields' ), 100, 1 );
					}
					break;

				case 'send_notifications_email': // switch true / false
					$is_enabled = $field_value;
					break;

				case 'notifications_email_address':
					// newsletter signup the new address
					$email = sanitize_email( $field_value );
					if ( $email ) {
						cmplz_wsc_auth::newsletter_sign_up( $email );
					}
					break;

				default:
					return;
			}
		}


		/**
		 * This method is called after the fields are saved.
		 * If the email address has changed, it clears the values of 'wsc_client_id' and 'wsc_client_secret' fields.
		 *
		 * @param array $fields The array of fields.
		 * @return array The modified array of fields.
		 */
		public function after_saved_fields( array $fields ): array {
			if ( ! cmplz_user_can_manage() ) {
				return $fields;
			}

			// in $fields, find wsc_client_id and wsc_client_secret, and set the 'value' to ''.
			foreach ( $fields as $key => $field ) {
				if ( in_array( $field['id'], array( cmplz_wsc::WSC_CLIENT_ID_OPTION_KEY, cmplz_wsc::WSC_CLIENT_SECRET_OPTION_KEY ) ) ) {
					$fields[ $key ]['value'] = '';
				}
			}

			return $fields;
		}


		/**
		 *
		 * Handle the website scan reset
		 * Delete values on cmplz_settings option, options and transients.
		 *
		 * @return bool
		 */
		public function cmplz_wsc_reset_websitescan(): bool {
			global $wpdb;

			$options = array(
				// settings
				'cmplz_wsc_signup_status',
				'cmplz_wsc_signup_date',
				'cmplz_wsc_status',
				'cmplz_wsc_onboarding_complete',
				'cmplz_wsc_auth_completed',
				// notices
				'cmplz_wsc_error_email_mismatch',
				'cmplz_wsc_error_missing_token',
				'cmplz_wsc_error_email_auth_failed',
				'cmplz_wsc_error_email_not_sent',
				// token.
				'cmplz_wsc_error_token_api',
				'cmplz_wsc_connection_updated',
				'cmplz_wsc_onboarding_status',
				'cmplz_wsc_scan_id',
				'cmplz_wsc_scan_createdAt',
				'cmplz_wsc_scan_status',
				'cmplz_wsc_scan_iteration',
				'cmplz_wsc_scan_progress',
				'cmplz_wsc_scan_first_run',
			);

			$extra_credential_keys = apply_filters( 'cmplz_remove_wsc_extra_options', array() );

			$dynamic_options = array(
				'cmplz_%_consentdata',
				'cmplz_consent_error_',
				'cmplz_wsc_user_deletion_%',
				'cmplz_consent_%',
			);

			try {
				$temp_credentials = get_option( 'cmplz_wsc_user_deletion_temp_credentials', array() );
				$client_id        = cmplz_wsc_auth::get_wsc_client_id();
				$client_secret    = cmplz_wsc_auth::get_wsc_client_secret();

				$current_user = array(
					'client_id'     => $client_id,
					'client_secret' => $client_secret,
				);

				$temp_credentials[] = $current_user;
				update_option( 'cmplz_wsc_user_deletion_temp_credentials', $temp_credentials );

				$token = cmplz_wsc_auth::get_token( true );  // new token request, skip the one not expired stored into the db

				if ( ! $token ) {
					$this->log_user_deletion_error( 'cmplz_wsc_user_deletion_token_error', 'Token not retrieved' );
				} else {
					// Make the API request to delete the application
					$request = wp_remote_request(
						cmplz_wsc_auth::WSC_ENDPOINT . '/api/lite/oauth_applications',
						array(
							'method'    => 'DELETE',
							'headers'   => array(
								'Content-Type'  => 'application/json',
								'Authorization' => 'Bearer ' . $token,
							),
							'timeout'   => 15,
							'sslverify' => true,
						)
					);

					if ( is_wp_error( $request ) ) {
						$this->log_user_deletion_error( 'cmplz_wsc_user_deletion_error', $request->get_error_message() );
					}

					$response_code = wp_remote_retrieve_response_code( $request );

					if ( $response_code === 204 ) {
						$options = array_merge(
							$options,
							array(
								'cmplz_wsc_user_deletion_temp_credentials',
							)
						);
					} else {
						$this->log_user_deletion_error( 'cmplz_wsc_user_deletion_error', 'Unexpected API response' );
					}
				}

				// Proceed with cleanup
				foreach ( $options as $option ) {
					delete_option( $option );
				}

				// remove dynamic options
				foreach ( $dynamic_options as $option_name ) {
					$wpdb->query(
						$wpdb->prepare(
							"DELETE FROM $wpdb->options WHERE option_name LIKE %s",
							$option_name
						)
					);
				}

				// remove client credentials and access token
				cmplz_wsc_auth::clear_wsc_credentials( $extra_credential_keys );

				update_option( 'cmplz_wsc_reset_complete', true, false );

				return true;
			} catch ( Exception $e ) {
				$error = 'Exception during API request: ' . $e->getMessage();
				$this->log_user_deletion_error( 'cmplz_wsc_user_deletion_error', $error );
				return false;
			}
		}


		/**
		 * Checks if there was an error during user deletion and triggers the reset of the website scan if necessary.
		 *
		 * This function checks if there was an error during user deletion by retrieving the values
		 * of the 'cmplz_wsc_user_deletion_error' and 'cmplz_wsc_user_deletion_token_error' options.
		 * If either of these options is set to true, the function triggers the reset of the website scan
		 * by calling the 'cmplz_wsc_reset_websitescan' method.
		 *
		 * @return void
		 */
		public function check_failed_user_deletion(): void {
			$options = array(
				'cmplz_wsc_user_deletion_error',
				'cmplz_wsc_user_deletion_error_message',
				'cmplz_wsc_user_deletion_error_timestamp',
				'cmplz_wsc_user_deletion_token_error',
				'cmplz_wsc_user_deletion_token_error_message',
				'cmplz_wsc_user_deletion_token_error_timestamp',
				'cmplz_wsc_user_deletion_temp_credentials',
			);

			$failed_deletion = get_option( 'cmplz_wsc_user_deletion_temp_credentials', false ); // array
			if ( ! $failed_deletion ) {
				foreach ( $options as $option ) {
					delete_option( $option );
				}
			} else {
				foreach ( $failed_deletion as $client_credentials ) {
					if ( ! $client_credentials['client_id'] || ! $client_credentials['client_secret'] ) {
						continue;
					}

					$token = cmplz_wsc_auth::get_token( true, true, $client_credentials );
					if ( ! $token ) {
						continue;
					}

					$request = wp_remote_request(
						cmplz_wsc_auth::WSC_ENDPOINT . '/api/lite/oauth_applications',
						array(
							'method'    => 'DELETE',
							'headers'   => array(
								'Content-Type'  => 'application/json',
								'Authorization' => 'Bearer ' . $token,
							),
							'timeout'   => 15,
							'sslverify' => true,
						)
					);

					$response_code = wp_remote_retrieve_response_code( $request );
					if ( $response_code === 204 ) {
						// update the $failed_deletions array removing the current one
						$failed_deletion = array_filter(
							$failed_deletion,
							function ( $value ) use ( $client_credentials ) {
								return $value !== $client_credentials;
							}
						);
						update_option( 'cmplz_wsc_user_deletion_temp_credentials', $failed_deletion );
					}
				}
			}
		}


		/**
		 * Logs an error when a user deletion fails.
		 *
		 * @param string $option The option name to update.
		 * @param string $message The error message to log.
		 * @return void
		 */
		public function log_user_deletion_error( string $option, string $message ): void {
			update_option( $option, true, false );
			update_option( $option . '_message', $message, false );
			update_option( $option . '_timestamp', time(), false );
			cmplz_wsc_logger::log_errors( 'user_deletion', $message );
		}

		/**
		 * Handles the newsletter onboarding consent.
		 *
		 * This static method is responsible for storing the consent given by the user
		 * during the onboarding process for the newsletter.
		 *
		 * @param string $type The type of consent being stored.
		 * @param array  $posted_data The data associated with the consent.
		 * @return void
		 */
		public static function cmplz_store_newsletter_onboarding_consent_handler( string $type, array $posted_data ): void {
			cmplz_wsc_auth::store_onboarding_consent( $type, $posted_data );
		}
	}
}