Seditio Source
Root |
./othercms/xenForo 2.2.8/src/XF/EmailBounce/Processor.php
<?php

namespace XF\EmailBounce;

use
XF\App;
use
XF\Entity\User;
use
Laminas\Mail\Storage;

class
Processor
{
   
/**
     * @var App
     */
   
protected $app;

   
/**
     * @var Parser
     */
   
protected $bounceParser;

   
/**
     * @var \XF\Repository\EmailBounce
     */
   
protected $bounceRepo;

    public function
__construct(App $app, Parser $parser)
    {
       
$this->app = $app;
       
$this->bounceParser = $parser;
       
$this->bounceRepo = $app->repository('XF:EmailBounce');
    }

    public function
processFromStorage(Storage\AbstractStorage $storage, $maxRunTime = 0)
    {
       
$s = microtime(true);

       
$total = $storage->countMessages();
        if (!
$total)
        {
            return
true;
        }

       
$finished = true;

        for (
$messageId = $total; $messageId > 0; $messageId--)
        {
           
$contentValid = false;

            try
            {
               
$headers = $storage->getRawHeader($messageId);
               
$content = $storage->getRawContent($messageId);
               
$contentValid = true;
            }
            catch (\
InvalidArgumentException $e)
            {
               
$headers = '';
               
$content = '';
               
$contentValid = false;
            }
            finally
            {
               
$storage->removeMessage($messageId);
            }

           
$rawMessage = trim($headers) . "\r\n\r\n" . trim($content);

            if (
$contentValid)
            {
               
$this->processMessage($rawMessage);
            }
            else
            {
               
$result = new ParseResult();
               
$result->date = \XF::$time;

               
$this->logBounceMessage($rawMessage, null, $result);
            }

            if (
$maxRunTime && microtime(true) - $s > $maxRunTime)
            {
               
$finished = false;
                break;
            }
        }

        return
$finished;
    }

    public function
processMessage($rawMessage, &$result = null)
    {
        try
        {
           
$result = $this->bounceParser->parseMessage($rawMessage);
        }
        catch (\
Exception $e)
        {
            \
XF::logException($e, false, "Bounce message processing failed: ");

           
$result = new ParseResult();
           
$result->date = \XF::$time;

            return
$this->logBounceMessage($rawMessage, null, $result);
        }

        if (
$result->recipient)
        {
           
/** @var User $user */
           
$user = $this->app->em()->findOne('XF:User', ['email' => $result->recipient]);
        }
        else
        {
           
$user = null;
        }

       
$action = null;

        if (
$user && $result->messageType == ParseResult::TYPE_BOUNCE)
        {
            if (
$result->recipientTrusted)
            {
               
$bounceType = $this->bounceParser->getBounceTypeFromStatus($result->remoteStatus);
                if (
$bounceType)
                {
                   
$action = $this->takeBounceAction($user, $bounceType, $result->date);
                }
            }
            else
            {
               
$action = 'untrusted';
            }
        }

        return
$this->logBounceMessage($rawMessage, $action, $result, $user);
    }

    public function
takeBounceAction(User $user, $bounceType, $bounceDate)
    {
        if (
$bounceType == 'hard')
        {
           
$this->triggerUserBounceAction($user);
            return
'hard';
        }
        else if (
$bounceType == 'soft')
        {
           
$this->bounceRepo->insertSoftBounceLogEntry($user->user_id, $bounceDate);

            if (
$this->hasSoftBouncedTooMuch($user->user_id))
            {
               
$this->triggerUserBounceAction($user);
                return
'soft_hard';
            }

            return
'soft';
        }
        else
        {
            throw new \
InvalidArgumentException("Unknown bounce type '$bounceType'");
        }
    }

    public function
triggerUserBounceAction(User $user)
    {
        if (
$this->canApplyUserBounceAction($user))
        {
           
$this->applyUserBounceAction($user);

            return
true;
        }

        return
false;
    }

    public function
applyUserBounceAction(User $user)
    {
       
$user->user_state = 'email_bounce';
       
$user->save();
    }

    protected function
canApplyUserBounceAction(User $user)
    {
        return (
           
$user->user_state == 'valid'
           
&& !$user->is_moderator
           
&& !$user->is_admin
           
&& !$user->is_staff
       
);
    }

    protected function
hasSoftBouncedTooMuch($userId)
    {
       
$bounces = $this->bounceRepo->countRecentSoftBounces($userId, 30);
        if (!
$bounces['bounce_total'])
        {
            return
false;
        }

       
$thresholds = $this->app->options()->emailSoftBounceThreshold;
        return (
           
$bounces['bounce_total'] >= $thresholds['bounce_total']
            &&
$bounces['unique_days'] >= $thresholds['unique_days']
            &&
$bounces['days_between'] >= $thresholds['days_between']
        );
    }

    protected function
logBounceMessage($rawMessage, $action, ParseResult $result, User $user = null)
    {
       
$bounce = $this->app->em()->create('XF:EmailBounceLog');
       
$bounce->bulkSet([
           
'email_date' => $result->date,
           
'message_type' => $result->messageType ?: ParseResult::TYPE_UNKNOWN,
           
'action_taken' => $action ?: '',
           
'user_id' => $user ? $user->user_id : null,
           
'recipient' => $result->recipient,
           
'raw_message' => substr($rawMessage, 0, 1024 * 1024), // limit logging to 1MB to prevent potential insert issues
           
'status_code' => $result->remoteStatus,
           
'diagnostic_info' => $result->remoteDiagnostics
       
]);
       
$bounce->save();

        return
$bounce;
    }

   
/**
     * @param App $app
     *
     * @return null|Storage\AbstractStorage
     */
   
public static function getDefaultBounceHandlerStorage(App $app)
    {
       
$options = $app->options();

        if (!
$options->bounceEmailAddress)
        {
            return
null;
        }

       
$handler = $options->emailBounceHandler;
        if (!
$handler || empty($handler['enabled']))
        {
            return
null;
        }

       
/** @var \XF\Repository\Option $optionRepo */
       
$optionRepo = $app->repository('XF:Option');
       
$handler = $optionRepo->refreshEmailAccessTokenIfNeeded('emailBounceHandler');

        try
        {
            if (
$handler['type'] == 'pop3')
            {
               
$connection = \XF\Mail\Storage\Pop3::setupFromHandler($handler);
            }
            else if (
$handler['type'] == 'imap')
            {
               
$connection = \XF\Mail\Storage\Imap::setupFromHandler($handler);
            }
            else
            {
                throw new \
Exception("Unknown email bounce handler $handler[type]");
            }
        }
        catch (\
Laminas\Mail\Exception\ExceptionInterface $e)
        {
           
$app->logException($e, false, "Bounce connection error: ");
            return
null;
        }

        return
$connection;
    }
}