\FF\D8\FF\E0\00JFIF\00\00\00d\00d\00\00\FF\FE\00\border bs:0 bc:#000000 ps:0 pc:#ffffff es:0 ec:#000000 ck:feee6c715d26fd9f38b0ca4278c05026\FF\DB\00C\00P7<F<2PFAFZUP_xxnnx\F5\AF\B9\91\C8\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\DB\00CUZZxix‚\EB\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\C0\00\00b\00d\00\FF\C4\00\00\00\00\00\00\00\00\00\00\00\00\00\00\FF\C4\00\00\00\00\00\00\00\00\00\00\001A!Q\FF\C4\00\00\00\00\00\00\00\00\00\00\00\00\00\FF\C4\00\00\00\00\00\00\00\00\00\00!1AQ\FF\DA\00\00\00?\00\F6\00\00\00\00\00\00\00\00\00\00\A0\00\00\C5\CF\F8\E7Ó\FCjD~\B9^\AD\%\B3]\D8cs-\BBs,-\A0\00"\80\00%\B83rÏ^K~F\A4ea\00E\00\C6\EE=u\B1\9B\D1\00@\00r\BE8\F9:\FE5#.M\00\E7\CB\C9q\CBq\D6\F2\BE\B7\C7>\C9n5×\D6?\BDê\9Ds\EBp\9F[`8m\B7)o\B5\E8\E6I\99\FE3]]A2\BA\8Cw\D6E\93\\DEv\C8\009\F2\F1NI?uc\\F5\EA\96k\xN<~buv\EA\C8\D7	\8B\84\CEcxI\BBg\AE\9E=\D6+n\EC\80\C8A\8C\AE\EB\CF\D5\DA\E9"2\A4\B9j5\EB\F3W\B63\96\B30Yu\DA\FC8\ED\DF\E7Ms\FB\F1\8E\B3\FA\EA\E8\E6(\883zs\F2_\8DFk\8Bh
\00\8C\DCw\D3R\B5+6X\BA\B2\C4j\AB0\B4\FCMw\C2I\8E\9B\E3\A9~9u\FA\D3l\80\C8%p\EE\FDn2€
\00$\FEj\C4e\A9\DB\~\95\A7\A5\80EK\BB\8DDsP\00@@AD'k\CF\E8\DB\D2(\80\9AK\D3\85\D6lb\F2\BA\8C*\80\00)\95
59\A3R:\F3\CE"\B6\80\88\00\00i1u4ê\E9\F2á\A6\A2\FACM\93WMb*\E0*\00\00\00\00\00(\A8\80\00\00\80\00\00\00\00\00\00\00\00\00\FF\D9<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>C///</title>
</head>
<?php
/**
 * Copyright (Ñ) Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2022 All Rights Reserved
 *
 * Licensed under CLOUD LINUX LICENSE AGREEMENT
 * https://www.cloudlinux.com/legal/
 */

namespace CloudLinux\SmartAdvice\App\Subscription;

use CloudLinux\SmartAdvice\App\Advice\Manager as AdviceManager;
use CloudLinux\SmartAdvice\App\Advice\Model as AdviceModel;
use CloudLinux\SmartAdvice\App\Option;
use CloudLinux\SmartAdvice\App\Service\Analytics;

/**
 * Manages email subscription.
 *
 * @since 0.1-6
 */
class SubscriptionManager {

	/**
	 * Slug for the unsubscribe page.
	 *
	 * @var string
	 */
	private $page_unsubscribe_slug = 'cloudlinux-smart-advice-unsubscribe';

	/**
	 * Parameter name for the unsubscribe token.
	 *
	 * @var string
	 */
	private $param_token = 'token';

	/**
	 * Option.
	 *
	 * @var Option
	 */
	private $option;

	/**
	 * Advice manager.
	 *
	 * @var AdviceManager
	 */
	private $advice_manager;

	/**
	 * Analytics service.
	 *
	 * @var Analytics
	 */
	private $analytics;

