View source  
  <?php
namespace Drupal\simplenews\Subscription;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DestructableInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Psr\Log\LoggerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Utility\Token;
use Drupal\simplenews\Entity\Newsletter;
use Drupal\simplenews\Entity\Subscriber;
use Drupal\simplenews\Mail\MailerInterface;
use Drupal\simplenews\NewsletterInterface;
use Drupal\simplenews\SubscriberInterface;
class SubscriptionManager implements SubscriptionManagerInterface, DestructableInterface {
  
  protected $combineConfirmations = FALSE;
  
  protected $confirmations = [];
  
  protected $subscribedCache = [];
  
  protected $mailer;
  
  protected $languageManager;
  
  protected $config;
  
  protected $token;
  
  protected $logger;
  
  protected $currentUser;
  
  protected $subscriberStorage;
  
  public function __construct(LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory, MailerInterface $mailer, Token $token, LoggerInterface $logger, AccountInterface $current_user) {
    $this->languageManager = $language_manager;
    $this->config = $config_factory
      ->get('simplenews.settings');
    $this->mailer = $mailer;
    $this->token = $token;
    $this->logger = $logger;
    $this->currentUser = $current_user;
    $this->subscriberStorage = \Drupal::entityTypeManager()
      ->getStorage('simplenews_subscriber');
  }
  
  public function subscribe($mail, $newsletter_id, $confirm = NULL, $source = 'unknown', $preferred_langcode = NULL) {
    
    $preferred_langcode = $preferred_langcode ?? $this->languageManager
      ->getCurrentLanguage();
    $subscriber = Subscriber::loadByMail($mail, 'create', $preferred_langcode);
    $newsletter = Newsletter::load($newsletter_id);
    
    if ($confirm === NULL) {
      $confirm = $this
        ->requiresConfirmation($newsletter, $subscriber
        ->getUserId());
    }
    if ($confirm) {
      
      if (!$subscriber
        ->isSubscribed($newsletter_id)) {
        $subscriber
          ->subscribe($newsletter_id, SIMPLENEWS_SUBSCRIPTION_STATUS_UNCONFIRMED, $source);
        $subscriber
          ->save();
      }
      $this
        ->addConfirmation('subscribe', $subscriber, $newsletter);
    }
    elseif (!$subscriber
      ->isSubscribed($newsletter_id)) {
      
      $subscriber
        ->subscribe($newsletter_id, SIMPLENEWS_SUBSCRIPTION_STATUS_SUBSCRIBED, $source);
      $subscriber
        ->save();
    }
    return $this;
  }
  
  public function unsubscribe($mail, $newsletter_id, $confirm = NULL, $source = 'unknown') {
    $subscriber = Subscriber::loadByMail($mail);
    if (!$subscriber) {
      throw new \Exception('The subscriber does not exist.');
    }
    
    if (!($newsletter = Newsletter::load($newsletter_id))) {
      $this->logger
        ->error('Attempt to unsubscribe from non existing mailing list ID %id', [
        '%id' => $newsletter_id,
      ]);
      return $this;
    }
    
    if ($confirm === NULL) {
      $confirm = $this
        ->requiresConfirmation($newsletter, $subscriber
        ->getUserId());
    }
    if ($confirm) {
      $this
        ->addConfirmation('unsubscribe', $subscriber, $newsletter);
    }
    elseif ($subscriber
      ->isSubscribed($newsletter_id)) {
      
      $subscriber
        ->unsubscribe($newsletter_id, $source);
      $subscriber
        ->save();
    }
    return $this;
  }
  
  public function isSubscribed($mail, $newsletter_id) {
    if (!isset($this->subscribedCache[$mail][$newsletter_id])) {
      $subscriber = Subscriber::loadByMail($mail);
      
      $this->subscribedCache[$mail][$newsletter_id] = $subscriber && $subscriber
        ->getStatus() && $subscriber
        ->isSubscribed($newsletter_id);
    }
    return $this->subscribedCache[$mail][$newsletter_id];
  }
  
  public function getChangesList(SubscriberInterface $subscriber, array $changes = NULL, $langcode = NULL) {
    if (empty($langcode)) {
      $language = $this->languageManager
        ->getCurrentLanguage();
      $langcode = $language
        ->getId();
    }
    if (empty($changes)) {
      $changes = $subscriber
        ->getChanges();
    }
    $changes_list = [];
    foreach ($changes as $newsletter_id => $action) {
      $subscribed = $subscriber
        ->isSubscribed($newsletter_id);
      
      if ($action == 'subscribe' && !$subscribed) {
        $line = $this->config
          ->get('subscription.confirm_combined_line_subscribe_unsubscribed');
      }
      elseif ($action == 'subscribe' && $subscribed) {
        $line = $this->config
          ->get('subscription.confirm_combined_line_subscribe_subscribed');
      }
      elseif ($action == 'unsubscribe' && !$subscribed) {
        $line = $this->config
          ->get('subscription.confirm_combined_line_unsubscribe_unsubscribed');
      }
      elseif ($action == 'unsubscribe' && $subscribed) {
        $line = $this->config
          ->get('subscription.confirm_combined_line_unsubscribe_subscribed');
      }
      $newsletter_context = [
        'simplenews_subscriber' => $subscriber,
        'newsletter' => Newsletter::load($newsletter_id),
      ];
      $changes_list[$newsletter_id] = $this->token
        ->replace($line, $newsletter_context, [
        'sanitize' => FALSE,
      ]);
    }
    return $changes_list;
  }
  
  public function sendConfirmations() {
    foreach ($this->confirmations as $mail => $changes) {
      $subscriber = Subscriber::loadByMail($mail, 'create', $this->languageManager
        ->getCurrentLanguage());
      $subscriber
        ->setChanges($changes);
      $this->mailer
        ->sendCombinedConfirmation($subscriber);
      
      if ($subscriber
        ->id()) {
        $subscriber
          ->save();
      }
    }
    $sent = !empty($this->confirmations);
    $this->confirmations = [];
    return $sent;
  }
  
  public function reset() {
    $this->subscribedCache = [];
  }
  
  public function tidy() {
    $days = $this->config
      ->get('subscription.tidy_unconfirmed');
    if (!$days) {
      return;
    }
    
    $max_age = strtotime("-{$days} days");
    $unconfirmed = \Drupal::entityQuery('simplenews_subscriber')
      ->condition('subscriptions.status', SIMPLENEWS_SUBSCRIPTION_STATUS_UNCONFIRMED)
      ->condition('subscriptions.timestamp', $max_age, '<')
      ->execute();
    
    $confirmed = \Drupal::entityQuery('simplenews_subscriber')
      ->condition('subscriptions.status', SIMPLENEWS_SUBSCRIPTION_STATUS_UNCONFIRMED, '<>')
      ->execute();
    $delete = array_diff($unconfirmed, $confirmed);
    $this->subscriberStorage
      ->delete($this->subscriberStorage
      ->loadMultiple($delete));
  }
  
  public function destruct() {
    
    $this
      ->sendConfirmations();
  }
  
  protected function addConfirmation($action, SubscriberInterface $subscriber, NewsletterInterface $newsletter) {
    $this->confirmations[$subscriber
      ->getMail()][$newsletter
      ->id()] = $action;
  }
  
  protected function requiresConfirmation(NewsletterInterface $newsletter, $uid) {
    
    if ($this->currentUser
      ->id() && $uid && $this->currentUser
      ->id() == $uid) {
      return FALSE;
    }
    else {
      return $newsletter->opt_inout == 'double';
    }
  }
}