<?php


namespace WPSecurityNinja\Plugin;

use Wf_Sn;

if ( ! defined( 'ABSPATH' ) ) {
	die;
}

class Utils
{


	/**
	 * Filters out any Freemius admin notices
	 *
	 * @author Lars Koudal
	 * @author Unknown
	 * @since v0.0.1
	 * @version v1.0.0 Wednesday, January 13th, 2021.	
	 * @version v1.0.1 Thursday, July 10th, 2025.
	 * @access public static
	 * @param mixed $show	
	 * @param mixed $msg
	 * @return mixed
	 */
	public static function do_filter_show_admin_notice( $show, $msg ) {
		if ( secnin_fs()->can_use_premium_code__premium_only() ) {
			include_once WF_SN_PLUGIN_DIR . 'modules/whitelabel/class-wf-sn-wl.php';
			if ( class_exists( __NAMESPACE__ . '\Wf_Sn_Wl' ) ) {
				if ( \WPSecurityNinja\Plugin\Wf_Sn_Wl::is_active() ) {
					return false;
				}
			}
		}
		return $show;
	}





	/**
	 * Do admin notices
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, January 12th, 2021.
	 * @version v1.0.1  Tuesday, March 22nd, 2022.
	 * @access  public static
	 * @return  void
	 */
	public static function do_admin_notices() {
		$is_sn_admin_page = \WPSecurityNinja\Plugin\Wf_Sn::is_plugin_page();
		$current_screen = get_current_screen();
		if ( ! $is_sn_admin_page ) {
			return;
		}

		$wf_sn_vu_vulns_notice = get_option( 'wf_sn_vu_vulns_notice', false );

		if ( isset( $wf_sn_vu_vulns_notice ) && ( $wf_sn_vu_vulns_notice ) && ( '' !== $wf_sn_vu_vulns_notice ) ) {
			$current_screen = get_current_screen();
			// Lets not show on the wizard page
			if ( strpos( $current_screen->id, 'page_security-ninja-wizard' ) === false ) {
?>
				<div class="notice notice-info is-dismissible secnin-notice">
					<h3><?php esc_html_e( 'Security Ninja - Vulnerability list updated!', 'security-ninja' ); ?></h3>
					<p><?php echo esc_html( $wf_sn_vu_vulns_notice ); ?></p>
				</div>
		<?php
				delete_option( 'wf_sn_vu_vulns_notice' );
			}
		}

		$review = get_option( 'wf_sn_review_notice' );
		$time   = time();
		$load   = false;

		if ( ! $review ) {
			$review = array(
				'time'      => $time,
				'dismissed' => false,
			);
			$load   = true;
		} elseif ( ( isset( $review['dismissed'] ) && ! $review['dismissed'] )
			&& ( isset( $review['time'] ) && ( $review['time'] <= $time ) )
		) {
			$load = true;
		}

		// Hvis vi skal vise den igen
		if ( isset( $review['time'] ) ) {
			if ( $time > $review['time'] ) {
				// Vi kan godt vise den igen
				$load = true;
			}
		}

		if ( ! $load ) {
			return;
		}
		// Update the review option now.
		update_option( 'wf_sn_review_notice', $review, false );
		$current_user = wp_get_current_user();
		$fname        = '';
		if ( ! empty( $current_user->user_firstname ) ) {
			$fname = $current_user->user_firstname;
		}

		if ( function_exists( '\\WPSecurityNinja\\Plugin\\secnin_fs' ) ) {
			if ( secnin_fs()->is_registered() ) {
				$get_user = secnin_fs()->get_user();
				$fname    = $get_user->first;
			}
		}

		// We have a candidate! Output a review message.

		$timeused = __( 'a while', 'security-ninja' );

		$options = \WPSecurityNinja\Plugin\Wf_Sn::$options;

		if ( isset( $options['first_install'] ) && is_numeric( $options['first_install'] ) ) {
			$first_install = intval( $options['first_install'] );
			$timeused = human_time_diff( $first_install, time() );
		}

		$current_screen = get_current_screen();
		// Lets not show on the wizard page
		if ( false !== strpos( $current_screen->id, 'page_security-ninja-wizard' ) ) {
			return;
		}
		?>
		<div class="notice notice-info is-dismissible wfsn-review-notice">
			<p>Hey <?php echo esc_html( $fname ); ?>, I noticed you have been using Security Ninja for
				<?php echo esc_html( $timeused ); ?> - that's awesome!</p>
			<p>Could you please do us a BIG favor and give it a 5-star rating on WordPress to help us spread the word?</p>
			<p>Thank you :-)</p>
			<p><strong>Lars Koudal,</br>wpsecurityninja.com</strong></p>
			<p>
			<ul>
				<li><a href="https://wordpress.org/support/plugin/security-ninja/reviews/?filter=5#new-post" class="wfsn-dismiss-review-notice wfsn-reviewlink button-primary" target="_blank" rel="noopener">Ok, you deserve
						it</a></li>
				<li><span class="dashicons dashicons-calendar"></span><a href="#" class="wfsn-dismiss-review-notice" target="_blank" rel="noopener">Nope, maybe later</a></li>
				<li><span class="dashicons dashicons-smiley"></span><a href="#" class="wfsn-dismiss-review-notice" target="_blank" rel="noopener">I already did</a></li>
			</ul>
			<p><small>This notice is shown every 30 days.</small></p>
		</div>
		<?php
	}


	/**
	 * signup_to_newsletter.
	 *
	 * @author  Lars Koudal
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, September 1st, 2021.
	 * @version v1.0.1  Thursday, March 3rd, 2022.
	 * @access  public static
	 * @return  void
	 */
	public static function signup_to_newsletter() {
		// Only show on SN pages
		$is_sn_admin_page = \WPSecurityNinja\Plugin\Wf_Sn::is_plugin_page();
		if ( ! $is_sn_admin_page ) {
			return;
		}

		$current_screen = get_current_screen();
		// Lets not show on the wizard page
		if ( false !== strpos( $current_screen->id, 'page_security-ninja-wizard' ) ) {
			return;
		}

		// Check if been dismissed already
		$review = get_option( 'wf_sn_review_notice' );
		if ( $review && isset( $review['dismissed'] ) && ( $review['dismissed'] ) ) {
			return;
		}

		if ( secnin_fs()->can_use_premium_code__premium_only() ) {
			// We do not show for customers
			return;
		}

		$current_user = wp_get_current_user();
		$admin_name   = $current_user->user_firstname;

		if ( $current_user->user_lastname ) {
			$admin_name .= ' ' . $current_user->user_lastname;
		}

		if ( \PAnD::is_admin_notice_active( 'wfs-newsletter-30' ) ) {
		?>
			<div data-dismissible="wfs-newsletter-30" class="secnin-notice sncard is-dismissible snnotice">
				<h3>Get WordPress Security Updates & Exclusive Deals</h3>
				<h4>Join 10,000+ WordPress admins getting critical security alerts, pro tips, and special offers directly in their inbox from wpsecurityninja.com</h4>
				<form class="ml-block-form" action="https://assets.mailerlite.com/jsonp/16490/forms/106309154087372203/subscribe" data-code="" method="post" target="_blank">
					<table>
						<tbody>
							<tr>
								<td>
									<input type="text" class="regular-text" data-inputmask="" name="fields[name]" placeholder="Your name" autocomplete="name" style="width:15em;" value="<?php echo esc_html( $current_user->display_name ); ?>" required="required">
								</td>
								<td>
									<input aria-label="email" aria-required="true" data-inputmask="" type="email" class="regular-text required email" data-inputmask="" name="fields[email]" placeholder="Your email" autocomplete="email" style="width:15em;" value="<?php echo esc_html( $current_user->user_email ); ?>" required="required">
								</td>
								<td>
									<button type="submit" class="button button-primary button-small">Subscribe</button>
								</td>
							</tr>
					</table>
					<input type="hidden" name="fields[signupsource]" value="Security Ninja Plugin <?php echo esc_attr( self::get_plugin_version() ); ?>">
					<input type="hidden" name="ml-submit" value="1">
					<input type="hidden" name="anticsrf" value="true">
				</form>


				<p>You can unsubscribe anytime. For more details, review our <a href="<?php echo esc_url( self::generate_sn_web_link( 'newsletter_signup', '/privacy-policy/' ) ); ?>" target="_blank" rel="noopener">Privacy Policy</a>.</p>
				<p><small>Signup form is shown every 30 days.</small> - <a href="javascript:;" class="dismiss-this">Click here to dismiss</a></p>
			</div>
		<?php
		}
	}

	/**
	 * Add last login column
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, August 1st, 2023.
	 * @access  public static
	 * @param   mixed $columns
	 * @return  mixed
	 */
	public static function add_user_last_login_column( $columns ) {
		$columns['secnin_last_login'] = __( 'Last Login', 'security-ninja' );
		return $columns;
	}



	/**
	 * return_last_login_column.
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, August 1st, 2023.
	 * @access  public static
	 * @param   mixed $output
	 * @param   mixed $column_id
	 * @param   mixed $user_id
	 * @return  mixed
	 */
	public static function return_last_login_column( $output, $column_id, $user_id) {
		if ( 'secnin_last_login' !== $column_id ) {
			return $output;
		}

		$current_time = current_time( 'timestamp' );

		$last_login = get_user_meta( $user_id, 'sn_last_login', true );
		if ( $last_login ) {
			$last_login_timestamp = strtotime( $last_login );
			if ( $last_login_timestamp <= $current_time ) {
				$human_time = human_time_diff( $last_login_timestamp, $current_time ) . ' ago';
				$friendly_date = date_i18n( get_option( 'date_format' ) . ' - ' . get_option( 'time_format' ), $last_login_timestamp );
				return $human_time . '<br><small>' . $friendly_date . '</small>';
			}
		} else {
			$session_tokens = get_user_meta( $user_id, 'session_tokens', true );
			if ( $session_tokens && is_array( $session_tokens ) ) {
				foreach ( $session_tokens as $stok ) {
					if ( isset( $stok['login'] ) && is_numeric( $stok['login'] ) && ( $stok['login'] <= $current_time ) ) {
						$human_time = human_time_diff( $stok['login'], $current_time ) . ' ago';
						$friendly_date = date_i18n( get_option( 'date_format' ) . ' - ' . get_option( 'time_format' ), $stok['login'] );
						return $human_time . '<br><small>' . $friendly_date . '</small>';
					}
				}
			}
		}
		return __( 'No recorded login', 'security-ninja' );
	}

	/**
	 * Checks for and migrates old license system to Freemius automatically.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.1  Thursday, September 21st, 2023.
	 * @access  public static
	 * @return  void
	 */
	public static function secnin_fs_license_key_migration() {
		$has_api_connectivity = false;
		if (secnin_fs()->has_api_connectivity()) {
			$has_api_connectivity = true;
		}
		$is_registered = false;
		if (secnin_fs()->is_registered()) {
			$is_registered = true;
		}
		if (false === $has_api_connectivity || $is_registered) {
			// No connectivity OR the user already opted-in to Freemius.
			return;
		}
		
		if ( 'pending' !== get_option( 'secnin_fs_migrated2fs', 'pending' ) ) {
			return;
		}

		// Check if license_key.txt exists in the plugin directory and use it to activate the license
		$license_file = WF_SN_PLUGIN_DIR . 'license_key.txt';
		$license_key  = '';

		if ( file_exists( $license_file ) ) {
			$file_contents = file( $license_file, FILE_IGNORE_NEW_LINES );
			if ( false !== $file_contents ) {
				$license_key = trim( $file_contents[0] );

				if ( empty( $license_key ) || strlen( $license_key ) !== 32 || strpos( $license_key, 'sk_' ) !== 0 ) {
					$license_key = '';
				}
			}
		}

		try {
			$next_page = secnin_fs()->activate_migrated_license( $license_key );
		} catch ( \Exception $e ) {
			update_option( 'secnin_fs_migrated2fs', 'unexpected_error', false );
			return;
		}

		if (secnin_fs()->can_use_premium_code()) {
			update_option( 'secnin_fs_migrated2fs', 'done', false );

			// Delete the license file if it exists
			if ( file_exists( $license_file ) ) {
				wp_delete_file( $license_file );
			}

			// Display admin notice for successful activation
			add_action(
				'admin_notices',
				function () {
					echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'License automatically activated!', 'security-ninja' ) . '</p></div>';
				}
			);
			if ( is_array( $next_page ) && isset( $next_page['success'] ) && 1 === $next_page['success'] && isset( $next_page['next_page'] ) ) {
				$next_page_url = wp_validate_redirect( $next_page['next_page'], admin_url() );
				wp_safe_redirect( $next_page_url );
				exit();
			}
		} else {
			update_option( 'secnin_fs_migrated2fs', 'failed', false );
		}
	}







	/**
	 * Handles incoming requests from MainWP Master.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 6th, 2023.
	 * @access  public static
	 * @param   mixed $information
	 * @param   mixed $data        Default: array()
	 * @return  mixed
	 */
	public static function do_filter_mainwp_site_sync_others_data($information, $data = array())
	{

		if (isset( $data['SecNin_get_details'] ) && $data['SecNin_get_details'] ) {
			try {

				global $wpdb;

				$information['SecNin_get_details'] = array(
					'plan' => 'Free',
					'ver'  => self::get_plugin_version(),
				);

				// Check vulnerabilities
				if (class_exists(__NAMESPACE__ . '\wf_sn_vu')) {
					try {
						$vulns      = \WPSecurityNinja\Plugin\Wf_Sn_Vu::return_vuln_count();
						$vulndetails = \WPSecurityNinja\Plugin\Wf_Sn_Vu::return_vulnerabilities();
					} catch (\Exception $e) {
						$vulns      = 0;
						$vulndetails = array();
					}
					$information['SecNin_get_details']['vulns'] = $vulns;
					$information['SecNin_get_details']['vulndetails'] = $vulndetails;
				}

				// Get test scores
				$information['SecNin_get_details']['tests']        = \WPSecurityNinja\Plugin\Wf_Sn::return_test_scores();
				$information['SecNin_get_details']['test_results'] = \WPSecurityNinja\Plugin\Wf_Sn::get_test_results();

				if (secnin_fs()->can_use_premium_code__premium_only()) {

					// Premium part
					$information['SecNin_get_details']['plan'] = 'Pro';

					$wf_sn_cs_results = get_option( 'wf_sn_cs_results' );
					$information['SecNin_get_details']['cs_results'] = $wf_sn_cs_results;
					if ( is_array( $wf_sn_cs_results ) && ! empty( $wf_sn_cs_results['last_run'] ) && class_exists( '\WPSecurityNinja\Plugin\Wf_Sn_Cs' ) ) {
						$information['SecNin_get_details']['cs_meta'] = \WPSecurityNinja\Plugin\Wf_Sn_Cs::build_meta_strings( $wf_sn_cs_results );
					}

					// Hente seneste events
					$table_name = $wpdb->prefix . 'wf_sn_el';
					$query      = $wpdb->prepare(
						"SELECT timestamp, ip, module, action, user_agent, user_id, description FROM `$table_name` 
                                WHERE action != %s AND action != %s AND user_agent NOT LIKE %s 
                                ORDER BY id DESC 
                                LIMIT %d;",
						'mainwp_request', // First action to exclude
						'mainwp', // Second action to exclude
						'%cron%', // Pattern to exclude from user_agent
						500 // Limit
					);

					$last_events = $wpdb->get_results( $query );
					$information['SecNin_get_details']['last_events'] = $last_events;
					$information['SecNin_get_details']['mysql_time_offset'] = get_option( 'gmt_offset' );
					$information['SecNin_get_details']['mysql_time_zone']   = get_option( 'timezone_string' );

					$unblock_url = '';
					if ( class_exists( __NAMESPACE__ . '\Wf_sn_cf' ) ) {
						$unblock_url = esc_url( \WPSecurityNinja\Plugin\Wf_sn_cf::get_unblock_url() );
					}
					$information['SecNin_get_details']['secret_access'] = $unblock_url;

					// hente malware scanner resultater
					$wf_sn_ms_results                                = get_option('wf_sn_ms_results');
					$information['SecNin_get_details']['ms_results'] = $wf_sn_ms_results;

					$globaloptions                                = \WPSecurityNinja\Plugin\Wf_Sn::return_global_options__premium_only();
					$information['SecNin_get_details']['options'] = $globaloptions;

					\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event('security_ninja', 'mainwp', __( 'Syncing data with MainWP Dashboard', 'security-ninja'), '');
				}
			} catch (\Exception $e) {
				$information['SecNin_get_details'] = array(
					'error' => $e->getMessage(),
				);
			}
		}

		return $information;
	}





	/**
	 * Integrating with MainWP
	 *
	 * @author  Lars Koudal
	 * @since   v5.139
	 * @version v1.0.0  Thursday, March 24th, 2022.
	 * @version v1.0.1  Saturday, April 2nd, 2022.
	 * @access  public static
	 * @param   mixed $info      – Information
	 *                           to return.
	 * @param   mixed $post_data – Post data array
	 *                           from MainWP.
	 * @return  mixed
	 */
	public static function do_filter_mainwp_child_extra_execution($info, $post_data)
	{

		if (isset( $post_data['action'] )) {

			if (secnin_fs()->can_use_premium_code__premium_only()) {
				\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event('security_ninja', 'mainwp_request', 'Request from MainWP Dashboard (' . esc_attr($post_data['action'] ) . ')', array('action' => esc_attr($post_data['action'] )));
			}

			switch ($post_data['action'] ) {

				// *** Run all tests
				case 'run_all_tests':
					if (secnin_fs()->can_use_premium_code__premium_only()) {
						\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event('security_ninja', 'mainwp', 'Got a request from MainWP to run tests.');
					}

					wp_schedule_single_event(time(), 'secnin_run_tests_event');

					break;

				// *** Start malware scan
				case 'run_malware_scan':
					if (secnin_fs()->can_use_premium_code__premium_only()) {
						\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event('security_ninja', 'mainwp', 'Got a request from MainWP to run malware scan.');
						\WPSecurityNinja\Plugin\wf_sn_ms::run_scan('do_mal_scan');
					}
					break;

				// *** Update settings
				case 'update_settings':
					// @todo - sanitity checks and then update values
					break;

				// *** Update vulnerabilities
				case 'update_vulnerabilities':
					if (secnin_fs()->can_use_premium_code__premium_only()) {
						\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event('security_ninja', 'mainwp', 'Got a request from MainWP Dashboard to update vulnerabilities.');
					}
					\WPSecurityNinja\Plugin\Wf_Sn_Vu::update_vuln_list();
					break;

				// ***  Update white label settings
				case 'update_white_label':
					if (secnin_fs()->can_use_premium_code__premium_only()) {

						$sanitized_settings = array(
							'wl_active'         => isset( $post_data['white_label_settings']['wl_active'] ) && '1' === $post_data['white_label_settings']['wl_active'] ? '1' : '0',
							'wl_newname'        => isset( $post_data['white_label_settings']['wl_newname'] ) ? sanitize_text_field($post_data['white_label_settings']['wl_newname'] ) : 'Security Ninja',
							'wl_newdesc'        => isset( $post_data['white_label_settings']['wl_newdesc'] ) ? sanitize_text_field($post_data['white_label_settings']['wl_newdesc'] ) : '',
							'wl_newauthor'      => isset( $post_data['white_label_settings']['wl_newauthor'] ) ? sanitize_text_field($post_data['white_label_settings']['wl_newauthor'] ) : '',
							'wl_newurl'         => isset( $post_data['white_label_settings']['wl_newurl'] ) ? esc_url_raw($post_data['white_label_settings']['wl_newurl'] ) : 'https://wpsecurityninja.com/',
							'wl_newiconurl'     => isset( $post_data['white_label_settings']['wl_newiconurl'] ) ? esc_url_raw($post_data['white_label_settings']['wl_newiconurl'] ) : '',
							'wl_newmenuiconurl' => isset( $post_data['white_label_settings']['wl_newmenuiconurl'] ) ? esc_url_raw($post_data['white_label_settings']['wl_newmenuiconurl'] ) : '',
						);

						// Set defaults if any field is empty
						$defaults = array(
							'wl_active'         => '0',
							'wl_newname'        => 'Security Ninja',
							'wl_newdesc'        => '',
							'wl_newauthor'      => '',
							'wl_newurl'         => 'https://wpsecurityninja.com/',
							'wl_newiconurl'     => '',
							'wl_newmenuiconurl' => '',
						);

						$final_settings = wp_parse_args($sanitized_settings, $defaults);

						update_option('wf_sn_wl', $final_settings, 'no');

						\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event('security_ninja', 'mainwp', 'Updated white label settings via MainWP Dashboard.');
					}

					break;

				// *** Force create database tables (for incomplete installations)
				case 'force_create_tables':
					if ( secnin_fs()->is__premium_only() ) {
						try {
							global $wpdb;
							include_once ABSPATH . 'wp-admin/includes/upgrade.php';
							$charset = $wpdb->get_charset_collate();
							self::create_tables_for_site( $charset );
							$info['secnin_force_create_tables'] = array(
								'success' => true,
								'message' => __( 'Database tables created or updated successfully.', 'security-ninja' ),
							);
							\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event( 'security_ninja', 'mainwp', __( 'Force created database tables via MainWP.', 'security-ninja' ), '' );
						} catch ( \Exception $e ) {
							$info['secnin_force_create_tables'] = array(
								'success' => false,
								'message' => $e->getMessage(),
							);
						}
					}
					break;

				default:
					break;
			}
		}

		return $info;
	}





	/**
	 * Create custom select element
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, January 13th, 2021.
	 * @version v1.0.1  Thursday, April 11th, 2024.
	 * @access  public static
	 * @param   mixed   $options
	 * @param   boolean $selected   Default: false
	 * @param   boolean $output     Default: true
	 * @return  void
	 */
	public static function create_select_options( $options, $selected = null, $output = true ) {
		$out               = '';
		$is_selected_array = is_array( $selected );

		foreach ( $options as $option ) {
			$value = isset( $option['val'] ) ? $option['val'] : '';
			$label = isset( $option['label'] ) ? $option['label'] : '';

			$is_selected = false;
			if ( $is_selected_array && is_array( $selected ) ) {
				$is_selected = in_array( $value, $selected, true );
			} elseif ( null !== $selected ) {
				$is_selected = ( is_numeric( $value ) && is_numeric( $selected ) )
					? $value == $selected // Loose comparison for numeric values
					: $value === $selected; // Strict comparison for other types
			}

			$value         = esc_attr( $value );
			$label         = esc_html( $label );
			$selected_attr = $is_selected ? ' selected="selected"' : '';

			$out .= sprintf( '<option value="%1$s"%2$s>%3$s</option>', $value, $selected_attr, $label );
		}

		$allowed_html = array(
			'option' => array(
				'value'    => array(),
				'selected' => array(),
			),
		);

		if ( $output ) {
			echo wp_kses( $out, $allowed_html );
		} else {
			return wp_kses( $out, $allowed_html );
		}
	}



	/**
	 * Helper function to generate tagged links
	 *
	 * @param  string $placement [description]
	 * @param  string $page      [description]
	 * @param  array  $params    [description]
	 * @return string            Full URL with utm_ parameters added
	 */
	public static function generate_sn_web_link( $placement = '', $page = '/', $params = array() ) {
		$base_url = 'https://wpsecurityninja.com';
		if ( '/' !== $page ) {
			$page = '/' . trim($page, '/') . '/';
		}
		$utm_source = 'security_ninja_free';

		if ( secnin_fs()->can_use_premium_code__premium_only() ) {
			$utm_source = 'security_ninja_pro';
		}

		$parts = array_merge(
			array(
				'utm_source'   => esc_attr( $utm_source ),
				'utm_medium'   => 'plugin',
				'utm_content'  => esc_attr( $placement ),
				'utm_campaign' => esc_attr( 'security_ninja_v' . self::get_plugin_version() ),
			),
			$params
		);

		$out = $base_url . $page . '?' . http_build_query( $parts, '', '&amp;' );
		return $out;
	}



	/**
	 * add_freemius_extra_permission.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Saturday, March 5th, 2022.
	 * @access  public static
	 * @param   mixed $permissions
	 * @return  mixed
	 */
	public static function add_freemius_extra_permission( $permissions ) {

		$permissions['wpsnapi']    = array(
			'id' => 'security_ninja_api',
			'icon-class' => 'dashicons dashicons-sos',
			'label'      => __( 'Security Ninja API', 'security-ninja' ),
			'desc'       => __( 'Sending and getting data from Security Ninja API servers.', 'security-ninja' ),
			'priority'   => 17,
		);
		$permissions['newsletter'] = array(
			'id' => 'security_ninja_newsletter',
			'icon-class' => 'dashicons dashicons-email-alt2',
			'label'      => __( 'Newsletter', 'security-ninja' ),
			'desc'       => __( 'You are added to our newsletter. Unsubscribe anytime.', 'security-ninja' ),
			'priority'   => 18,
		);

		return $permissions;
	}




	/**
	 * Add markup for UI overlay.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, January 14th, 2021.
	 * @access  public static
	 * @return  void
	 */
	public static function admin_footer()
	{
		if (\WPSecurityNinja\Plugin\Wf_Sn::is_plugin_page()) {

			echo '<div id="sn_overlay"><div class="sn-overlay-wrapper">';
			echo '<div class="inner">';
			if (secnin_fs()->can_use_premium_code__premium_only()) {
				if (class_exists(__NAMESPACE__ . '\Wf_Sn_Wl')) {
					if (! \WPSecurityNinja\Plugin\Wf_Sn_Wl::is_active()) {
						echo '<div class="wf-sn-title">
										<h2><img src="' . esc_url(WF_SN_PLUGIN_URL . 'images/security-ninja-logo.png') . '" alt="Security Ninja" title="Security Ninja"></h2>
									  </div>';
					}
				}
			}
			// Outer
			echo '<div class="wf-sn-overlay-outer">';

			echo '<div class="wf-sn-overlay-content">';
			echo '<div id="sn-site-scan" style="display: none;">';

			echo '</div>';
			echo '<p><a id="abort-scan" href="#" class="button button-secondary">Cancel</a></p>';
			echo '</div>'; // wf-sn-overlay-content

			echo '</div></div></div></div>';

			echo '<div id="test-details-dialog" style="display: none;" title="Test details"><p>Please wait.</p></div>';

			echo '<div id="sn_tests_descriptions" style="display: none;">';
			include_once WF_SN_PLUGIN_DIR . 'sn-tests-description.php';
			echo '</div>';
		}
	}


		/**
		 * Returns icon in SVG format
		 * Thanks Yoast for code.
		 *
		 * @author  Lars Koudal
		 * @since   v0.0.1
		 * @version v1.0.0  Thursday, January 14th, 2021.
		 * @access  public static
		 * @param   boolean $base64 Return SVG in base64 or not
		 * @param   string  $color  Default: '82878c'
		 * @return  mixed
		 */
		public static function get_icon_svg( $base64 = true, $color = '82878c' ) {
			$svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
																													<g fill="#' . $color . '">
																													<path d="M171.117 262.277c14.583-.142 25.832 20.664 25.921 35.25.094 15.265-11.418 37.682-26.678 37.227-14.687-.438-23.797-22.605-23.494-37.296.295-14.24 10.095-35.044 24.25-35.181zM322.387 263.03c14.584-.142 25.832 20.664 25.922 35.25.093 15.265-11.419 37.681-26.679 37.227-14.686-.438-23.797-22.606-23.493-37.296.294-14.24 10.094-35.044 24.25-35.182z"/>
																													<path d="M331.348 26.203c0-.107 98.038-7.914 98.038-7.914s-9.219 91.716-10.104 96.592c1.277-3.3 22.717-46.002 22.818-46.002.105 0 53.047 69.799 53.047 69.799l-46.63 42.993c26.6 30.762 41.632 67.951 41.724 107.653.239 103.748-110.253 191.827-245.68 191.091-130.352-.706-239.977-86.977-240.475-188.91-.5-102.38 105.089-191.741 239.663-192.095 38.677-.1 74.34 6.068 105.82 17.154-3.241-16.067-18.22-90.265-18.22-90.36zm-85.421 157.959c-74.098-1.337-161.3 41.627-161.054 105.87.247 63.88 87.825 103.981 160.683 104.125 78.85.154 164.156-41.58 163.722-106.614-.428-64.436-86.566-101.996-163.351-103.381z"/>
																													</g>
																													</svg>';
			if ( $base64 ) {
				return 'data:image/svg+xml;base64,' . base64_encode( $svg );
			}
			return $svg;
		}


	/**
	 * Renders the output for the whitelabel page
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Saturday, March 5th, 2022.
	 * @access  public static
	 * @return  void
	 */
	public static function render_whitelabel_page()
	{
		?>
		<div class="submit-test-container">
			<div class="fomcont">
				<h3>Unlock Agency-Level Branding</h3>
				<img src="<?php echo esc_url(WF_SN_PLUGIN_URL . 'images/whitelabel.jpg'); ?>" alt="Whitelabel your security work." class="tabimage">



				<p>
					White label is the ultimate branding tool for agencies and professionals. Present a fully customized security solution to your clients, boosting your brand recognition and client loyalty. Hide WP Security Ninja branding, customize the plugin name, add your company URL, and even use your own logo.
				</p>
				<ul>
					<li><strong>Increase Brand Visibility:</strong> Showcase your brand with every security scan and notification.</li>
					<li><strong>Enhanced Client Loyalty:</strong> Provide a seamless, branded experience that keeps clients coming back.</li>
				</ul>
				<p>
					<em>This feature is available for Pro users with 25+ site licenses.</em>
				</p>





				<p class="fomlink"><a target="_blank" href="<?php echo esc_url( \WPSecurityNinja\Plugin\Utils::generate_sn_web_link( 'tab_whitelabel', '/' )); ?>" class="button button-primary" rel="noopener"><?php esc_html_e( 'Learn more', 'security-ninja' ); ?></a></p>

			</div>
		</div>
		<?php
	}

	/**
	 * Safely strips HTML and PHP tags from a string, handling null values.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, December 7th, 2021.
	 * @access  public static
	 * @param   string|null $str The string to strip tags from.
	 * @param   string|null $allowable_tags Optional. Tags to allow.
	 * @return  string The string with tags stripped.
	 */
	public static function safe_strip_tags( $str, $allowable_tags = null ) {
		if ( null === $str ) {
			return '';
		}
		return strip_tags( $str, $allowable_tags );
	}


	public static function extra_modern_checkout_parameters( $parameters ) {
		// You can modify existing parameters in the $parameters array or add new ones.
		$parameters['show_refund_badge']    = true;
		$parameters['show_reviews']         = true;
		$parameters['billing_cycle_selector'] = 'dropdown';

		return $parameters;
}


		/**
		 * Creates database tables for a specific site.
		 *
		 * @author  Lars Koudal
		 * @since   v0.0.1
		 * @version v1.0.0  Tuesday, December 7th, 2021.
		 * @access  private static
		 * @param   string $charset Database charset.
		 * @return  bool Always returns true to indicate completion.
		 */
		public static function create_tables_for_site( $charset ) {
			global $wpdb;

			// Create main tests table
			$table_name = $wpdb->prefix . 'wf_sn_tests';
			$sql = "CREATE TABLE {$table_name} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, testid varchar(30) NOT NULL, timestamp datetime NOT NULL, title text, status tinyint(4) NOT NULL, score tinyint(4) NOT NULL, runtime float DEFAULT NULL, msg text, details text, PRIMARY KEY  (testid), KEY id (id)) {$charset};";
			dbDelta( $sql );

			// EVENT LOGS
			$table_name = $wpdb->prefix . 'wf_sn_el';
			$sql = "CREATE TABLE {$table_name} (id bigint(20) unsigned NOT NULL AUTO_INCREMENT,timestamp datetime NOT NULL,ip varchar(39) NOT NULL,user_agent varchar(255) NOT NULL,user_id int(10) unsigned NOT NULL,module varchar(32) NOT NULL,action varchar(64) NOT NULL,description text NOT NULL,raw_data blob NOT NULL,PRIMARY KEY  (id)) {$charset};";
			dbDelta( $sql );

			// Firewall - local list of blocked IPs (free feature)
			$table_name = $wpdb->prefix . 'wf_sn_cf_bl_ips';
			$sql = "CREATE TABLE {$table_name} (tid datetime NOT NULL DEFAULT NOW(),ip varchar(46) NOT NULL, reason varchar(255) NOT NULL, PRIMARY KEY  (ip),KEY tid (tid)) {$charset}";
			dbDelta( $sql );

			// Set up default settings for Event logger (free feature)
			if ( class_exists( '\\WPSecurityNinja\\Plugin\\Wf_Sn_El' ) ) {
				include_once WF_SN_PLUGIN_DIR . 'modules/cloud-firewall/class-sn-geolocation.php';
				Wf_Sn_El::default_settings( false );
			}

			if ( secnin_fs()->is__premium_only() ) {

				// VISITOR LOG
				$table_name = $wpdb->prefix . 'wf_sn_cf_vl';
				$sql = "CREATE TABLE {$table_name} (id bigint(20) unsigned NOT NULL AUTO_INCREMENT,ip varchar(45) NOT NULL,timestamp datetime NOT NULL,user_agent text,country varchar(2),banned tinyint(1) DEFAULT 0,ban_reason varchar(255),description text,raw_data text,URL text,PRIMARY KEY  (id),KEY ip (ip),KEY timestamp (timestamp),KEY banned (banned)) {$charset};";
				dbDelta( $sql );

				// SCHEDULED SCANNER
				$table_name = $wpdb->prefix . 'wf_sn_ss_log';
				$sql = "CREATE TABLE {$table_name} (id bigint(20) unsigned NOT NULL AUTO_INCREMENT,timestamp datetime NOT NULL,runtime float NOT NULL,sn_results text,cs_results text,sn_change tinyint(4) NOT NULL,cs_change tinyint(4) NOT NULL,PRIMARY KEY  (id)) {$charset};";
				dbDelta( $sql );

				// Log completion
				if ( class_exists( '\\WPSecurityNinja\\Plugin\\wf_sn_el_modules' ) ) {
					\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event( 'security_ninja', 'installation_routines', 'Completed Pro activation processes for site: ' . get_current_blog_id() );
				}
			}

			return true;
		}


	/**
	 * Safely trims whitespace from the beginning of a string, handling null values.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, December 7th, 2021.
	 * @access  public static
	 * @param   string|null $str The string to trim.
	 * @param   string $character_mask Optional. Characters to trim.
	 * @return  string The trimmed string.
	 */
	public static function safe_ltrim( $str, $character_mask = " \t\n\r\0\x0B" ) {
		if ( null === $str ) {
			return '';
		}
		return ltrim( $str, $character_mask );
	}

	/**
	 * Safely trims whitespace from both ends of a string, handling null values.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, December 7th, 2021.
	 * @access  public static
	 * @param   string|null $str The string to trim.
	 * @param   string $character_mask Optional. Characters to trim.
	 * @return  string The trimmed string.
	 */
	public static function safe_trim( $str, $character_mask = " \t\n\r\0\x0B" ) {
		if ( null === $str ) {
			return '';
		}
		return trim( $str, $character_mask );
	}

	/**
	 * Start timer for performance measurement
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, January 12th, 2021.
	 * @access  public static
	 * @param   string $watchname Timer identifier
	 * @return  void
	 */
	public static function timerstart( $watchname ) {
		set_transient( 'security_ninja_' . esc_attr( $watchname ), microtime( true ), 60 * 60 * 1 );
	}

	/**
	 * End timer and return elapsed time
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, January 12th, 2021.
	 * @access  public static
	 * @param   string   $watchname Timer identifier
	 * @param   integer  $digits    Number of decimal places (default: 5)
	 * @return  float    Elapsed time in seconds
	 */
	public static function timerstop( $watchname, $digits = 5 ) {
		$return = round( microtime( true ) - get_transient( 'security_ninja_' . esc_attr( $watchname ) ), $digits );
		delete_transient( 'security_ninja_' . esc_attr( $watchname ) );
		return $return;
	}

	/**
	 * Shows the topbar, logo and version
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0    Monday, March 11th, 2024.
	 * @access  public static
	 * @global
	 * @return  void
	 */
	public static function show_topbar() {
		$icon_url = WF_SN_PLUGIN_URL . 'images/sn-logo.svg';

		$menu_title = 'Security Ninja';
		if ( secnin_fs()->can_use_premium_code__premium_only() ) {
			// If whitelabel enabled
		if ( file_exists( WF_SN_PLUGIN_DIR . 'modules/whitelabel/class-wf-sn-wl.php' ) ) {
			require_once WF_SN_PLUGIN_DIR . 'modules/whitelabel/class-wf-sn-wl.php';
		}
		if ( class_exists( 'WPSecurityNinja\\Plugin\\Wf_Sn_Wl' ) ) {
			if ( \WPSecurityNinja\Plugin\Wf_Sn_Wl::is_active() ) {
					$get_new_icon_url = \WPSecurityNinja\Plugin\Wf_Sn_Wl::get_new_icon_url();
					// $get_new_menu_icon_url = \WPSecurityNinja\Plugin\Wf_Sn_Wl::get_new_menu_icon_url();
					if ( $get_new_icon_url ) {
						$icon_url = $get_new_icon_url;
					} else {
						$icon_url = '';
						// No icon if not set
					}
					$menu_title = \WPSecurityNinja\Plugin\Wf_Sn_Wl::get_new_name();
				}
			}
		}

		$topbar  = '<div id="sntopbar">';
		$topbar .= '<div class="plugname">';
		if ( ! empty( $icon_url ) ) {
			$topbar .= '<img src="' . $icon_url . '" height="28" alt="' . esc_attr( $menu_title ) . '" class="logoleft">';
		}
		$topbar .= '<div class="name">' . esc_html( $menu_title ) . ' <span>v.' . esc_html( self::get_plugin_version() ) . '</span></div></div>';

		$menu_links = '<a href="https://wordpress.org/support/plugin/security-ninja/" target="_blank" rel="noopener noreferrer" class="extlink">Support Forum</a>';

		if ( secnin_fs()->can_use_premium_code__premium_only() ) {
			// more info for premium users
			$help_link   = \WPSecurityNinja\Plugin\Utils::generate_sn_web_link( 'sidebar_link', '/help/' );
			$doc_link    = \WPSecurityNinja\Plugin\Utils::generate_sn_web_link( 'sidebar_link', '/docs/' );
			$menu_links  = '<a href="' . esc_attr( $doc_link ) . '" target="_blank" rel="noopener noreferrer" class="extlink">Documentation</a>';
			$menu_links .= '<a href="' . esc_attr( $help_link ) . '" target="_blank" rel="noopener noreferrer" class="extlink">Support</a>';
		}

		$is_registered = false;
		if (secnin_fs()->is_registered()) {
			$is_registered = true;
		}
		$is_tracking_allowed = false;
		if (secnin_fs()->is_tracking_allowed()) {
			$is_tracking_allowed = true;
		}
		if ($is_registered && $is_tracking_allowed) {
			$menu_links .= '<a href="#" class="productlift-sidebar=caa739c4-554e-4526-9720-a600a6702a8e whatsnew">Updates 🚀</a>';
		}

		if ( secnin_fs()->can_use_premium_code__premium_only() ) {

		// remove details for when white label is enabled
		if ( class_exists( 'WPSecurityNinja\\Plugin\\Wf_Sn_Wl' ) ) {
			if ( \WPSecurityNinja\Plugin\Wf_Sn_Wl::is_active() ) {
					$menu_links = '';
				}
			}
		}

		$topbar .= '<div class="links">' . $menu_links . '</div>';
		$topbar .= '</div>';
		echo wp_kses_post( $topbar );
	}

	/**
	 * Creates a toggle switch HTML element
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, January 12th, 2021.
	 * @access  public static
	 * @param   string $name    The name/id of the toggle switch
	 * @param   array  $options Options for the toggle switch
	 * @param   bool   $output  Whether to output or return the HTML
	 * @return  string|void     HTML string if $output is false, void otherwise
	 */
	public static function create_toggle_switch( $name, $options = array(), $output = true ) {
		$default_options = array(
			'value'       => 1,
			'saved_value' => 0,
			'option_key'  => '',
		);
		$options         = wp_parse_args( $options, $default_options );
		$value           = (int) $options['value'];
		$saved_value     = ( isset( $options['saved_value'] ) ? (int) $options['saved_value'] : 0 );
		$checked         = ( $value === $saved_value ? ' checked' : '' );

		// When unchecked, checkboxes are omitted from POST. A hidden input with value 0
		// ensures the field is always submitted so "off" persists (e.g. 2FA disable).
		$html = '';
		if ( '' !== (string) $options['option_key'] ) {
			$html .= sprintf(
				'<input type="hidden" name="%1$s" value="0">',
				esc_attr( $options['option_key'] )
			);
		}
		$html .= sprintf(
			'<input type="checkbox" id="%1$s" value="%2$s" class="switch" name="%3$s"%4$s>',
			esc_attr( $name ),
			esc_attr( $options['value'] ),
			esc_attr( $options['option_key'] ),
			$checked
		);

		if ( $output ) {
			echo wp_kses(
				$html,
				array(
					'div'   => array(
						'class' => array(),
					),
					'input' => array(
						'type'    => array(),
						'id'      => array(),
						'value'   => array(),
						'name'    => array(),
						'class'   => array(),
						'checked' => array(),
					),
					'label' => array(
						'for'   => array(),
						'class' => array(),
					),
					'span'  => array(
						'class' => array(),
					),
				)
			);
		} else {
			return $html;
		}
	}

	/**
	 * Get country flag image URL
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Saturday, March 5th, 2022.
	 * @access  public static
	 * @param   string|bool $country Country code or false
	 * @return  string|bool           Country flag URL or false
	 */
	public static function get_country_img__premium_only( $country = false ) {
		if ( ! $country ) {
			return false;
		}
		$filepath = trailingslashit( WF_SN_PLUGIN_DIR ) . 'images/flags/' . $country . '.png';
		if ( file_exists( $filepath ) ) {
			$fileurl = trailingslashit( WF_SN_PLUGIN_URL ) . 'images/flags/' . $country . '.png';
			return $fileurl;
		}
		return false;
	}

	/**
	 * Get country flag image HTML with country name
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Saturday, March 5th, 2022.
	 * @access  public static
	 * @param   string|bool $country Country code or false
	 * @param   integer     $size    Image size in pixels (default: 32)
	 * @return  string|bool          HTML img tag or false
	 */
	public static function get_country_img_src__premium_only( $country = false, $size = 32 ) {
		if ( ! $country ) {
			return false;
		}
		$return          = false;
		$country_img_url = self::get_country_img__premium_only( $country );
		if ( ! isset( $geoip_countrylist ) ) {
			include WF_SN_PLUGIN_DIR . 'modules/cloud-firewall/class-sn-geoip-countrylist.php';
		}
		$country_name = '';
		if ( isset( $geoip_countrylist[ $country ] ) ) {
			$country_name = $geoip_countrylist[ $country ];
		}
		if ( $country_img_url ) {
			$return = '<img src="' . $country_img_url . '" width="' . $size . '" height="' . $size . '" class="countryimg" title="' . $country_name . '">';
		}
		return $return;
	}

	/**
	 * Fetch plugin version from plugin PHP header
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, January 13th, 2021.
	 * @access  public static
	 * @return  string Plugin version
	 */
	public static function get_plugin_version() {
		$plugin_data = get_file_data(
			WF_SN_BASE_FILE,
			array(
				'version' => 'Version',
			),
			'plugin'
		);
		return $plugin_data['version'];
	}

	/**
	 * Fetch plugin name from plugin PHP header
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, January 13th, 2021.
	 * @access  public static
	 * @return  string Plugin name
	 */
	public static function get_plugin_name() {
		$plugin_data = get_file_data(
			WF_SN_BASE_FILE,
			array(
				'name' => 'Plugin Name',
			),
			'plugin'
		);
		return $plugin_data['name'];
	}

	/**
	 * Converts a status integer to a button.
	 *
	 * This method takes a status code as an integer and returns a string representing a button with a class indicating the status.
	 * The status codes are mapped to the following buttons:
	 * - 0: Fail (class "sn-error")
	 * - 10: OK (class "sn-success")
	 * - Any other value: Warning (class "sn-warning")
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, December 7th, 2021.
	 * @access  public static
	 * @param   int $statuscode The status code to convert.
	 * @return  string The HTML string representing the button.
	 */
	public static function status( $statuscode ) {
		switch ( $statuscode ) {
			case 0:
				$string = '<span class="sn-error">' . __( 'Fail', 'security-ninja' ) . '</span>';
				break;
			case 10:
				$string = '<span class="sn-success">' . __( 'OK', 'security-ninja' ) . '</span>';
				break;
			default:
				$string = '<span class="sn-warning">' . __( 'Warning', 'security-ninja' ) . '</span>';
				break;
		}
		return $string;
	}


	/**
	 * Normalize a boolean-like value to integer 0 or 1
	 * 
	 * Converts various boolean representations (true, false, 'true', '1', 1, 0, etc.)
	 * to a consistent integer format (0 or 1) for storage and comparison.
	 * 
	 * @author  Lars Koudal
	 * @since   v1.0.0
	 * @version v1.0.0  Monday, January 20th, 2025.
	 * @access  public static
	 * @param   mixed   $value  The value to normalize (can be boolean, string, integer, etc.)
	 * @return  int     Returns 1 if value is truthy, 0 otherwise
	 */
	public static function normalize_flag( $value ) {
		// Handle explicit true/false
		if ( $value === true ) {
			return 1;
		}
		if ( $value === false ) {
			return 0;
		}
		
		// Handle string representations
		if ( is_string( $value ) ) {
			$value = strtolower( trim( $value ) );
			if ( $value === 'true' || $value === '1' || $value === 'yes' || $value === 'on' ) {
				return 1;
			}
			return 0;
		}
		
		// Handle numeric values
		if ( is_numeric( $value ) ) {
			return ( (int) $value ) ? 1 : 0;
		}
		
		// For any other type, use truthiness check
		return $value ? 1 : 0;
	}
}
