OwlCyberSecurity - MANAGER
Edit File: SubscriptionManager.php
<?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. $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 ); $data['unsubscribe_text'] = esc_html__( '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 > 0 ) { return $item; } } return null; } /** * Sends analytics event. * * @param array $data Data. * * @return void */ public function post_analytics_event( $data ) { $analytics_data = $this->get_analytics_data( $data ); if ( is_null( $analytics_data ) ) { return; } $session = $analytics_data['session']; $this->analytics()->send_event( $analytics_data['email'], $analytics_data['advice_id'], $analytics_data['event'], ( isset( $session['user_hash'] ) ) ? $session['user_hash'] : null, ( isset( $session['journey_id'] ) ) ? $session['journey_id'] : null ); } /** * Prepare analytics data. * * @param array $data Data. * * @return array|null */ public function get_analytics_data( $data ) { // Check received params. $session = ( ! empty( $_GET ) ) ? $this->analytics()->extract_session_params( $_GET ) : array(); $advice = $this->advice_manager()->get_advice_by_type( $data['advice_type'] ); $email = $data['email']; if ( is_null( $advice ) ) { do_action( 'cl_smart_advice_set_error', E_ERROR, 'SubscriptionManager. Advice not found.', __FILE__, __LINE__, $data ); return null; } $event = ''; switch ( $data['email_type'] ) { case 'reminders': $event = 'reminder_cancelled'; break; case 'advices': $event = 'email_cancelled'; break; } if ( empty( $event ) ) { do_action( 'cl_smart_advice_set_error', E_ERROR, 'SubscriptionManager. Email type not found: ' . $data['email_type'], __FILE__, __LINE__, $data ); return null; } return array( 'email' => $email, 'advice_id' => $advice->id, 'event' => $event, 'session' => $session, ); } }