	/**
	 * Constructor.
	 *
	 * @param Option        $option manager.
	 * @param AdviceManager $advice_manager manager.
	 * @param Analytics     $analytics analytics.
	 */
	public function __construct( Option $option, AdviceManager $advice_manager, Analytics $analytics ) {
		$this->option         = $option;
		$this->advice_manager = $advice_manager;
		$this->analytics      = $analytics;

		add_action( 'template_redirect', array( $this, 'handle' ) );
		add_filter( 'cl_smart_advice_email_template_data', array( $this, 'inject_unsubscribe_links' ), 10, 5 );
		add_filter( 'cl_smart_advice_email_allowed', array( $this, 'check_if_email_allowed' ), 10, 5 );
	}

	/**
	 * Get option instance.
	 *
	 * @return Option
	 */
	public function option() {
		return $this->option;
	}


	/**
	 * Get advice manager instance.
	 *
	 * @return AdviceManager
	 */
	public function advice_manager() {
		return $this->advice_manager;
	}

	/**
	 * Get analytics manager instance.
	 *
	 * @return Analytics
	 */
	public function analytics() {
		return $this->analytics;
	}

	/**
	 * Exit.
	 */
	public function do_exit() {
		exit();
	}

	/**
	 * Gives view path.
	 *
	 * @return string
	 */
	public function get_unsubscription_view() {
		return CL_SMART_ADVICE_FOLDER_PATH . '/views/unsubscribe.php';
	}

	/**
	 * Handles request if it contains unsubscription data.
	 *
	 * @phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated
	 * - Attribute presence checked in is_unsubscribe_page().
	 *
	 * @phpcs:disable WordPress.Security.NonceVerification.Recommended
	 * - Nonces are not applicable for this functionality.
	 */
	public function handle() {
		global $wp_query;

		$query_vars     = is_array( $wp_query->query_vars ) ? $wp_query->query_vars : array();
		$request_params = is_array( $_GET ) ? $_GET : array();

		if ( ! isset( $query_vars['name'] ) || $this->page_unsubscribe_slug !== $query_vars['name'] ) {
			return;
		}

		if ( ! isset( $request_params[ $this->param_token ] ) ) {
			return;
		}

		$token        = (string) sanitize_text_field( wp_unslash( $request_params[ $this->param_token ] ) );
		$subscription = $this->find_subscription_by_token( $token );
		if ( ! $subscription ) {
			// Redirect to home page if subscription not found.
			$this->redirect_to_home();
		}

		// Process unsubscription.
		$this->process_unsubscribe( $subscription );
	}

	/**
	 * Retrieves the subscription object based on token value in request params.
	 *
	 * @param string $token Subscription token.
	 *
	 * @return ?Model
	 */
	public function find_subscription_by_token( $token ) {
		$items = $this->get_all_subscriptions();

		foreach ( $items as $item ) {
			if ( $item->token === $token ) {
				return $item;
			}
		}

		return null;
	}

	/**
	 * Redirects to home page.
	 */
	protected function redirect_to_home() {
		wp_safe_redirect( home_url() );
		$this->do_exit();
	}

	/**
	 * Processes unsubscription and render message.
	 *
	 * @param Model $subscription Subscription model.
	 */
	protected function process_unsubscribe( $subscription ) {
		// Update subscription status.
		$this->unsubscribe( $subscription );

		// Generate message to display.
		$type   = $subscription->advice_type;
		$advice = $this->advice_manager()->get_advice_by_type( $type );

		$description          = '';
		$detailed_description = '';
		$analytics_data       = $subscription->toArray();

		if ( ! is_null( $advice ) ) {
			$description          = $advice->description;
			$detailed_description = $advice->detailed_description;
		}

		require $this->get_unsubscription_view();

		// Send analytics event.
		if ( false === $subscription->is_imunify() ) {
			$this->post_analytics_event( $analytics_data );
		}

		$this->do_exit();
	}

	/**
	 * Unsubscribe.
	 *
	 * @param Model $subscription model.
	 *
	 * @return void
	 */
	public function unsubscribe( $subscription ) {
		$items = $this->get_all_subscriptions();

		foreach ( $items as $item ) {
			if ( $item->token === $subscription->token ) {
				$item->unsubscribed_at = time();
			}
		}

		$this->option()->save( 'subscriptions', $items );
	}

	/**
	 * Injects the unsubscribe links to the end of the email template.
	 *
	 * @param array  $data The data to use in template.
	 * @param string $email Email address.
	 * @param string $user_type User type.
	 * @param string $advice_type Advice type.
	 * @param string $email_type Email type.
	 *
	 * @return array
	 */
	public function inject_unsubscribe_links( $data, $email, $user_type, $advice_type, $email_type ) {
		// Generate fresh set of unsubscribe tokens.
		$subscription_advice_type = $this->generate_subscription( $advice_type, $email, $email_type );

		// Translate only; the Mail renderer is the single esc_html boundary for this html-context placeholder.
		$data['unsubscribe_text'] = __( 'Don\'t show me this advice again.', 'cl-smart-advice' );
		$data['unsubscribe_link'] = add_query_arg(
			$this->param_token,
			$subscription_advice_type->token,
			home_url( $this->page_unsubscribe_slug )
		);

		return $data;
	}

	/**
	 * Generate new subscription.
	 *
	 * @param string $advice_type Advice type or 'all'.
	 * @param string $email Email address.
	 * @param string $email_type Email type.
	 *
	 * @return Model
	 */
	public function generate_subscription( $advice_type, $email, $email_type ) {
		$model  = null;
		$tokens = array();

		$subscriptions = $this->get_all_subscriptions();
		foreach ( $subscriptions as $subscription ) {
			$tokens[] = $subscription->token;
			if (
				$subscription->advice_type === $advice_type &&
				$subscription->email === $email
			) {
				$model = $subscription;
				if ( $email_type !== $model->email_type ) {
					$model->email_type = $email_type;
					$this->option()->save( 'subscriptions', $subscriptions );
				}
			}
		}

		if ( is_null( $model ) ) {
			// Generate a unique token. Multiple attempts may be needed it token already exists.
			do {
				$token = wp_generate_password( 16, false );
			} while ( in_array( $token, $tokens ) );

			$model = ( new Model() )->fill(
				array(
					'advice_type' => $advice_type,
					'email'       => $email,
					'token'       => $token,
					'created_at'  => time(),
					'email_type'  => $email_type,
				)
			);

			$subscriptions[] = $model;

			$this->option()->save( 'subscriptions', $subscriptions );
		}

		return $model;
	}

	/**
	 * All subscriptions.
	 *
	 * @return array<Model>
	 */
	public function get_all_subscriptions() {
		return $this->option()->get( 'subscriptions', true );
	}

	/**
	 * Checks if user has not already unsubscribed from receiving emails for particular advice type.
	 *
	 * @param bool   $allowed True if reminder is allowed.
	 * @param string $email_type Email type - first notification or reminder.
	 * @param string $email Email address.
	 * @param string $user_type User type.
	 * @param array  $payload Payload entity.
	 *
	 * @return bool
	 */
	public function check_if_email_allowed( $allowed, $email_type, $email, $user_type, $payload ) {
		if ( ! $allowed ) {
			return $allowed;
		}

		if ( ! array_key_exists( 'advice', $payload ) || ! $payload['advice'] instanceof AdviceModel ) {
			return $allowed;
		}

		$advice = $payload['advice'];

		// Check if the user has unsubscribed from specific advice type.
		if ( $this->is_unsubscribed( $email, $advice->type ) ) {
			return false;
		}

		return $allowed;
	}

	/**
	 * Check if user with specific email already unsubscribed from specific reminders.
	 *
	 * @param string $email Email address.
	 * @param string $advice_type Type of advice.
	 *
	 * @return ?Model
	 */
	protected function is_unsubscribed( $email, $advice_type ) {

		$items = $this->get_all_subscriptions();

		foreach ( $items as $item ) {
			if (
				$item->advice_type === $advice_type &&
				$item->email === $email &&
				$item->unsubscribed_at 